* [PATCH net-next v2 0/3] add flow director for txgbe
@ 2024-06-05 2:08 Jiawen Wu
2024-06-05 2:08 ` [PATCH net-next v2 1/3] net: txgbe: add FDIR ATR support Jiawen Wu
` (3 more replies)
0 siblings, 4 replies; 12+ messages in thread
From: Jiawen Wu @ 2024-06-05 2:08 UTC (permalink / raw)
To: davem, edumazet, kuba, pabeni, linux, horms, andrew, netdev
Cc: mengyuanlou, Jiawen Wu
Add flow director support for Wangxun 10Gb NICs.
v1 -> v2:
- Fix build warnings reported by kernel test robot.
Jiawen Wu (3):
net: txgbe: add FDIR ATR support
net: txgbe: support Flow Director perfect filters
net: txgbe: add FDIR info to ethtool ops
.../net/ethernet/wangxun/libwx/wx_ethtool.c | 39 +-
drivers/net/ethernet/wangxun/libwx/wx_hw.c | 32 +-
drivers/net/ethernet/wangxun/libwx/wx_hw.h | 2 +
drivers/net/ethernet/wangxun/libwx/wx_lib.c | 62 +-
drivers/net/ethernet/wangxun/libwx/wx_lib.h | 1 +
drivers/net/ethernet/wangxun/libwx/wx_type.h | 56 +-
drivers/net/ethernet/wangxun/txgbe/Makefile | 1 +
.../ethernet/wangxun/txgbe/txgbe_ethtool.c | 417 ++++++++++++
.../net/ethernet/wangxun/txgbe/txgbe_fdir.c | 631 ++++++++++++++++++
.../net/ethernet/wangxun/txgbe/txgbe_fdir.h | 18 +
.../net/ethernet/wangxun/txgbe/txgbe_main.c | 18 +
.../net/ethernet/wangxun/txgbe/txgbe_type.h | 147 ++++
12 files changed, 1413 insertions(+), 11 deletions(-)
create mode 100644 drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.c
create mode 100644 drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.h
--
2.27.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH net-next v2 1/3] net: txgbe: add FDIR ATR support
2024-06-05 2:08 [PATCH net-next v2 0/3] add flow director for txgbe Jiawen Wu
@ 2024-06-05 2:08 ` Jiawen Wu
2024-06-05 2:08 ` [PATCH net-next v2 2/3] net: txgbe: support Flow Director perfect filters Jiawen Wu
` (2 subsequent siblings)
3 siblings, 0 replies; 12+ messages in thread
From: Jiawen Wu @ 2024-06-05 2:08 UTC (permalink / raw)
To: davem, edumazet, kuba, pabeni, linux, horms, andrew, netdev
Cc: mengyuanlou, Jiawen Wu
Add flow director ATR filter. ATR mode is enabled by default to filter
TCP packets.
Signed-off-by: Jiawen Wu <jiawenwu@trustnetic.com>
---
drivers/net/ethernet/wangxun/libwx/wx_hw.c | 27 +-
drivers/net/ethernet/wangxun/libwx/wx_hw.h | 2 +
drivers/net/ethernet/wangxun/libwx/wx_lib.c | 31 +-
drivers/net/ethernet/wangxun/libwx/wx_lib.h | 1 +
drivers/net/ethernet/wangxun/libwx/wx_type.h | 52 ++-
drivers/net/ethernet/wangxun/txgbe/Makefile | 1 +
.../net/ethernet/wangxun/txgbe/txgbe_fdir.c | 299 ++++++++++++++++++
.../net/ethernet/wangxun/txgbe/txgbe_fdir.h | 10 +
.../net/ethernet/wangxun/txgbe/txgbe_main.c | 9 +
.../net/ethernet/wangxun/txgbe/txgbe_type.h | 121 +++++++
10 files changed, 545 insertions(+), 8 deletions(-)
create mode 100644 drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.c
create mode 100644 drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.h
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_hw.c b/drivers/net/ethernet/wangxun/libwx/wx_hw.c
index 7c4b6881a93f..8fb38f83a615 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_hw.c
+++ b/drivers/net/ethernet/wangxun/libwx/wx_hw.c
@@ -1147,8 +1147,15 @@ static void wx_enable_rx(struct wx *wx)
static void wx_set_rxpba(struct wx *wx)
{
u32 rxpktsize, txpktsize, txpbthresh;
+ u32 pbsize = wx->mac.rx_pb_size;
- rxpktsize = wx->mac.rx_pb_size << WX_RDB_PB_SZ_SHIFT;
+ if (test_bit(WX_FLAG_FDIR_CAPABLE, wx->flags)) {
+ if (test_bit(WX_FLAG_FDIR_HASH, wx->flags) ||
+ test_bit(WX_FLAG_FDIR_PERFECT, wx->flags))
+ pbsize -= 64; /* Default 64KB */
+ }
+
+ rxpktsize = pbsize << WX_RDB_PB_SZ_SHIFT;
wr32(wx, WX_RDB_PB_SZ(0), rxpktsize);
/* Only support an equally distributed Tx packet buffer strategy. */
@@ -1261,7 +1268,7 @@ static void wx_configure_port(struct wx *wx)
* Stops the receive data path and waits for the HW to internally empty
* the Rx security block
**/
-static int wx_disable_sec_rx_path(struct wx *wx)
+int wx_disable_sec_rx_path(struct wx *wx)
{
u32 secrx;
@@ -1271,6 +1278,7 @@ static int wx_disable_sec_rx_path(struct wx *wx)
return read_poll_timeout(rd32, secrx, secrx & WX_RSC_ST_RSEC_RDY,
1000, 40000, false, wx, WX_RSC_ST);
}
+EXPORT_SYMBOL(wx_disable_sec_rx_path);
/**
* wx_enable_sec_rx_path - Enables the receive data path
@@ -1278,11 +1286,12 @@ static int wx_disable_sec_rx_path(struct wx *wx)
*
* Enables the receive data path.
**/
-static void wx_enable_sec_rx_path(struct wx *wx)
+void wx_enable_sec_rx_path(struct wx *wx)
{
wr32m(wx, WX_RSC_CTL, WX_RSC_CTL_RX_DIS, 0);
WX_WRITE_FLUSH(wx);
}
+EXPORT_SYMBOL(wx_enable_sec_rx_path);
static void wx_vlan_strip_control(struct wx *wx, bool enable)
{
@@ -1499,6 +1508,13 @@ static void wx_configure_tx_ring(struct wx *wx,
txdctl |= ring->count / 128 << WX_PX_TR_CFG_TR_SIZE_SHIFT;
txdctl |= 0x20 << WX_PX_TR_CFG_WTHRESH_SHIFT;
+ ring->atr_count = 0;
+ if (test_bit(WX_FLAG_FDIR_CAPABLE, wx->flags) &&
+ test_bit(WX_FLAG_FDIR_HASH, wx->flags))
+ ring->atr_sample_rate = wx->atr_sample_rate;
+ else
+ ring->atr_sample_rate = 0;
+
/* reinitialize tx_buffer_info */
memset(ring->tx_buffer_info, 0,
sizeof(struct wx_tx_buffer) * ring->count);
@@ -1732,7 +1748,9 @@ void wx_configure(struct wx *wx)
wx_set_rx_mode(wx->netdev);
wx_restore_vlan(wx);
- wx_enable_sec_rx_path(wx);
+
+ if (test_bit(WX_FLAG_FDIR_CAPABLE, wx->flags))
+ wx->configure_fdir(wx);
wx_configure_tx(wx);
wx_configure_rx(wx);
@@ -1959,6 +1977,7 @@ int wx_sw_init(struct wx *wx)
}
bitmap_zero(wx->state, WX_STATE_NBITS);
+ bitmap_zero(wx->flags, WX_PF_FLAGS_NBITS);
return 0;
}
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_hw.h b/drivers/net/ethernet/wangxun/libwx/wx_hw.h
index 9e219fa717a2..11fb33349482 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_hw.h
+++ b/drivers/net/ethernet/wangxun/libwx/wx_hw.h
@@ -28,6 +28,8 @@ void wx_mac_set_default_filter(struct wx *wx, u8 *addr);
void wx_flush_sw_mac_table(struct wx *wx);
int wx_set_mac(struct net_device *netdev, void *p);
void wx_disable_rx(struct wx *wx);
+int wx_disable_sec_rx_path(struct wx *wx);
+void wx_enable_sec_rx_path(struct wx *wx);
void wx_set_rx_mode(struct net_device *netdev);
int wx_change_mtu(struct net_device *netdev, int new_mtu);
void wx_disable_rx_queue(struct wx *wx, struct wx_ring *ring);
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_lib.c b/drivers/net/ethernet/wangxun/libwx/wx_lib.c
index 07ba3a270a14..8774206ca496 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_lib.c
+++ b/drivers/net/ethernet/wangxun/libwx/wx_lib.c
@@ -148,10 +148,11 @@ static struct wx_dec_ptype wx_ptype_lookup[256] = {
[0xFD] = WX_PTT(IP, IPV6, IGMV, IPV6, SCTP, PAY4),
};
-static struct wx_dec_ptype wx_decode_ptype(const u8 ptype)
+struct wx_dec_ptype wx_decode_ptype(const u8 ptype)
{
return wx_ptype_lookup[ptype];
}
+EXPORT_SYMBOL(wx_decode_ptype);
/* wx_test_staterr - tests bits in Rx descriptor status and error fields */
static __le32 wx_test_staterr(union wx_rx_desc *rx_desc,
@@ -1453,6 +1454,7 @@ static void wx_tx_csum(struct wx_ring *tx_ring, struct wx_tx_buffer *first,
static netdev_tx_t wx_xmit_frame_ring(struct sk_buff *skb,
struct wx_ring *tx_ring)
{
+ struct wx *wx = netdev_priv(tx_ring->netdev);
u16 count = TXD_USE_COUNT(skb_headlen(skb));
struct wx_tx_buffer *first;
u8 hdr_len = 0, ptype;
@@ -1498,6 +1500,10 @@ static netdev_tx_t wx_xmit_frame_ring(struct sk_buff *skb,
goto out_drop;
else if (!tso)
wx_tx_csum(tx_ring, first, ptype);
+
+ if (test_bit(WX_FLAG_FDIR_CAPABLE, wx->flags) && tx_ring->atr_sample_rate)
+ wx->atr(tx_ring, first, ptype);
+
wx_tx_map(tx_ring, first, hdr_len);
return NETDEV_TX_OK;
@@ -1574,8 +1580,27 @@ static void wx_set_rss_queues(struct wx *wx)
f = &wx->ring_feature[RING_F_RSS];
f->indices = f->limit;
- wx->num_rx_queues = f->limit;
- wx->num_tx_queues = f->limit;
+ if (!(test_bit(WX_FLAG_FDIR_CAPABLE, wx->flags)))
+ goto out;
+
+ clear_bit(WX_FLAG_FDIR_HASH, wx->flags);
+
+ /* Use Flow Director in addition to RSS to ensure the best
+ * distribution of flows across cores, even when an FDIR flow
+ * isn't matched.
+ */
+ if (f->indices > 1) {
+ f = &wx->ring_feature[RING_F_FDIR];
+
+ f->indices = f->limit;
+
+ if (!(test_bit(WX_FLAG_FDIR_PERFECT, wx->flags)))
+ set_bit(WX_FLAG_FDIR_HASH, wx->flags);
+ }
+
+out:
+ wx->num_rx_queues = f->indices;
+ wx->num_tx_queues = f->indices;
}
static void wx_set_num_queues(struct wx *wx)
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_lib.h b/drivers/net/ethernet/wangxun/libwx/wx_lib.h
index c41b29ea812f..fdeb0c315b75 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_lib.h
+++ b/drivers/net/ethernet/wangxun/libwx/wx_lib.h
@@ -7,6 +7,7 @@
#ifndef _WX_LIB_H_
#define _WX_LIB_H_
+struct wx_dec_ptype wx_decode_ptype(const u8 ptype);
void wx_alloc_rx_buffers(struct wx_ring *rx_ring, u16 cleaned_count);
u16 wx_desc_unused(struct wx_ring *ring);
netdev_tx_t wx_xmit_frame(struct sk_buff *skb,
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_type.h b/drivers/net/ethernet/wangxun/libwx/wx_type.h
index 5aaf7b1fa2db..b1f9bab06e90 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_type.h
+++ b/drivers/net/ethernet/wangxun/libwx/wx_type.h
@@ -503,6 +503,34 @@ enum WX_MSCA_CMD_value {
#define WX_PTYPE_TYP_TCP 0x04
#define WX_PTYPE_TYP_SCTP 0x05
+/* Packet type non-ip values */
+enum wx_l2_ptypes {
+ WX_PTYPE_L2_ABORTED = (WX_PTYPE_PKT_MAC),
+ WX_PTYPE_L2_MAC = (WX_PTYPE_PKT_MAC | WX_PTYPE_TYP_MAC),
+
+ WX_PTYPE_L2_IPV4_FRAG = (WX_PTYPE_PKT_IP | WX_PTYPE_TYP_IPFRAG),
+ WX_PTYPE_L2_IPV4 = (WX_PTYPE_PKT_IP | WX_PTYPE_TYP_IP),
+ WX_PTYPE_L2_IPV4_UDP = (WX_PTYPE_PKT_IP | WX_PTYPE_TYP_UDP),
+ WX_PTYPE_L2_IPV4_TCP = (WX_PTYPE_PKT_IP | WX_PTYPE_TYP_TCP),
+ WX_PTYPE_L2_IPV4_SCTP = (WX_PTYPE_PKT_IP | WX_PTYPE_TYP_SCTP),
+ WX_PTYPE_L2_IPV6_FRAG = (WX_PTYPE_PKT_IP | WX_PTYPE_PKT_IPV6 |
+ WX_PTYPE_TYP_IPFRAG),
+ WX_PTYPE_L2_IPV6 = (WX_PTYPE_PKT_IP | WX_PTYPE_PKT_IPV6 |
+ WX_PTYPE_TYP_IP),
+ WX_PTYPE_L2_IPV6_UDP = (WX_PTYPE_PKT_IP | WX_PTYPE_PKT_IPV6 |
+ WX_PTYPE_TYP_UDP),
+ WX_PTYPE_L2_IPV6_TCP = (WX_PTYPE_PKT_IP | WX_PTYPE_PKT_IPV6 |
+ WX_PTYPE_TYP_TCP),
+ WX_PTYPE_L2_IPV6_SCTP = (WX_PTYPE_PKT_IP | WX_PTYPE_PKT_IPV6 |
+ WX_PTYPE_TYP_SCTP),
+
+ WX_PTYPE_L2_TUN4_MAC = (WX_PTYPE_TUN_IPV4 | WX_PTYPE_PKT_IGM),
+ WX_PTYPE_L2_TUN6_MAC = (WX_PTYPE_TUN_IPV6 | WX_PTYPE_PKT_IGM),
+};
+
+#define WX_PTYPE_PKT(_pt) ((_pt) & 0x30)
+#define WX_PTYPE_TYPL4(_pt) ((_pt) & 0x07)
+
#define WX_RXD_PKTTYPE(_rxd) \
((le32_to_cpu((_rxd)->wb.lower.lo_dword.data) >> 9) & 0xFF)
#define WX_RXD_IPV6EX(_rxd) \
@@ -552,6 +580,9 @@ enum wx_tx_flags {
WX_TX_FLAGS_OUTER_IPV4 = 0x100,
WX_TX_FLAGS_LINKSEC = 0x200,
WX_TX_FLAGS_IPSEC = 0x400,
+
+ /* software defined flags */
+ WX_TX_FLAGS_SW_VLAN = 0x40,
};
/* VLAN info */
@@ -900,7 +931,13 @@ struct wx_ring {
*/
u16 next_to_use;
u16 next_to_clean;
- u16 next_to_alloc;
+ union {
+ u16 next_to_alloc;
+ struct {
+ u8 atr_sample_rate;
+ u8 atr_count;
+ };
+ };
struct wx_queue_stats stats;
struct u64_stats_sync syncp;
@@ -939,6 +976,7 @@ struct wx_ring_feature {
enum wx_ring_f_enum {
RING_F_NONE = 0,
RING_F_RSS,
+ RING_F_FDIR,
RING_F_ARRAY_SIZE /* must be last in enum set */
};
@@ -986,9 +1024,18 @@ enum wx_state {
WX_STATE_RESETTING,
WX_STATE_NBITS, /* must be last */
};
+
+enum wx_pf_flags {
+ WX_FLAG_FDIR_CAPABLE,
+ WX_FLAG_FDIR_HASH,
+ WX_FLAG_FDIR_PERFECT,
+ WX_PF_FLAGS_NBITS /* must be last */
+};
+
struct wx {
unsigned long active_vlans[BITS_TO_LONGS(VLAN_N_VID)];
DECLARE_BITMAP(state, WX_STATE_NBITS);
+ DECLARE_BITMAP(flags, WX_PF_FLAGS_NBITS);
void *priv;
u8 __iomem *hw_addr;
@@ -1077,6 +1124,9 @@ struct wx {
u64 hw_csum_rx_error;
u64 alloc_rx_buff_failed;
+ u32 atr_sample_rate;
+ void (*atr)(struct wx_ring *ring, struct wx_tx_buffer *first, u8 ptype);
+ void (*configure_fdir)(struct wx *wx);
void (*do_reset)(struct net_device *netdev);
};
diff --git a/drivers/net/ethernet/wangxun/txgbe/Makefile b/drivers/net/ethernet/wangxun/txgbe/Makefile
index 42718875277c..f74576fe7062 100644
--- a/drivers/net/ethernet/wangxun/txgbe/Makefile
+++ b/drivers/net/ethernet/wangxun/txgbe/Makefile
@@ -10,4 +10,5 @@ txgbe-objs := txgbe_main.o \
txgbe_hw.o \
txgbe_phy.o \
txgbe_irq.o \
+ txgbe_fdir.o \
txgbe_ethtool.o
diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.c
new file mode 100644
index 000000000000..b10676c00cea
--- /dev/null
+++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.c
@@ -0,0 +1,299 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2015 - 2024 Beijing WangXun Technology Co., Ltd. */
+
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/pci.h>
+
+#include "../libwx/wx_type.h"
+#include "../libwx/wx_lib.h"
+#include "../libwx/wx_hw.h"
+#include "txgbe_type.h"
+#include "txgbe_fdir.h"
+
+/* These defines allow us to quickly generate all of the necessary instructions
+ * in the function below by simply calling out TXGBE_COMPUTE_SIG_HASH_ITERATION
+ * for values 0 through 15
+ */
+#define TXGBE_ATR_COMMON_HASH_KEY \
+ (TXGBE_ATR_BUCKET_HASH_KEY & TXGBE_ATR_SIGNATURE_HASH_KEY)
+#define TXGBE_COMPUTE_SIG_HASH_ITERATION(_n) \
+do { \
+ u32 n = (_n); \
+ if (TXGBE_ATR_COMMON_HASH_KEY & (0x01 << n)) \
+ common_hash ^= lo_hash_dword >> n; \
+ else if (TXGBE_ATR_BUCKET_HASH_KEY & (0x01 << n)) \
+ bucket_hash ^= lo_hash_dword >> n; \
+ else if (TXGBE_ATR_SIGNATURE_HASH_KEY & (0x01 << n)) \
+ sig_hash ^= lo_hash_dword << (16 - n); \
+ if (TXGBE_ATR_COMMON_HASH_KEY & (0x01 << (n + 16))) \
+ common_hash ^= hi_hash_dword >> n; \
+ else if (TXGBE_ATR_BUCKET_HASH_KEY & (0x01 << (n + 16))) \
+ bucket_hash ^= hi_hash_dword >> n; \
+ else if (TXGBE_ATR_SIGNATURE_HASH_KEY & (0x01 << (n + 16))) \
+ sig_hash ^= hi_hash_dword << (16 - n); \
+} while (0)
+
+/**
+ * txgbe_atr_compute_sig_hash - Compute the signature hash
+ * @input: input bitstream to compute the hash on
+ * @common: compressed common input dword
+ * @hash: pointer to the computed hash
+ *
+ * This function is almost identical to the function above but contains
+ * several optimizations such as unwinding all of the loops, letting the
+ * compiler work out all of the conditional ifs since the keys are static
+ * defines, and computing two keys at once since the hashed dword stream
+ * will be the same for both keys.
+ **/
+static void txgbe_atr_compute_sig_hash(union txgbe_atr_hash_dword input,
+ union txgbe_atr_hash_dword common,
+ u32 *hash)
+{
+ u32 sig_hash = 0, bucket_hash = 0, common_hash = 0;
+ u32 hi_hash_dword, lo_hash_dword, flow_vm_vlan;
+ u32 i;
+
+ /* record the flow_vm_vlan bits as they are a key part to the hash */
+ flow_vm_vlan = ntohl(input.dword);
+
+ /* generate common hash dword */
+ hi_hash_dword = ntohl(common.dword);
+
+ /* low dword is word swapped version of common */
+ lo_hash_dword = (hi_hash_dword >> 16) | (hi_hash_dword << 16);
+
+ /* apply flow ID/VM pool/VLAN ID bits to hash words */
+ hi_hash_dword ^= flow_vm_vlan ^ (flow_vm_vlan >> 16);
+
+ /* Process bits 0 and 16 */
+ TXGBE_COMPUTE_SIG_HASH_ITERATION(0);
+
+ /* apply flow ID/VM pool/VLAN ID bits to lo hash dword, we had to
+ * delay this because bit 0 of the stream should not be processed
+ * so we do not add the VLAN until after bit 0 was processed
+ */
+ lo_hash_dword ^= flow_vm_vlan ^ (flow_vm_vlan << 16);
+
+ /* Process remaining 30 bit of the key */
+ for (i = 1; i <= 15; i++)
+ TXGBE_COMPUTE_SIG_HASH_ITERATION(i);
+
+ /* combine common_hash result with signature and bucket hashes */
+ bucket_hash ^= common_hash;
+ bucket_hash &= TXGBE_ATR_HASH_MASK;
+
+ sig_hash ^= common_hash << 16;
+ sig_hash &= TXGBE_ATR_HASH_MASK << 16;
+
+ /* return completed signature hash */
+ *hash = sig_hash ^ bucket_hash;
+}
+
+static int txgbe_fdir_check_cmd_complete(struct wx *wx)
+{
+ u32 val;
+
+ return read_poll_timeout_atomic(rd32, val,
+ !(val & TXGBE_RDB_FDIR_CMD_CMD_MASK),
+ 10, 100, false, wx, TXGBE_RDB_FDIR_CMD);
+}
+
+/**
+ * txgbe_fdir_add_signature_filter - Adds a signature hash filter
+ * @wx: pointer to hardware structure
+ * @input: unique input dword
+ * @common: compressed common input dword
+ * @queue: queue index to direct traffic to
+ **/
+static int txgbe_fdir_add_signature_filter(struct wx *wx,
+ union txgbe_atr_hash_dword input,
+ union txgbe_atr_hash_dword common,
+ u8 queue)
+{
+ u32 fdirhashcmd, fdircmd;
+ u8 flow_type;
+ int err;
+
+ /* Get the flow_type in order to program FDIRCMD properly
+ * lowest 2 bits are FDIRCMD.L4TYPE, third lowest bit is FDIRCMD.IPV6
+ * fifth is FDIRCMD.TUNNEL_FILTER
+ */
+ flow_type = input.formatted.flow_type;
+ switch (flow_type) {
+ case TXGBE_ATR_FLOW_TYPE_TCPV4:
+ case TXGBE_ATR_FLOW_TYPE_UDPV4:
+ case TXGBE_ATR_FLOW_TYPE_SCTPV4:
+ case TXGBE_ATR_FLOW_TYPE_TCPV6:
+ case TXGBE_ATR_FLOW_TYPE_UDPV6:
+ case TXGBE_ATR_FLOW_TYPE_SCTPV6:
+ break;
+ default:
+ wx_err(wx, "Error on flow type input\n");
+ return -EINVAL;
+ }
+
+ /* configure FDIRCMD register */
+ fdircmd = TXGBE_RDB_FDIR_CMD_CMD_ADD_FLOW |
+ TXGBE_RDB_FDIR_CMD_FILTER_UPDATE |
+ TXGBE_RDB_FDIR_CMD_LAST | TXGBE_RDB_FDIR_CMD_QUEUE_EN;
+ fdircmd |= TXGBE_RDB_FDIR_CMD_FLOW_TYPE(flow_type);
+ fdircmd |= TXGBE_RDB_FDIR_CMD_RX_QUEUE(queue);
+
+ txgbe_atr_compute_sig_hash(input, common, &fdirhashcmd);
+ fdirhashcmd |= TXGBE_RDB_FDIR_HASH_BUCKET_VALID;
+ wr32(wx, TXGBE_RDB_FDIR_HASH, fdirhashcmd);
+ wr32(wx, TXGBE_RDB_FDIR_CMD, fdircmd);
+
+ wx_dbg(wx, "Tx Queue=%x hash=%x\n", queue, (u32)fdirhashcmd);
+
+ err = txgbe_fdir_check_cmd_complete(wx);
+ if (err)
+ wx_err(wx, "Flow Director command did not complete!\n");
+
+ return err;
+}
+
+void txgbe_atr(struct wx_ring *ring, struct wx_tx_buffer *first, u8 ptype)
+{
+ union txgbe_atr_hash_dword common = { .dword = 0 };
+ union txgbe_atr_hash_dword input = { .dword = 0 };
+ struct wx_q_vector *q_vector = ring->q_vector;
+ struct wx_dec_ptype dptype;
+ union network_header {
+ struct ipv6hdr *ipv6;
+ struct iphdr *ipv4;
+ void *raw;
+ } hdr;
+ struct tcphdr *th;
+
+ /* if ring doesn't have a interrupt vector, cannot perform ATR */
+ if (!q_vector)
+ return;
+
+ ring->atr_count++;
+ dptype = wx_decode_ptype(ptype);
+ if (dptype.etype) {
+ if (WX_PTYPE_TYPL4(ptype) != WX_PTYPE_TYP_TCP)
+ return;
+ hdr.raw = (void *)skb_inner_network_header(first->skb);
+ th = inner_tcp_hdr(first->skb);
+ } else {
+ if (WX_PTYPE_PKT(ptype) != WX_PTYPE_PKT_IP ||
+ WX_PTYPE_TYPL4(ptype) != WX_PTYPE_TYP_TCP)
+ return;
+ hdr.raw = (void *)skb_network_header(first->skb);
+ th = tcp_hdr(first->skb);
+ }
+
+ /* skip this packet since it is invalid or the socket is closing */
+ if (!th || th->fin)
+ return;
+
+ /* sample on all syn packets or once every atr sample count */
+ if (!th->syn && ring->atr_count < ring->atr_sample_rate)
+ return;
+
+ /* reset sample count */
+ ring->atr_count = 0;
+
+ /* src and dst are inverted, think how the receiver sees them
+ *
+ * The input is broken into two sections, a non-compressed section
+ * containing vm_pool, vlan_id, and flow_type. The rest of the data
+ * is XORed together and stored in the compressed dword.
+ */
+ input.formatted.vlan_id = htons((u16)ptype);
+
+ /* since src port and flex bytes occupy the same word XOR them together
+ * and write the value to source port portion of compressed dword
+ */
+ if (first->tx_flags & WX_TX_FLAGS_SW_VLAN)
+ common.port.src ^= th->dest ^ first->skb->protocol;
+ else if (first->tx_flags & WX_TX_FLAGS_HW_VLAN)
+ common.port.src ^= th->dest ^ first->skb->vlan_proto;
+ else
+ common.port.src ^= th->dest ^ first->protocol;
+ common.port.dst ^= th->source;
+
+ if (WX_PTYPE_PKT_IPV6 & WX_PTYPE_PKT(ptype)) {
+ input.formatted.flow_type = TXGBE_ATR_FLOW_TYPE_TCPV6;
+ common.ip ^= hdr.ipv6->saddr.s6_addr32[0] ^
+ hdr.ipv6->saddr.s6_addr32[1] ^
+ hdr.ipv6->saddr.s6_addr32[2] ^
+ hdr.ipv6->saddr.s6_addr32[3] ^
+ hdr.ipv6->daddr.s6_addr32[0] ^
+ hdr.ipv6->daddr.s6_addr32[1] ^
+ hdr.ipv6->daddr.s6_addr32[2] ^
+ hdr.ipv6->daddr.s6_addr32[3];
+ } else {
+ input.formatted.flow_type = TXGBE_ATR_FLOW_TYPE_TCPV4;
+ common.ip ^= hdr.ipv4->saddr ^ hdr.ipv4->daddr;
+ }
+
+ /* This assumes the Rx queue and Tx queue are bound to the same CPU */
+ txgbe_fdir_add_signature_filter(q_vector->wx, input, common,
+ ring->queue_index);
+}
+
+/**
+ * txgbe_fdir_enable - Initialize Flow Director control registers
+ * @wx: pointer to hardware structure
+ * @fdirctrl: value to write to flow director control register
+ **/
+static void txgbe_fdir_enable(struct wx *wx, u32 fdirctrl)
+{
+ u32 val;
+ int ret;
+
+ /* Prime the keys for hashing */
+ wr32(wx, TXGBE_RDB_FDIR_HKEY, TXGBE_ATR_BUCKET_HASH_KEY);
+ wr32(wx, TXGBE_RDB_FDIR_SKEY, TXGBE_ATR_SIGNATURE_HASH_KEY);
+
+ wr32(wx, TXGBE_RDB_FDIR_CTL, fdirctrl);
+ WX_WRITE_FLUSH(wx);
+ ret = read_poll_timeout(rd32, val, val & TXGBE_RDB_FDIR_CTL_INIT_DONE,
+ 1000, 10000, false, wx, TXGBE_RDB_FDIR_CTL);
+
+ if (ret < 0)
+ wx_dbg(wx, "Flow Director poll time exceeded!\n");
+}
+
+/**
+ * txgbe_init_fdir_signature -Initialize Flow Director sig filters
+ * @wx: pointer to hardware structure
+ **/
+static void txgbe_init_fdir_signature(struct wx *wx)
+{
+ u32 fdirctrl = TXGBE_FDIR_PBALLOC_64K;
+ u32 flex = 0;
+
+ flex = rd32(wx, TXGBE_RDB_FDIR_FLEX_CFG(0));
+ flex &= ~TXGBE_RDB_FDIR_FLEX_CFG_FIELD0;
+
+ flex |= (TXGBE_RDB_FDIR_FLEX_CFG_BASE_MAC |
+ TXGBE_RDB_FDIR_FLEX_CFG_OFST(0x6));
+ wr32(wx, TXGBE_RDB_FDIR_FLEX_CFG(0), flex);
+
+ /* Continue setup of fdirctrl register bits:
+ * Move the flexible bytes to use the ethertype - shift 6 words
+ * Set the maximum length per hash bucket to 0xA filters
+ * Send interrupt when 64 filters are left
+ */
+ fdirctrl |= TXGBE_RDB_FDIR_CTL_HASH_BITS(0xF) |
+ TXGBE_RDB_FDIR_CTL_MAX_LENGTH(0xA) |
+ TXGBE_RDB_FDIR_CTL_FULL_THRESH(4);
+
+ /* write hashes and fdirctrl register, poll for completion */
+ txgbe_fdir_enable(wx, fdirctrl);
+}
+
+void txgbe_configure_fdir(struct wx *wx)
+{
+ wx_disable_sec_rx_path(wx);
+
+ if (test_bit(WX_FLAG_FDIR_HASH, wx->flags))
+ txgbe_init_fdir_signature(wx);
+
+ wx_enable_sec_rx_path(wx);
+}
diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.h b/drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.h
new file mode 100644
index 000000000000..ed245b66dc2a
--- /dev/null
+++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2015 - 2024 Beijing WangXun Technology Co., Ltd. */
+
+#ifndef _TXGBE_FDIR_H_
+#define _TXGBE_FDIR_H_
+
+void txgbe_atr(struct wx_ring *ring, struct wx_tx_buffer *first, u8 ptype);
+void txgbe_configure_fdir(struct wx *wx);
+
+#endif /* _TXGBE_FDIR_H_ */
diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
index 8c7a74981b90..ce49fb725541 100644
--- a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
+++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
@@ -18,6 +18,7 @@
#include "txgbe_hw.h"
#include "txgbe_phy.h"
#include "txgbe_irq.h"
+#include "txgbe_fdir.h"
#include "txgbe_ethtool.h"
char txgbe_driver_name[] = "txgbe";
@@ -257,6 +258,14 @@ static int txgbe_sw_init(struct wx *wx)
num_online_cpus());
wx->rss_enabled = true;
+ wx->ring_feature[RING_F_FDIR].limit = min_t(int, TXGBE_MAX_FDIR_INDICES,
+ num_online_cpus());
+ set_bit(WX_FLAG_FDIR_CAPABLE, wx->flags);
+ set_bit(WX_FLAG_FDIR_HASH, wx->flags);
+ wx->atr_sample_rate = TXGBE_DEFAULT_ATR_SAMPLE_RATE;
+ wx->atr = txgbe_atr;
+ wx->configure_fdir = txgbe_configure_fdir;
+
/* enable itr by default in dynamic mode */
wx->rx_itr_setting = 1;
wx->tx_itr_setting = 1;
diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_type.h b/drivers/net/ethernet/wangxun/txgbe/txgbe_type.h
index f434a7865cb7..5b8c55df35fe 100644
--- a/drivers/net/ethernet/wangxun/txgbe/txgbe_type.h
+++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_type.h
@@ -89,6 +89,39 @@
#define TXGBE_XPCS_IDA_ADDR 0x13000
#define TXGBE_XPCS_IDA_DATA 0x13004
+/********************************* Flow Director *****************************/
+#define TXGBE_RDB_FDIR_CTL 0x19500
+#define TXGBE_RDB_FDIR_CTL_INIT_DONE BIT(3)
+#define TXGBE_RDB_FDIR_CTL_PERFECT_MATCH BIT(4)
+#define TXGBE_RDB_FDIR_CTL_DROP_Q(v) FIELD_PREP(GENMASK(14, 8), v)
+#define TXGBE_RDB_FDIR_CTL_HASH_BITS(v) FIELD_PREP(GENMASK(23, 20), v)
+#define TXGBE_RDB_FDIR_CTL_MAX_LENGTH(v) FIELD_PREP(GENMASK(27, 24), v)
+#define TXGBE_RDB_FDIR_CTL_FULL_THRESH(v) FIELD_PREP(GENMASK(31, 28), v)
+#define TXGBE_RDB_FDIR_HASH 0x19528
+#define TXGBE_RDB_FDIR_HASH_SIG_SW_INDEX(v) FIELD_PREP(GENMASK(31, 16), v)
+#define TXGBE_RDB_FDIR_HASH_BUCKET_VALID BIT(15)
+#define TXGBE_RDB_FDIR_CMD 0x1952C
+#define TXGBE_RDB_FDIR_CMD_CMD_MASK GENMASK(1, 0)
+#define TXGBE_RDB_FDIR_CMD_CMD(v) FIELD_PREP(GENMASK(1, 0), v)
+#define TXGBE_RDB_FDIR_CMD_CMD_ADD_FLOW TXGBE_RDB_FDIR_CMD_CMD(1)
+#define TXGBE_RDB_FDIR_CMD_CMD_REMOVE_FLOW TXGBE_RDB_FDIR_CMD_CMD(2)
+#define TXGBE_RDB_FDIR_CMD_CMD_QUERY_REM_FILT TXGBE_RDB_FDIR_CMD_CMD(3)
+#define TXGBE_RDB_FDIR_CMD_FILTER_VALID BIT(2)
+#define TXGBE_RDB_FDIR_CMD_FILTER_UPDATE BIT(3)
+#define TXGBE_RDB_FDIR_CMD_FLOW_TYPE(v) FIELD_PREP(GENMASK(6, 5), v)
+#define TXGBE_RDB_FDIR_CMD_DROP BIT(9)
+#define TXGBE_RDB_FDIR_CMD_LAST BIT(11)
+#define TXGBE_RDB_FDIR_CMD_QUEUE_EN BIT(15)
+#define TXGBE_RDB_FDIR_CMD_RX_QUEUE(v) FIELD_PREP(GENMASK(22, 16), v)
+#define TXGBE_RDB_FDIR_CMD_VT_POOL(v) FIELD_PREP(GENMASK(29, 24), v)
+#define TXGBE_RDB_FDIR_HKEY 0x19568
+#define TXGBE_RDB_FDIR_SKEY 0x1956C
+#define TXGBE_RDB_FDIR_FLEX_CFG(_i) (0x19580 + ((_i) * 4))
+#define TXGBE_RDB_FDIR_FLEX_CFG_FIELD0 GENMASK(7, 0)
+#define TXGBE_RDB_FDIR_FLEX_CFG_BASE_MAC FIELD_PREP(GENMASK(1, 0), 0)
+#define TXGBE_RDB_FDIR_FLEX_CFG_MSK BIT(2)
+#define TXGBE_RDB_FDIR_FLEX_CFG_OFST(v) FIELD_PREP(GENMASK(7, 3), v)
+
/* Checksum and EEPROM pointers */
#define TXGBE_EEPROM_LAST_WORD 0x800
#define TXGBE_EEPROM_CHECKSUM 0x2F
@@ -112,6 +145,91 @@
#define TXGBE_SP_RX_PB_SIZE 512
#define TXGBE_SP_TDB_PB_SZ (160 * 1024) /* 160KB Packet Buffer */
+#define TXGBE_DEFAULT_ATR_SAMPLE_RATE 20
+
+/* Software ATR hash keys */
+#define TXGBE_ATR_BUCKET_HASH_KEY 0x3DAD14E2
+#define TXGBE_ATR_SIGNATURE_HASH_KEY 0x174D3614
+
+/* Software ATR input stream values and masks */
+#define TXGBE_ATR_HASH_MASK 0x7fff
+#define TXGBE_ATR_L4TYPE_MASK 0x3
+#define TXGBE_ATR_L4TYPE_UDP 0x1
+#define TXGBE_ATR_L4TYPE_TCP 0x2
+#define TXGBE_ATR_L4TYPE_SCTP 0x3
+#define TXGBE_ATR_L4TYPE_IPV6_MASK 0x4
+#define TXGBE_ATR_L4TYPE_TUNNEL_MASK 0x10
+
+enum txgbe_atr_flow_type {
+ TXGBE_ATR_FLOW_TYPE_IPV4 = 0x0,
+ TXGBE_ATR_FLOW_TYPE_UDPV4 = 0x1,
+ TXGBE_ATR_FLOW_TYPE_TCPV4 = 0x2,
+ TXGBE_ATR_FLOW_TYPE_SCTPV4 = 0x3,
+ TXGBE_ATR_FLOW_TYPE_IPV6 = 0x4,
+ TXGBE_ATR_FLOW_TYPE_UDPV6 = 0x5,
+ TXGBE_ATR_FLOW_TYPE_TCPV6 = 0x6,
+ TXGBE_ATR_FLOW_TYPE_SCTPV6 = 0x7,
+ TXGBE_ATR_FLOW_TYPE_TUNNELED_IPV4 = 0x10,
+ TXGBE_ATR_FLOW_TYPE_TUNNELED_UDPV4 = 0x11,
+ TXGBE_ATR_FLOW_TYPE_TUNNELED_TCPV4 = 0x12,
+ TXGBE_ATR_FLOW_TYPE_TUNNELED_SCTPV4 = 0x13,
+ TXGBE_ATR_FLOW_TYPE_TUNNELED_IPV6 = 0x14,
+ TXGBE_ATR_FLOW_TYPE_TUNNELED_UDPV6 = 0x15,
+ TXGBE_ATR_FLOW_TYPE_TUNNELED_TCPV6 = 0x16,
+ TXGBE_ATR_FLOW_TYPE_TUNNELED_SCTPV6 = 0x17,
+};
+
+/* Flow Director ATR input struct. */
+union txgbe_atr_input {
+ /* Byte layout in order, all values with MSB first:
+ *
+ * vm_pool - 1 byte
+ * flow_type - 1 byte
+ * vlan_id - 2 bytes
+ * dst_ip - 16 bytes
+ * src_ip - 16 bytes
+ * src_port - 2 bytes
+ * dst_port - 2 bytes
+ * flex_bytes - 2 bytes
+ * bkt_hash - 2 bytes
+ */
+ struct {
+ u8 vm_pool;
+ u8 flow_type;
+ __be16 vlan_id;
+ __be32 dst_ip[4];
+ __be32 src_ip[4];
+ __be16 src_port;
+ __be16 dst_port;
+ __be16 flex_bytes;
+ __be16 bkt_hash;
+ } formatted;
+ __be32 dword_stream[11];
+};
+
+/* Flow Director compressed ATR hash input struct */
+union txgbe_atr_hash_dword {
+ struct {
+ u8 vm_pool;
+ u8 flow_type;
+ __be16 vlan_id;
+ } formatted;
+ __be32 ip;
+ struct {
+ __be16 src;
+ __be16 dst;
+ } port;
+ __be16 flex_bytes;
+ __be32 dword;
+};
+
+enum txgbe_fdir_pballoc_type {
+ TXGBE_FDIR_PBALLOC_NONE = 0,
+ TXGBE_FDIR_PBALLOC_64K = 1,
+ TXGBE_FDIR_PBALLOC_128K = 2,
+ TXGBE_FDIR_PBALLOC_256K = 3,
+};
+
/* TX/RX descriptor defines */
#define TXGBE_DEFAULT_TXD 512
#define TXGBE_DEFAULT_TX_WORK 256
@@ -196,6 +314,9 @@ struct txgbe {
struct gpio_chip *gpio;
unsigned int gpio_irq;
unsigned int link_irq;
+
+ /* flow director */
+ union txgbe_atr_input fdir_mask;
};
#endif /* _TXGBE_TYPE_H_ */
--
2.27.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH net-next v2 2/3] net: txgbe: support Flow Director perfect filters
2024-06-05 2:08 [PATCH net-next v2 0/3] add flow director for txgbe Jiawen Wu
2024-06-05 2:08 ` [PATCH net-next v2 1/3] net: txgbe: add FDIR ATR support Jiawen Wu
@ 2024-06-05 2:08 ` Jiawen Wu
2024-06-06 8:53 ` Hariprasad Kelam
2024-06-06 20:49 ` Simon Horman
2024-06-05 2:08 ` [PATCH net-next v2 3/3] net: txgbe: add FDIR info to ethtool ops Jiawen Wu
2024-06-06 3:04 ` [PATCH net-next v2 0/3] add flow director for txgbe Jakub Kicinski
3 siblings, 2 replies; 12+ messages in thread
From: Jiawen Wu @ 2024-06-05 2:08 UTC (permalink / raw)
To: davem, edumazet, kuba, pabeni, linux, horms, andrew, netdev
Cc: mengyuanlou, Jiawen Wu
Support the addition and deletion of Flow Director filters.
Supported fields: src-ip, dst-ip, src-port, dst-port
Supported flow-types: tcp4, udp4, sctp4, ipv4
Signed-off-by: Jiawen Wu <jiawenwu@trustnetic.com>
---
drivers/net/ethernet/wangxun/libwx/wx_lib.c | 31 ++
.../ethernet/wangxun/txgbe/txgbe_ethtool.c | 417 ++++++++++++++++++
.../net/ethernet/wangxun/txgbe/txgbe_fdir.c | 334 +++++++++++++-
.../net/ethernet/wangxun/txgbe/txgbe_fdir.h | 8 +
.../net/ethernet/wangxun/txgbe/txgbe_main.c | 9 +
.../net/ethernet/wangxun/txgbe/txgbe_type.h | 26 ++
6 files changed, 824 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_lib.c b/drivers/net/ethernet/wangxun/libwx/wx_lib.c
index 8774206ca496..59317a8a3320 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_lib.c
+++ b/drivers/net/ethernet/wangxun/libwx/wx_lib.c
@@ -2705,6 +2705,7 @@ int wx_set_features(struct net_device *netdev, netdev_features_t features)
{
netdev_features_t changed = netdev->features ^ features;
struct wx *wx = netdev_priv(netdev);
+ bool need_reset = false;
if (features & NETIF_F_RXHASH) {
wr32m(wx, WX_RDB_RA_CTL, WX_RDB_RA_CTL_RSS_EN,
@@ -2722,6 +2723,36 @@ int wx_set_features(struct net_device *netdev, netdev_features_t features)
else if (changed & (NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_HW_VLAN_CTAG_FILTER))
wx_set_rx_mode(netdev);
+ if (!(test_bit(WX_FLAG_FDIR_CAPABLE, wx->flags)))
+ return 0;
+
+ /* Check if Flow Director n-tuple support was enabled or disabled. If
+ * the state changed, we need to reset.
+ */
+ switch (features & NETIF_F_NTUPLE) {
+ case NETIF_F_NTUPLE:
+ /* turn off ATR, enable perfect filters and reset */
+ if (!(test_and_set_bit(WX_FLAG_FDIR_PERFECT, wx->flags)))
+ need_reset = true;
+
+ clear_bit(WX_FLAG_FDIR_HASH, wx->flags);
+ break;
+ default:
+ /* turn off perfect filters, enable ATR and reset */
+ if (test_and_clear_bit(WX_FLAG_FDIR_PERFECT, wx->flags))
+ need_reset = true;
+
+ /* We cannot enable ATR if RSS is disabled */
+ if (wx->ring_feature[RING_F_RSS].limit <= 1)
+ break;
+
+ set_bit(WX_FLAG_FDIR_HASH, wx->flags);
+ break;
+ }
+
+ if (need_reset)
+ wx->do_reset(netdev);
+
return 0;
}
EXPORT_SYMBOL(wx_set_features);
diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_ethtool.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_ethtool.c
index 31fde3fa7c6b..4aac64820eb3 100644
--- a/drivers/net/ethernet/wangxun/txgbe/txgbe_ethtool.c
+++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_ethtool.c
@@ -9,6 +9,7 @@
#include "../libwx/wx_type.h"
#include "../libwx/wx_lib.h"
#include "txgbe_type.h"
+#include "txgbe_fdir.h"
#include "txgbe_ethtool.h"
static int txgbe_set_ringparam(struct net_device *netdev,
@@ -79,6 +80,420 @@ static int txgbe_set_channels(struct net_device *dev,
return txgbe_setup_tc(dev, netdev_get_num_tc(dev));
}
+static int txgbe_get_ethtool_fdir_entry(struct txgbe *txgbe,
+ struct ethtool_rxnfc *cmd)
+{
+ struct ethtool_rx_flow_spec *fsp = (struct ethtool_rx_flow_spec *)&cmd->fs;
+ union txgbe_atr_input *mask = &txgbe->fdir_mask;
+ struct txgbe_fdir_filter *rule = NULL;
+ struct hlist_node *node;
+
+ /* report total rule count */
+ cmd->data = (1024 << TXGBE_FDIR_PBALLOC_64K) - 2;
+
+ hlist_for_each_entry_safe(rule, node, &txgbe->fdir_filter_list, fdir_node) {
+ if (fsp->location <= rule->sw_idx)
+ break;
+ }
+
+ if (!rule || fsp->location != rule->sw_idx)
+ return -EINVAL;
+
+ /* set flow type field */
+ switch (rule->filter.formatted.flow_type) {
+ case TXGBE_ATR_FLOW_TYPE_TCPV4:
+ fsp->flow_type = TCP_V4_FLOW;
+ break;
+ case TXGBE_ATR_FLOW_TYPE_UDPV4:
+ fsp->flow_type = UDP_V4_FLOW;
+ break;
+ case TXGBE_ATR_FLOW_TYPE_SCTPV4:
+ fsp->flow_type = SCTP_V4_FLOW;
+ break;
+ case TXGBE_ATR_FLOW_TYPE_IPV4:
+ fsp->flow_type = IP_USER_FLOW;
+ fsp->h_u.usr_ip4_spec.ip_ver = ETH_RX_NFC_IP4;
+ fsp->h_u.usr_ip4_spec.proto = 0;
+ fsp->m_u.usr_ip4_spec.proto = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ fsp->h_u.tcp_ip4_spec.psrc = rule->filter.formatted.src_port;
+ fsp->m_u.tcp_ip4_spec.psrc = mask->formatted.src_port;
+ fsp->h_u.tcp_ip4_spec.pdst = rule->filter.formatted.dst_port;
+ fsp->m_u.tcp_ip4_spec.pdst = mask->formatted.dst_port;
+ fsp->h_u.tcp_ip4_spec.ip4src = rule->filter.formatted.src_ip[0];
+ fsp->m_u.tcp_ip4_spec.ip4src = mask->formatted.src_ip[0];
+ fsp->h_u.tcp_ip4_spec.ip4dst = rule->filter.formatted.dst_ip[0];
+ fsp->m_u.tcp_ip4_spec.ip4dst = mask->formatted.dst_ip[0];
+ fsp->h_ext.vlan_etype = rule->filter.formatted.flex_bytes;
+ fsp->m_ext.vlan_etype = mask->formatted.flex_bytes;
+ fsp->h_ext.data[1] = htonl(rule->filter.formatted.vm_pool);
+ fsp->m_ext.data[1] = htonl(mask->formatted.vm_pool);
+ fsp->flow_type |= FLOW_EXT;
+
+ /* record action */
+ if (rule->action == TXGBE_RDB_FDIR_DROP_QUEUE)
+ fsp->ring_cookie = RX_CLS_FLOW_DISC;
+ else
+ fsp->ring_cookie = rule->action;
+
+ return 0;
+}
+
+static int txgbe_get_ethtool_fdir_all(struct txgbe *txgbe,
+ struct ethtool_rxnfc *cmd,
+ u32 *rule_locs)
+{
+ struct txgbe_fdir_filter *rule;
+ struct hlist_node *node;
+ int cnt = 0;
+
+ /* report total rule count */
+ cmd->data = (1024 << TXGBE_FDIR_PBALLOC_64K) - 2;
+
+ hlist_for_each_entry_safe(rule, node, &txgbe->fdir_filter_list, fdir_node) {
+ if (cnt == cmd->rule_cnt)
+ return -EMSGSIZE;
+ rule_locs[cnt] = rule->sw_idx;
+ cnt++;
+ }
+
+ cmd->rule_cnt = cnt;
+
+ return 0;
+}
+
+static int txgbe_get_rxnfc(struct net_device *dev, struct ethtool_rxnfc *cmd,
+ u32 *rule_locs)
+{
+ struct wx *wx = netdev_priv(dev);
+ struct txgbe *txgbe = wx->priv;
+ int ret = -EOPNOTSUPP;
+
+ switch (cmd->cmd) {
+ case ETHTOOL_GRXRINGS:
+ cmd->data = wx->num_rx_queues;
+ ret = 0;
+ break;
+ case ETHTOOL_GRXCLSRLCNT:
+ cmd->rule_cnt = txgbe->fdir_filter_count;
+ ret = 0;
+ break;
+ case ETHTOOL_GRXCLSRULE:
+ ret = txgbe_get_ethtool_fdir_entry(txgbe, cmd);
+ break;
+ case ETHTOOL_GRXCLSRLALL:
+ ret = txgbe_get_ethtool_fdir_all(txgbe, cmd, (u32 *)rule_locs);
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static int txgbe_flowspec_to_flow_type(struct ethtool_rx_flow_spec *fsp,
+ u8 *flow_type)
+{
+ switch (fsp->flow_type & ~FLOW_EXT) {
+ case TCP_V4_FLOW:
+ *flow_type = TXGBE_ATR_FLOW_TYPE_TCPV4;
+ break;
+ case UDP_V4_FLOW:
+ *flow_type = TXGBE_ATR_FLOW_TYPE_UDPV4;
+ break;
+ case SCTP_V4_FLOW:
+ *flow_type = TXGBE_ATR_FLOW_TYPE_SCTPV4;
+ break;
+ case IP_USER_FLOW:
+ switch (fsp->h_u.usr_ip4_spec.proto) {
+ case IPPROTO_TCP:
+ *flow_type = TXGBE_ATR_FLOW_TYPE_TCPV4;
+ break;
+ case IPPROTO_UDP:
+ *flow_type = TXGBE_ATR_FLOW_TYPE_UDPV4;
+ break;
+ case IPPROTO_SCTP:
+ *flow_type = TXGBE_ATR_FLOW_TYPE_SCTPV4;
+ break;
+ case 0:
+ if (!fsp->m_u.usr_ip4_spec.proto) {
+ *flow_type = TXGBE_ATR_FLOW_TYPE_IPV4;
+ break;
+ }
+ fallthrough;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static bool txgbe_match_ethtool_fdir_entry(struct txgbe *txgbe,
+ struct txgbe_fdir_filter *input)
+{
+ struct txgbe_fdir_filter *rule = NULL;
+ struct hlist_node *node2;
+
+ hlist_for_each_entry_safe(rule, node2, &txgbe->fdir_filter_list, fdir_node) {
+ if (rule->filter.formatted.bkt_hash ==
+ input->filter.formatted.bkt_hash &&
+ rule->action == input->action) {
+ wx_dbg(txgbe->wx, "FDIR entry already exist\n");
+ return true;
+ }
+ }
+ return false;
+}
+
+static int txgbe_update_ethtool_fdir_entry(struct txgbe *txgbe,
+ struct txgbe_fdir_filter *input,
+ u16 sw_idx)
+{
+ struct hlist_node *node = NULL, *parent = NULL;
+ struct txgbe_fdir_filter *rule;
+ struct wx *wx = txgbe->wx;
+ bool deleted = false;
+ int err;
+
+ hlist_for_each_entry_safe(rule, node, &txgbe->fdir_filter_list, fdir_node) {
+ /* hash found, or no matching entry */
+ if (rule->sw_idx >= sw_idx)
+ break;
+ parent = node;
+ }
+
+ /* if there is an old rule occupying our place remove it */
+ if (rule && rule->sw_idx == sw_idx) {
+ /* hardware filters are only configured when interface is up,
+ * and we should not issue filter commands while the interface
+ * is down
+ */
+ if (netif_running(wx->netdev) &&
+ (!input || rule->filter.formatted.bkt_hash !=
+ input->filter.formatted.bkt_hash)) {
+ err = txgbe_fdir_erase_perfect_filter(wx, &rule->filter, sw_idx);
+ if (err)
+ return -EINVAL;
+ }
+
+ hlist_del(&rule->fdir_node);
+ kfree(rule);
+ txgbe->fdir_filter_count--;
+ deleted = true;
+ }
+
+ /* If we weren't given an input, then this was a request to delete a
+ * filter. We should return -EINVAL if the filter wasn't found, but
+ * return 0 if the rule was successfully deleted.
+ */
+ if (!input)
+ return deleted ? 0 : -EINVAL;
+
+ /* initialize node and set software index */
+ INIT_HLIST_NODE(&input->fdir_node);
+
+ /* add filter to the list */
+ if (parent)
+ hlist_add_behind(&input->fdir_node, parent);
+ else
+ hlist_add_head(&input->fdir_node,
+ &txgbe->fdir_filter_list);
+
+ /* update counts */
+ txgbe->fdir_filter_count++;
+
+ return 0;
+}
+
+static int txgbe_add_ethtool_fdir_entry(struct txgbe *txgbe,
+ struct ethtool_rxnfc *cmd)
+{
+ struct ethtool_rx_flow_spec *fsp = (struct ethtool_rx_flow_spec *)&cmd->fs;
+ struct txgbe_fdir_filter *input;
+ union txgbe_atr_input mask;
+ struct wx *wx = txgbe->wx;
+ u16 ptype = 0;
+ u8 queue;
+ int err;
+
+ if (!(test_bit(WX_FLAG_FDIR_PERFECT, wx->flags)))
+ return -EOPNOTSUPP;
+
+ /* ring_cookie is a masked into a set of queues and txgbe pools or
+ * we use drop index
+ */
+ if (fsp->ring_cookie == RX_CLS_FLOW_DISC) {
+ queue = TXGBE_RDB_FDIR_DROP_QUEUE;
+ } else {
+ u32 ring = ethtool_get_flow_spec_ring(fsp->ring_cookie);
+
+ if (ring >= wx->num_rx_queues)
+ return -EINVAL;
+
+ /* Map the ring onto the absolute queue index */
+ queue = wx->rx_ring[ring]->reg_idx;
+ }
+
+ /* Don't allow indexes to exist outside of available space */
+ if (fsp->location >= ((1024 << TXGBE_FDIR_PBALLOC_64K) - 2)) {
+ wx_err(wx, "Location out of range\n");
+ return -EINVAL;
+ }
+
+ input = kzalloc(sizeof(*input), GFP_ATOMIC);
+ if (!input)
+ return -ENOMEM;
+
+ memset(&mask, 0, sizeof(union txgbe_atr_input));
+
+ /* set SW index */
+ input->sw_idx = fsp->location;
+
+ /* record flow type */
+ if (txgbe_flowspec_to_flow_type(fsp, &input->filter.formatted.flow_type)) {
+ wx_err(wx, "Unrecognized flow type\n");
+ goto err_out;
+ }
+
+ mask.formatted.flow_type = TXGBE_ATR_L4TYPE_IPV6_MASK |
+ TXGBE_ATR_L4TYPE_MASK;
+
+ if (input->filter.formatted.flow_type == TXGBE_ATR_FLOW_TYPE_IPV4)
+ mask.formatted.flow_type &= TXGBE_ATR_L4TYPE_IPV6_MASK;
+
+ /* Copy input into formatted structures */
+ input->filter.formatted.src_ip[0] = fsp->h_u.tcp_ip4_spec.ip4src;
+ mask.formatted.src_ip[0] = fsp->m_u.tcp_ip4_spec.ip4src;
+ input->filter.formatted.dst_ip[0] = fsp->h_u.tcp_ip4_spec.ip4dst;
+ mask.formatted.dst_ip[0] = fsp->m_u.tcp_ip4_spec.ip4dst;
+ input->filter.formatted.src_port = fsp->h_u.tcp_ip4_spec.psrc;
+ mask.formatted.src_port = fsp->m_u.tcp_ip4_spec.psrc;
+ input->filter.formatted.dst_port = fsp->h_u.tcp_ip4_spec.pdst;
+ mask.formatted.dst_port = fsp->m_u.tcp_ip4_spec.pdst;
+
+ if (fsp->flow_type & FLOW_EXT) {
+ input->filter.formatted.vm_pool =
+ (unsigned char)ntohl(fsp->h_ext.data[1]);
+ mask.formatted.vm_pool =
+ (unsigned char)ntohl(fsp->m_ext.data[1]);
+ input->filter.formatted.flex_bytes =
+ fsp->h_ext.vlan_etype;
+ mask.formatted.flex_bytes = fsp->m_ext.vlan_etype;
+ }
+
+ switch (input->filter.formatted.flow_type) {
+ case TXGBE_ATR_FLOW_TYPE_TCPV4:
+ ptype = WX_PTYPE_L2_IPV4_TCP;
+ break;
+ case TXGBE_ATR_FLOW_TYPE_UDPV4:
+ ptype = WX_PTYPE_L2_IPV4_UDP;
+ break;
+ case TXGBE_ATR_FLOW_TYPE_SCTPV4:
+ ptype = WX_PTYPE_L2_IPV4_SCTP;
+ break;
+ case TXGBE_ATR_FLOW_TYPE_IPV4:
+ ptype = WX_PTYPE_L2_IPV4;
+ break;
+ default:
+ break;
+ }
+
+ input->filter.formatted.vlan_id = htons(ptype);
+ if (mask.formatted.flow_type & TXGBE_ATR_L4TYPE_MASK)
+ mask.formatted.vlan_id = htons(0xFFFF);
+ else
+ mask.formatted.vlan_id = htons(0xFFF8);
+
+ /* determine if we need to drop or route the packet */
+ if (fsp->ring_cookie == RX_CLS_FLOW_DISC)
+ input->action = TXGBE_RDB_FDIR_DROP_QUEUE;
+ else
+ input->action = fsp->ring_cookie;
+
+ spin_lock(&txgbe->fdir_perfect_lock);
+
+ if (hlist_empty(&txgbe->fdir_filter_list)) {
+ /* save mask and program input mask into HW */
+ memcpy(&txgbe->fdir_mask, &mask, sizeof(mask));
+ err = txgbe_fdir_set_input_mask(wx, &mask);
+ if (err)
+ goto err_unlock;
+ } else if (memcmp(&txgbe->fdir_mask, &mask, sizeof(mask))) {
+ wx_err(wx, "Hardware only supports one mask per port. To change the mask you must first delete all the rules.\n");
+ goto err_unlock;
+ }
+
+ /* apply mask and compute/store hash */
+ txgbe_atr_compute_perfect_hash(&input->filter, &mask);
+
+ /* check if new entry does not exist on filter list */
+ if (txgbe_match_ethtool_fdir_entry(txgbe, input))
+ goto err_unlock;
+
+ /* only program filters to hardware if the net device is running, as
+ * we store the filters in the Rx buffer which is not allocated when
+ * the device is down
+ */
+ if (netif_running(wx->netdev)) {
+ err = txgbe_fdir_write_perfect_filter(wx, &input->filter,
+ input->sw_idx, queue);
+ if (err)
+ goto err_unlock;
+ }
+
+ txgbe_update_ethtool_fdir_entry(txgbe, input, input->sw_idx);
+
+ spin_unlock(&txgbe->fdir_perfect_lock);
+
+ return err;
+err_unlock:
+ spin_unlock(&txgbe->fdir_perfect_lock);
+err_out:
+ kfree(input);
+ return -EINVAL;
+}
+
+static int txgbe_del_ethtool_fdir_entry(struct txgbe *txgbe,
+ struct ethtool_rxnfc *cmd)
+{
+ struct ethtool_rx_flow_spec *fsp = (struct ethtool_rx_flow_spec *)&cmd->fs;
+ int err = 0;
+
+ spin_lock(&txgbe->fdir_perfect_lock);
+ err = txgbe_update_ethtool_fdir_entry(txgbe, NULL, fsp->location);
+ spin_unlock(&txgbe->fdir_perfect_lock);
+
+ return err;
+}
+
+static int txgbe_set_rxnfc(struct net_device *dev, struct ethtool_rxnfc *cmd)
+{
+ struct wx *wx = netdev_priv(dev);
+ struct txgbe *txgbe = wx->priv;
+ int ret = -EOPNOTSUPP;
+
+ switch (cmd->cmd) {
+ case ETHTOOL_SRXCLSRLINS:
+ ret = txgbe_add_ethtool_fdir_entry(txgbe, cmd);
+ break;
+ case ETHTOOL_SRXCLSRLDEL:
+ ret = txgbe_del_ethtool_fdir_entry(txgbe, cmd);
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
static const struct ethtool_ops txgbe_ethtool_ops = {
.supported_coalesce_params = ETHTOOL_COALESCE_USECS |
ETHTOOL_COALESCE_TX_MAX_FRAMES_IRQ,
@@ -100,6 +515,8 @@ static const struct ethtool_ops txgbe_ethtool_ops = {
.set_coalesce = wx_set_coalesce,
.get_channels = wx_get_channels,
.set_channels = txgbe_set_channels,
+ .get_rxnfc = txgbe_get_rxnfc,
+ .set_rxnfc = txgbe_set_rxnfc,
.get_msglevel = wx_get_msglevel,
.set_msglevel = wx_set_msglevel,
};
diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.c
index b10676c00cea..ac6b690b6da6 100644
--- a/drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.c
+++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.c
@@ -90,6 +90,71 @@ static void txgbe_atr_compute_sig_hash(union txgbe_atr_hash_dword input,
*hash = sig_hash ^ bucket_hash;
}
+#define TXGBE_COMPUTE_BKT_HASH_ITERATION(_n) \
+do { \
+ u32 n = (_n); \
+ if (TXGBE_ATR_BUCKET_HASH_KEY & (0x01 << n)) \
+ bucket_hash ^= lo_hash_dword >> n; \
+ if (TXGBE_ATR_BUCKET_HASH_KEY & (0x01 << (n + 16))) \
+ bucket_hash ^= hi_hash_dword >> n; \
+} while (0)
+
+/**
+ * txgbe_atr_compute_perfect_hash - Compute the perfect filter hash
+ * @input: input bitstream to compute the hash on
+ * @input_mask: mask for the input bitstream
+ *
+ * This function serves two main purposes. First it applies the input_mask
+ * to the atr_input resulting in a cleaned up atr_input data stream.
+ * Secondly it computes the hash and stores it in the bkt_hash field at
+ * the end of the input byte stream. This way it will be available for
+ * future use without needing to recompute the hash.
+ **/
+void txgbe_atr_compute_perfect_hash(union txgbe_atr_input *input,
+ union txgbe_atr_input *input_mask)
+{
+ u32 hi_hash_dword, lo_hash_dword, flow_vm_vlan;
+ u32 bucket_hash = 0;
+ __be32 hi_dword = 0;
+ u32 i = 0;
+
+ /* Apply masks to input data */
+ for (i = 0; i < 11; i++)
+ input->dword_stream[i] &= input_mask->dword_stream[i];
+
+ /* record the flow_vm_vlan bits as they are a key part to the hash */
+ flow_vm_vlan = ntohl(input->dword_stream[0]);
+
+ /* generate common hash dword */
+ for (i = 1; i <= 10; i++)
+ hi_dword ^= input->dword_stream[i];
+ hi_hash_dword = ntohl(hi_dword);
+
+ /* low dword is word swapped version of common */
+ lo_hash_dword = (hi_hash_dword >> 16) | (hi_hash_dword << 16);
+
+ /* apply flow ID/VM pool/VLAN ID bits to hash words */
+ hi_hash_dword ^= flow_vm_vlan ^ (flow_vm_vlan >> 16);
+
+ /* Process bits 0 and 16 */
+ TXGBE_COMPUTE_BKT_HASH_ITERATION(0);
+
+ /* apply flow ID/VM pool/VLAN ID bits to lo hash dword, we had to
+ * delay this because bit 0 of the stream should not be processed
+ * so we do not add the VLAN until after bit 0 was processed
+ */
+ lo_hash_dword ^= flow_vm_vlan ^ (flow_vm_vlan << 16);
+
+ /* Process remaining 30 bit of the key */
+ for (i = 1; i <= 15; i++)
+ TXGBE_COMPUTE_BKT_HASH_ITERATION(i);
+
+ /* Limit hash to 13 bits since max bucket count is 8K.
+ * Store result at the end of the input stream.
+ */
+ input->formatted.bkt_hash = (__force __be16)(bucket_hash & 0x1FFF);
+}
+
static int txgbe_fdir_check_cmd_complete(struct wx *wx)
{
u32 val;
@@ -236,6 +301,181 @@ void txgbe_atr(struct wx_ring *ring, struct wx_tx_buffer *first, u8 ptype)
ring->queue_index);
}
+int txgbe_fdir_set_input_mask(struct wx *wx, union txgbe_atr_input *input_mask)
+{
+ u32 fdirm = 0, fdirtcpm = 0, flex = 0;
+
+ /* Program the relevant mask registers. If src/dst_port or src/dst_addr
+ * are zero, then assume a full mask for that field. Also assume that
+ * a VLAN of 0 is unspecified, so mask that out as well. L4type
+ * cannot be masked out in this implementation.
+ *
+ * This also assumes IPv4 only. IPv6 masking isn't supported at this
+ * point in time.
+ */
+
+ /* verify bucket hash is cleared on hash generation */
+ if (input_mask->formatted.bkt_hash)
+ wx_dbg(wx, "bucket hash should always be 0 in mask\n");
+
+ /* Program FDIRM and verify partial masks */
+ switch (input_mask->formatted.vm_pool & 0x7F) {
+ case 0x0:
+ fdirm |= TXGBE_RDB_FDIR_OTHER_MSK_POOL;
+ break;
+ case 0x7F:
+ break;
+ default:
+ wx_err(wx, "Error on vm pool mask\n");
+ return -EINVAL;
+ }
+
+ switch (input_mask->formatted.flow_type & TXGBE_ATR_L4TYPE_MASK) {
+ case 0x0:
+ fdirm |= TXGBE_RDB_FDIR_OTHER_MSK_L4P;
+ if (input_mask->formatted.dst_port ||
+ input_mask->formatted.src_port) {
+ wx_err(wx, "Error on src/dst port mask\n");
+ return -EINVAL;
+ }
+ break;
+ case TXGBE_ATR_L4TYPE_MASK:
+ break;
+ default:
+ wx_err(wx, "Error on flow type mask\n");
+ return -EINVAL;
+ }
+
+ /* Now mask VM pool and destination IPv6 - bits 5 and 2 */
+ wr32(wx, TXGBE_RDB_FDIR_OTHER_MSK, fdirm);
+
+ flex = rd32(wx, TXGBE_RDB_FDIR_FLEX_CFG(0));
+ flex &= ~TXGBE_RDB_FDIR_FLEX_CFG_FIELD0;
+ flex |= (TXGBE_RDB_FDIR_FLEX_CFG_BASE_MAC |
+ TXGBE_RDB_FDIR_FLEX_CFG_OFST(0x6));
+
+ switch ((__force u16)input_mask->formatted.flex_bytes & 0xFFFF) {
+ case 0x0000:
+ /* Mask Flex Bytes */
+ flex |= TXGBE_RDB_FDIR_FLEX_CFG_MSK;
+ break;
+ case 0xFFFF:
+ break;
+ default:
+ wx_err(wx, "Error on flexible byte mask\n");
+ return -EINVAL;
+ }
+ wr32(wx, TXGBE_RDB_FDIR_FLEX_CFG(0), flex);
+
+ /* store the TCP/UDP port masks, bit reversed from port layout */
+ fdirtcpm = ntohs(input_mask->formatted.dst_port);
+ fdirtcpm <<= TXGBE_RDB_FDIR_PORT_DESTINATION_SHIFT;
+ fdirtcpm |= ntohs(input_mask->formatted.src_port);
+
+ /* write both the same so that UDP and TCP use the same mask */
+ wr32(wx, TXGBE_RDB_FDIR_TCP_MSK, ~fdirtcpm);
+ wr32(wx, TXGBE_RDB_FDIR_UDP_MSK, ~fdirtcpm);
+ wr32(wx, TXGBE_RDB_FDIR_SCTP_MSK, ~fdirtcpm);
+
+ /* store source and destination IP masks (little-enian) */
+ wr32(wx, TXGBE_RDB_FDIR_SA4_MSK, ntohl(~input_mask->formatted.src_ip[0]));
+ wr32(wx, TXGBE_RDB_FDIR_DA4_MSK, ntohl(~input_mask->formatted.dst_ip[0]));
+
+ return 0;
+}
+
+int txgbe_fdir_write_perfect_filter(struct wx *wx, union txgbe_atr_input *input,
+ u16 soft_id, u8 queue)
+{
+ u32 fdirport, fdirvlan, fdirhash, fdircmd;
+ int err = 0;
+
+ /* currently IPv6 is not supported, must be programmed with 0 */
+ wr32(wx, TXGBE_RDB_FDIR_IP6(2), ntohl(input->formatted.src_ip[0]));
+ wr32(wx, TXGBE_RDB_FDIR_IP6(1), ntohl(input->formatted.src_ip[1]));
+ wr32(wx, TXGBE_RDB_FDIR_IP6(0), ntohl(input->formatted.src_ip[2]));
+
+ /* record the source address (little-endian) */
+ wr32(wx, TXGBE_RDB_FDIR_SA, ntohl(input->formatted.src_ip[0]));
+
+ /* record the first 32 bits of the destination address (little-endian) */
+ wr32(wx, TXGBE_RDB_FDIR_DA, ntohl(input->formatted.dst_ip[0]));
+
+ /* record source and destination port (little-endian)*/
+ fdirport = ntohs(input->formatted.dst_port);
+ fdirport <<= TXGBE_RDB_FDIR_PORT_DESTINATION_SHIFT;
+ fdirport |= ntohs(input->formatted.src_port);
+ wr32(wx, TXGBE_RDB_FDIR_PORT, fdirport);
+
+ /* record packet type and flex_bytes (little-endian) */
+ fdirvlan = ntohs(input->formatted.flex_bytes);
+ fdirvlan <<= TXGBE_RDB_FDIR_FLEX_FLEX_SHIFT;
+ fdirvlan |= ntohs(input->formatted.vlan_id);
+ wr32(wx, TXGBE_RDB_FDIR_FLEX, fdirvlan);
+
+ /* configure FDIRHASH register */
+ fdirhash = (__force u32)input->formatted.bkt_hash |
+ TXGBE_RDB_FDIR_HASH_BUCKET_VALID |
+ TXGBE_RDB_FDIR_HASH_SIG_SW_INDEX(soft_id);
+ wr32(wx, TXGBE_RDB_FDIR_HASH, fdirhash);
+
+ /* flush all previous writes to make certain registers are
+ * programmed prior to issuing the command
+ */
+ WX_WRITE_FLUSH(wx);
+
+ /* configure FDIRCMD register */
+ fdircmd = TXGBE_RDB_FDIR_CMD_CMD_ADD_FLOW |
+ TXGBE_RDB_FDIR_CMD_FILTER_UPDATE |
+ TXGBE_RDB_FDIR_CMD_LAST | TXGBE_RDB_FDIR_CMD_QUEUE_EN;
+ if (queue == TXGBE_RDB_FDIR_DROP_QUEUE)
+ fdircmd |= TXGBE_RDB_FDIR_CMD_DROP;
+ fdircmd |= TXGBE_RDB_FDIR_CMD_FLOW_TYPE(input->formatted.flow_type);
+ fdircmd |= TXGBE_RDB_FDIR_CMD_RX_QUEUE(queue);
+ fdircmd |= TXGBE_RDB_FDIR_CMD_VT_POOL(input->formatted.vm_pool);
+
+ wr32(wx, TXGBE_RDB_FDIR_CMD, fdircmd);
+ err = txgbe_fdir_check_cmd_complete(wx);
+ if (err)
+ wx_err(wx, "Flow Director command did not complete!\n");
+
+ return err;
+}
+
+int txgbe_fdir_erase_perfect_filter(struct wx *wx, union txgbe_atr_input *input,
+ u16 soft_id)
+{
+ u32 fdirhash, fdircmd;
+ int err = 0;
+
+ /* configure FDIRHASH register */
+ fdirhash = (__force u32)input->formatted.bkt_hash;
+ fdirhash |= TXGBE_RDB_FDIR_HASH_SIG_SW_INDEX(soft_id);
+ wr32(wx, TXGBE_RDB_FDIR_HASH, fdirhash);
+
+ /* flush hash to HW */
+ WX_WRITE_FLUSH(wx);
+
+ /* Query if filter is present */
+ wr32(wx, TXGBE_RDB_FDIR_CMD, TXGBE_RDB_FDIR_CMD_CMD_QUERY_REM_FILT);
+
+ err = txgbe_fdir_check_cmd_complete(wx);
+ if (err) {
+ wx_err(wx, "Flow Director command did not complete!\n");
+ return err;
+ }
+
+ fdircmd = rd32(wx, TXGBE_RDB_FDIR_CMD);
+ /* if filter exists in hardware then remove it */
+ if (fdircmd & TXGBE_RDB_FDIR_CMD_FILTER_VALID) {
+ wr32(wx, TXGBE_RDB_FDIR_HASH, fdirhash);
+ WX_WRITE_FLUSH(wx);
+ wr32(wx, TXGBE_RDB_FDIR_CMD, TXGBE_RDB_FDIR_CMD_CMD_REMOVE_FLOW);
+ }
+
+ return 0;
+}
+
/**
* txgbe_fdir_enable - Initialize Flow Director control registers
* @wx: pointer to hardware structure
@@ -288,12 +528,104 @@ static void txgbe_init_fdir_signature(struct wx *wx)
txgbe_fdir_enable(wx, fdirctrl);
}
+/**
+ * txgbe_init_fdir_perfect - Initialize Flow Director perfect filters
+ * @wx: pointer to hardware structure
+ **/
+static void txgbe_init_fdir_perfect(struct wx *wx)
+{
+ u32 fdirctrl = TXGBE_FDIR_PBALLOC_64K;
+
+ /* Continue setup of fdirctrl register bits:
+ * Turn perfect match filtering on
+ * Report hash in RSS field of Rx wb descriptor
+ * Initialize the drop queue
+ * Move the flexible bytes to use the ethertype - shift 6 words
+ * Set the maximum length per hash bucket to 0xA filters
+ * Send interrupt when 64 (0x4 * 16) filters are left
+ */
+ fdirctrl |= TXGBE_RDB_FDIR_CTL_PERFECT_MATCH |
+ TXGBE_RDB_FDIR_CTL_DROP_Q(TXGBE_RDB_FDIR_DROP_QUEUE) |
+ TXGBE_RDB_FDIR_CTL_HASH_BITS(0xF) |
+ TXGBE_RDB_FDIR_CTL_MAX_LENGTH(0xA) |
+ TXGBE_RDB_FDIR_CTL_FULL_THRESH(4);
+
+ /* write hashes and fdirctrl register, poll for completion */
+ txgbe_fdir_enable(wx, fdirctrl);
+}
+
+static void txgbe_fdir_filter_restore(struct wx *wx)
+{
+ struct txgbe_fdir_filter *filter;
+ struct txgbe *txgbe = wx->priv;
+ struct hlist_node *node;
+ u8 queue = 0;
+ int ret = 0;
+
+ spin_lock(&txgbe->fdir_perfect_lock);
+
+ if (!hlist_empty(&txgbe->fdir_filter_list))
+ ret = txgbe_fdir_set_input_mask(wx, &txgbe->fdir_mask);
+
+ if (ret)
+ goto unlock;
+
+ hlist_for_each_entry_safe(filter, node,
+ &txgbe->fdir_filter_list, fdir_node) {
+ if (filter->action == TXGBE_RDB_FDIR_DROP_QUEUE) {
+ queue = TXGBE_RDB_FDIR_DROP_QUEUE;
+ } else {
+ u32 ring = ethtool_get_flow_spec_ring(filter->action);
+
+ if (ring >= wx->num_rx_queues) {
+ wx_err(wx, "FDIR restore failed, ring:%u\n", ring);
+ continue;
+ }
+
+ /* Map the ring onto the absolute queue index */
+ queue = wx->rx_ring[ring]->reg_idx;
+ }
+
+ ret = txgbe_fdir_write_perfect_filter(wx,
+ &filter->filter,
+ filter->sw_idx,
+ queue);
+ if (ret)
+ wx_err(wx, "FDIR restore failed, index:%u\n", filter->sw_idx);
+ }
+
+unlock:
+ spin_unlock(&txgbe->fdir_perfect_lock);
+}
+
void txgbe_configure_fdir(struct wx *wx)
{
wx_disable_sec_rx_path(wx);
- if (test_bit(WX_FLAG_FDIR_HASH, wx->flags))
+ if (test_bit(WX_FLAG_FDIR_HASH, wx->flags)) {
txgbe_init_fdir_signature(wx);
+ } else if (test_bit(WX_FLAG_FDIR_PERFECT, wx->flags)) {
+ txgbe_init_fdir_perfect(wx);
+ txgbe_fdir_filter_restore(wx);
+ }
wx_enable_sec_rx_path(wx);
}
+
+void txgbe_fdir_filter_exit(struct wx *wx)
+{
+ struct txgbe_fdir_filter *filter;
+ struct txgbe *txgbe = wx->priv;
+ struct hlist_node *node;
+
+ spin_lock(&txgbe->fdir_perfect_lock);
+
+ hlist_for_each_entry_safe(filter, node,
+ &txgbe->fdir_filter_list, fdir_node) {
+ hlist_del(&filter->fdir_node);
+ kfree(filter);
+ }
+ txgbe->fdir_filter_count = 0;
+
+ spin_unlock(&txgbe->fdir_perfect_lock);
+}
diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.h b/drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.h
index ed245b66dc2a..ce89b54a44f7 100644
--- a/drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.h
+++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.h
@@ -4,7 +4,15 @@
#ifndef _TXGBE_FDIR_H_
#define _TXGBE_FDIR_H_
+void txgbe_atr_compute_perfect_hash(union txgbe_atr_input *input,
+ union txgbe_atr_input *input_mask);
void txgbe_atr(struct wx_ring *ring, struct wx_tx_buffer *first, u8 ptype);
+int txgbe_fdir_set_input_mask(struct wx *wx, union txgbe_atr_input *input_mask);
+int txgbe_fdir_write_perfect_filter(struct wx *wx, union txgbe_atr_input *input,
+ u16 soft_id, u8 queue);
+int txgbe_fdir_erase_perfect_filter(struct wx *wx, union txgbe_atr_input *input,
+ u16 soft_id);
void txgbe_configure_fdir(struct wx *wx);
+void txgbe_fdir_filter_exit(struct wx *wx);
#endif /* _TXGBE_FDIR_H_ */
diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
index ce49fb725541..41e9ebf11e41 100644
--- a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
+++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
@@ -283,6 +283,12 @@ static int txgbe_sw_init(struct wx *wx)
return 0;
}
+static void txgbe_init_fdir(struct txgbe *txgbe)
+{
+ txgbe->fdir_filter_count = 0;
+ spin_lock_init(&txgbe->fdir_perfect_lock);
+}
+
/**
* txgbe_open - Called when a network interface is made active
* @netdev: network interface device structure
@@ -361,6 +367,7 @@ static int txgbe_close(struct net_device *netdev)
txgbe_down(wx);
wx_free_irq(wx);
wx_free_resources(wx);
+ txgbe_fdir_filter_exit(wx);
wx_control_hw(wx, false);
return 0;
@@ -669,6 +676,8 @@ static int txgbe_probe(struct pci_dev *pdev,
txgbe->wx = wx;
wx->priv = txgbe;
+ txgbe_init_fdir(txgbe);
+
err = txgbe_setup_misc_irq(txgbe);
if (err)
goto err_release_hw;
diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_type.h b/drivers/net/ethernet/wangxun/txgbe/txgbe_type.h
index 5b8c55df35fe..63bd034e0f0e 100644
--- a/drivers/net/ethernet/wangxun/txgbe/txgbe_type.h
+++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_type.h
@@ -90,6 +90,7 @@
#define TXGBE_XPCS_IDA_DATA 0x13004
/********************************* Flow Director *****************************/
+#define TXGBE_RDB_FDIR_DROP_QUEUE 127
#define TXGBE_RDB_FDIR_CTL 0x19500
#define TXGBE_RDB_FDIR_CTL_INIT_DONE BIT(3)
#define TXGBE_RDB_FDIR_CTL_PERFECT_MATCH BIT(4)
@@ -97,6 +98,13 @@
#define TXGBE_RDB_FDIR_CTL_HASH_BITS(v) FIELD_PREP(GENMASK(23, 20), v)
#define TXGBE_RDB_FDIR_CTL_MAX_LENGTH(v) FIELD_PREP(GENMASK(27, 24), v)
#define TXGBE_RDB_FDIR_CTL_FULL_THRESH(v) FIELD_PREP(GENMASK(31, 28), v)
+#define TXGBE_RDB_FDIR_IP6(_i) (0x1950C + ((_i) * 4)) /* 0-2 */
+#define TXGBE_RDB_FDIR_SA 0x19518
+#define TXGBE_RDB_FDIR_DA 0x1951C
+#define TXGBE_RDB_FDIR_PORT 0x19520
+#define TXGBE_RDB_FDIR_PORT_DESTINATION_SHIFT 16
+#define TXGBE_RDB_FDIR_FLEX 0x19524
+#define TXGBE_RDB_FDIR_FLEX_FLEX_SHIFT 16
#define TXGBE_RDB_FDIR_HASH 0x19528
#define TXGBE_RDB_FDIR_HASH_SIG_SW_INDEX(v) FIELD_PREP(GENMASK(31, 16), v)
#define TXGBE_RDB_FDIR_HASH_BUCKET_VALID BIT(15)
@@ -114,8 +122,16 @@
#define TXGBE_RDB_FDIR_CMD_QUEUE_EN BIT(15)
#define TXGBE_RDB_FDIR_CMD_RX_QUEUE(v) FIELD_PREP(GENMASK(22, 16), v)
#define TXGBE_RDB_FDIR_CMD_VT_POOL(v) FIELD_PREP(GENMASK(29, 24), v)
+#define TXGBE_RDB_FDIR_DA4_MSK 0x1953C
+#define TXGBE_RDB_FDIR_SA4_MSK 0x19540
+#define TXGBE_RDB_FDIR_TCP_MSK 0x19544
+#define TXGBE_RDB_FDIR_UDP_MSK 0x19548
+#define TXGBE_RDB_FDIR_SCTP_MSK 0x19560
#define TXGBE_RDB_FDIR_HKEY 0x19568
#define TXGBE_RDB_FDIR_SKEY 0x1956C
+#define TXGBE_RDB_FDIR_OTHER_MSK 0x19570
+#define TXGBE_RDB_FDIR_OTHER_MSK_POOL BIT(2)
+#define TXGBE_RDB_FDIR_OTHER_MSK_L4P BIT(3)
#define TXGBE_RDB_FDIR_FLEX_CFG(_i) (0x19580 + ((_i) * 4))
#define TXGBE_RDB_FDIR_FLEX_CFG_FIELD0 GENMASK(7, 0)
#define TXGBE_RDB_FDIR_FLEX_CFG_BASE_MAC FIELD_PREP(GENMASK(1, 0), 0)
@@ -230,6 +246,13 @@ enum txgbe_fdir_pballoc_type {
TXGBE_FDIR_PBALLOC_256K = 3,
};
+struct txgbe_fdir_filter {
+ struct hlist_node fdir_node;
+ union txgbe_atr_input filter;
+ u16 sw_idx;
+ u16 action;
+};
+
/* TX/RX descriptor defines */
#define TXGBE_DEFAULT_TXD 512
#define TXGBE_DEFAULT_TX_WORK 256
@@ -316,7 +339,10 @@ struct txgbe {
unsigned int link_irq;
/* flow director */
+ struct hlist_head fdir_filter_list;
union txgbe_atr_input fdir_mask;
+ int fdir_filter_count;
+ spinlock_t fdir_perfect_lock; /*spinlock for FDIR */
};
#endif /* _TXGBE_TYPE_H_ */
--
2.27.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH net-next v2 3/3] net: txgbe: add FDIR info to ethtool ops
2024-06-05 2:08 [PATCH net-next v2 0/3] add flow director for txgbe Jiawen Wu
2024-06-05 2:08 ` [PATCH net-next v2 1/3] net: txgbe: add FDIR ATR support Jiawen Wu
2024-06-05 2:08 ` [PATCH net-next v2 2/3] net: txgbe: support Flow Director perfect filters Jiawen Wu
@ 2024-06-05 2:08 ` Jiawen Wu
2024-06-05 7:33 ` Hariprasad Kelam
2024-06-06 3:04 ` [PATCH net-next v2 0/3] add flow director for txgbe Jakub Kicinski
3 siblings, 1 reply; 12+ messages in thread
From: Jiawen Wu @ 2024-06-05 2:08 UTC (permalink / raw)
To: davem, edumazet, kuba, pabeni, linux, horms, andrew, netdev
Cc: mengyuanlou, Jiawen Wu
Add flow director filter match and miss statistics to ethtool -S.
And change the number of queues when using flow director for ehtool -l.
Signed-off-by: Jiawen Wu <jiawenwu@trustnetic.com>
---
.../net/ethernet/wangxun/libwx/wx_ethtool.c | 39 +++++++++++++++++--
drivers/net/ethernet/wangxun/libwx/wx_hw.c | 5 +++
drivers/net/ethernet/wangxun/libwx/wx_type.h | 4 ++
3 files changed, 45 insertions(+), 3 deletions(-)
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c b/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c
index cc3bec42ed8e..a6241091e95c 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c
+++ b/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c
@@ -43,6 +43,11 @@ static const struct wx_stats wx_gstrings_stats[] = {
WX_STAT("alloc_rx_buff_failed", alloc_rx_buff_failed),
};
+static const struct wx_stats wx_gstrings_fdir_stats[] = {
+ WX_STAT("fdir_match", stats.fdirmatch),
+ WX_STAT("fdir_miss", stats.fdirmiss),
+};
+
/* drivers allocates num_tx_queues and num_rx_queues symmetrically so
* we set the num_rx_queues to evaluate to num_tx_queues. This is
* used because we do not have a good way to get the max number of
@@ -55,12 +60,17 @@ static const struct wx_stats wx_gstrings_stats[] = {
(WX_NUM_TX_QUEUES + WX_NUM_RX_QUEUES) * \
(sizeof(struct wx_queue_stats) / sizeof(u64)))
#define WX_GLOBAL_STATS_LEN ARRAY_SIZE(wx_gstrings_stats)
+#define WX_FDIR_STATS_LEN ARRAY_SIZE(wx_gstrings_fdir_stats)
#define WX_STATS_LEN (WX_GLOBAL_STATS_LEN + WX_QUEUE_STATS_LEN)
int wx_get_sset_count(struct net_device *netdev, int sset)
{
+ struct wx *wx = netdev_priv(netdev);
+
switch (sset) {
case ETH_SS_STATS:
+ if (wx->mac.type == wx_mac_sp)
+ return WX_STATS_LEN + WX_FDIR_STATS_LEN;
return WX_STATS_LEN;
default:
return -EOPNOTSUPP;
@@ -70,6 +80,7 @@ EXPORT_SYMBOL(wx_get_sset_count);
void wx_get_strings(struct net_device *netdev, u32 stringset, u8 *data)
{
+ struct wx *wx = netdev_priv(netdev);
u8 *p = data;
int i;
@@ -77,6 +88,10 @@ void wx_get_strings(struct net_device *netdev, u32 stringset, u8 *data)
case ETH_SS_STATS:
for (i = 0; i < WX_GLOBAL_STATS_LEN; i++)
ethtool_puts(&p, wx_gstrings_stats[i].stat_string);
+ if (wx->mac.type == wx_mac_sp) {
+ for (i = 0; i < WX_FDIR_STATS_LEN; i++)
+ ethtool_puts(&p, wx_gstrings_fdir_stats[i].stat_string);
+ }
for (i = 0; i < netdev->num_tx_queues; i++) {
ethtool_sprintf(&p, "tx_queue_%u_packets", i);
ethtool_sprintf(&p, "tx_queue_%u_bytes", i);
@@ -96,7 +111,7 @@ void wx_get_ethtool_stats(struct net_device *netdev,
struct wx *wx = netdev_priv(netdev);
struct wx_ring *ring;
unsigned int start;
- int i, j;
+ int i, j, k;
char *p;
wx_update_stats(wx);
@@ -107,6 +122,14 @@ void wx_get_ethtool_stats(struct net_device *netdev,
sizeof(u64)) ? *(u64 *)p : *(u32 *)p;
}
+ if (wx->mac.type == wx_mac_sp) {
+ for (k = 0; k < WX_FDIR_STATS_LEN; k++) {
+ p = (char *)wx + wx_gstrings_fdir_stats[k].stat_offset;
+ data[i++] = (wx_gstrings_fdir_stats[k].sizeof_stat ==
+ sizeof(u64)) ? *(u64 *)p : *(u32 *)p;
+ }
+ }
+
for (j = 0; j < netdev->num_tx_queues; j++) {
ring = wx->tx_ring[j];
if (!ring) {
@@ -172,17 +195,21 @@ EXPORT_SYMBOL(wx_get_pause_stats);
void wx_get_drvinfo(struct net_device *netdev, struct ethtool_drvinfo *info)
{
+ unsigned int stats_len = WX_STATS_LEN;
struct wx *wx = netdev_priv(netdev);
+ if (wx->mac.type == wx_mac_sp)
+ stats_len += WX_FDIR_STATS_LEN;
+
strscpy(info->driver, wx->driver_name, sizeof(info->driver));
strscpy(info->fw_version, wx->eeprom_id, sizeof(info->fw_version));
strscpy(info->bus_info, pci_name(wx->pdev), sizeof(info->bus_info));
if (wx->num_tx_queues <= WX_NUM_TX_QUEUES) {
- info->n_stats = WX_STATS_LEN -
+ info->n_stats = stats_len -
(WX_NUM_TX_QUEUES - wx->num_tx_queues) *
(sizeof(struct wx_queue_stats) / sizeof(u64)) * 2;
} else {
- info->n_stats = WX_STATS_LEN;
+ info->n_stats = stats_len;
}
}
EXPORT_SYMBOL(wx_get_drvinfo);
@@ -383,6 +410,9 @@ void wx_get_channels(struct net_device *dev,
/* record RSS queues */
ch->combined_count = wx->ring_feature[RING_F_RSS].indices;
+
+ if (test_bit(WX_FLAG_FDIR_CAPABLE, wx->flags))
+ ch->combined_count = wx->ring_feature[RING_F_FDIR].indices;
}
EXPORT_SYMBOL(wx_get_channels);
@@ -400,6 +430,9 @@ int wx_set_channels(struct net_device *dev,
if (count > wx_max_channels(wx))
return -EINVAL;
+ if (test_bit(WX_FLAG_FDIR_CAPABLE, wx->flags))
+ wx->ring_feature[RING_F_FDIR].limit = count;
+
wx->ring_feature[RING_F_RSS].limit = count;
return 0;
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_hw.c b/drivers/net/ethernet/wangxun/libwx/wx_hw.c
index 8fb38f83a615..44cd7a5866c1 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_hw.c
+++ b/drivers/net/ethernet/wangxun/libwx/wx_hw.c
@@ -2352,6 +2352,11 @@ void wx_update_stats(struct wx *wx)
hwstats->b2ogprc += rd32(wx, WX_RDM_BMC2OS_CNT);
hwstats->rdmdrop += rd32(wx, WX_RDM_DRP_PKT);
+ if (wx->mac.type == wx_mac_sp) {
+ hwstats->fdirmatch += rd32(wx, WX_RDB_FDIR_MATCH);
+ hwstats->fdirmiss += rd32(wx, WX_RDB_FDIR_MISS);
+ }
+
for (i = 0; i < wx->mac.max_rx_queues; i++)
hwstats->qmprc += rd32(wx, WX_PX_MPRC(i));
}
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_type.h b/drivers/net/ethernet/wangxun/libwx/wx_type.h
index b1f9bab06e90..e0b7866f96ec 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_type.h
+++ b/drivers/net/ethernet/wangxun/libwx/wx_type.h
@@ -157,6 +157,8 @@
#define WX_RDB_RA_CTL_RSS_IPV6_TCP BIT(21)
#define WX_RDB_RA_CTL_RSS_IPV4_UDP BIT(22)
#define WX_RDB_RA_CTL_RSS_IPV6_UDP BIT(23)
+#define WX_RDB_FDIR_MATCH 0x19558
+#define WX_RDB_FDIR_MISS 0x1955C
/******************************* PSR Registers *******************************/
/* psr control */
@@ -1018,6 +1020,8 @@ struct wx_hw_stats {
u64 crcerrs;
u64 rlec;
u64 qmprc;
+ u64 fdirmatch;
+ u64 fdirmiss;
};
enum wx_state {
--
2.27.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH net-next v2 3/3] net: txgbe: add FDIR info to ethtool ops
2024-06-05 2:08 ` [PATCH net-next v2 3/3] net: txgbe: add FDIR info to ethtool ops Jiawen Wu
@ 2024-06-05 7:33 ` Hariprasad Kelam
2024-06-06 0:52 ` Jakub Kicinski
0 siblings, 1 reply; 12+ messages in thread
From: Hariprasad Kelam @ 2024-06-05 7:33 UTC (permalink / raw)
To: Jiawen Wu, davem@davemloft.net, edumazet@google.com,
kuba@kernel.org, pabeni@redhat.com, linux@armlinux.org.uk,
horms@kernel.org, andrew@lunn.ch, netdev@vger.kernel.org
Cc: mengyuanlou@net-swift.com
> Add flow director filter match and miss statistics to ethtool -S.
> And change the number of queues when using flow director for ehtool -l.
>
> Signed-off-by: Jiawen Wu <jiawenwu@trustnetic.com>
> ---
> .../net/ethernet/wangxun/libwx/wx_ethtool.c | 39 +++++++++++++++++--
> drivers/net/ethernet/wangxun/libwx/wx_hw.c | 5 +++
> drivers/net/ethernet/wangxun/libwx/wx_type.h | 4 ++
> 3 files changed, 45 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c
> b/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c
> index cc3bec42ed8e..a6241091e95c 100644
> --- a/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c
> +++ b/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c
> @@ -43,6 +43,11 @@ static const struct wx_stats wx_gstrings_stats[] = {
> WX_STAT("alloc_rx_buff_failed", alloc_rx_buff_failed), };
>
> +static const struct wx_stats wx_gstrings_fdir_stats[] = {
> + WX_STAT("fdir_match", stats.fdirmatch),
> + WX_STAT("fdir_miss", stats.fdirmiss),
> +};
> +
> /* drivers allocates num_tx_queues and num_rx_queues symmetrically so
> * we set the num_rx_queues to evaluate to num_tx_queues. This is
> * used because we do not have a good way to get the max number of @@ -
> 55,12 +60,17 @@ static const struct wx_stats wx_gstrings_stats[] = {
> (WX_NUM_TX_QUEUES + WX_NUM_RX_QUEUES) * \
> (sizeof(struct wx_queue_stats) / sizeof(u64))) #define
> WX_GLOBAL_STATS_LEN ARRAY_SIZE(wx_gstrings_stats)
> +#define WX_FDIR_STATS_LEN ARRAY_SIZE(wx_gstrings_fdir_stats)
> #define WX_STATS_LEN (WX_GLOBAL_STATS_LEN + WX_QUEUE_STATS_LEN)
>
> int wx_get_sset_count(struct net_device *netdev, int sset) {
> + struct wx *wx = netdev_priv(netdev);
> +
> switch (sset) {
> case ETH_SS_STATS:
> + if (wx->mac.type == wx_mac_sp)
> + return WX_STATS_LEN + WX_FDIR_STATS_LEN;
> return WX_STATS_LEN;
Better way is to use ternary operator.
Return (wx->mac.type == wx_mac_sp) ? WX_STATS_LEN + WX_FDIR_STATS_LEN : WX_STATS_LEN;
> default:
> return -EOPNOTSUPP;
> @@ -70,6 +80,7 @@ EXPORT_SYMBOL(wx_get_sset_count);
>
> void wx_get_strings(struct net_device *netdev, u32 stringset, u8 *data) {
> + struct wx *wx = netdev_priv(netdev);
> u8 *p = data;
> int i;
>
> @@ -77,6 +88,10 @@ void wx_get_strings(struct net_device *netdev, u32
> stringset, u8 *data)
> case ETH_SS_STATS:
> for (i = 0; i < WX_GLOBAL_STATS_LEN; i++)
> ethtool_puts(&p, wx_gstrings_stats[i].stat_string);
> + if (wx->mac.type == wx_mac_sp) {
> + for (i = 0; i < WX_FDIR_STATS_LEN; i++)
> + ethtool_puts(&p,
> wx_gstrings_fdir_stats[i].stat_string);
> + }
> for (i = 0; i < netdev->num_tx_queues; i++) {
> ethtool_sprintf(&p, "tx_queue_%u_packets", i);
> ethtool_sprintf(&p, "tx_queue_%u_bytes", i); @@ -
> 96,7 +111,7 @@ void wx_get_ethtool_stats(struct net_device *netdev,
> struct wx *wx = netdev_priv(netdev);
> struct wx_ring *ring;
> unsigned int start;
> - int i, j;
> + int i, j, k;
> char *p;
>
> wx_update_stats(wx);
> @@ -107,6 +122,14 @@ void wx_get_ethtool_stats(struct net_device
> *netdev,
> sizeof(u64)) ? *(u64 *)p : *(u32 *)p;
> }
>
> + if (wx->mac.type == wx_mac_sp) {
> + for (k = 0; k < WX_FDIR_STATS_LEN; k++) {
> + p = (char *)wx + wx_gstrings_fdir_stats[k].stat_offset;
> + data[i++] = (wx_gstrings_fdir_stats[k].sizeof_stat ==
> + sizeof(u64)) ? *(u64 *)p : *(u32 *)p;
Since fdir_match/fdir_len are u64, do we need to check the size here?
Thanks,
Hariprasad k
> + }
> + }
> +
> for (j = 0; j < netdev->num_tx_queues; j++) {
> ring = wx->tx_ring[j];
> if (!ring) {
> @@ -172,17 +195,21 @@ EXPORT_SYMBOL(wx_get_pause_stats);
>
> void wx_get_drvinfo(struct net_device *netdev, struct ethtool_drvinfo *info)
> {
> + unsigned int stats_len = WX_STATS_LEN;
> struct wx *wx = netdev_priv(netdev);
>
> + if (wx->mac.type == wx_mac_sp)
> + stats_len += WX_FDIR_STATS_LEN;
> +
> strscpy(info->driver, wx->driver_name, sizeof(info->driver));
> strscpy(info->fw_version, wx->eeprom_id, sizeof(info->fw_version));
> strscpy(info->bus_info, pci_name(wx->pdev), sizeof(info->bus_info));
> if (wx->num_tx_queues <= WX_NUM_TX_QUEUES) {
> - info->n_stats = WX_STATS_LEN -
> + info->n_stats = stats_len -
> (WX_NUM_TX_QUEUES - wx-
> >num_tx_queues) *
> (sizeof(struct wx_queue_stats) / sizeof(u64))
> * 2;
> } else {
> - info->n_stats = WX_STATS_LEN;
> + info->n_stats = stats_len;
> }
> }
> EXPORT_SYMBOL(wx_get_drvinfo);
> @@ -383,6 +410,9 @@ void wx_get_channels(struct net_device *dev,
>
> /* record RSS queues */
> ch->combined_count = wx->ring_feature[RING_F_RSS].indices;
> +
> + if (test_bit(WX_FLAG_FDIR_CAPABLE, wx->flags))
> + ch->combined_count = wx-
> >ring_feature[RING_F_FDIR].indices;
> }
> EXPORT_SYMBOL(wx_get_channels);
>
> @@ -400,6 +430,9 @@ int wx_set_channels(struct net_device *dev,
> if (count > wx_max_channels(wx))
> return -EINVAL;
>
> + if (test_bit(WX_FLAG_FDIR_CAPABLE, wx->flags))
> + wx->ring_feature[RING_F_FDIR].limit = count;
> +
> wx->ring_feature[RING_F_RSS].limit = count;
>
> return 0;
> diff --git a/drivers/net/ethernet/wangxun/libwx/wx_hw.c
> b/drivers/net/ethernet/wangxun/libwx/wx_hw.c
> index 8fb38f83a615..44cd7a5866c1 100644
> --- a/drivers/net/ethernet/wangxun/libwx/wx_hw.c
> +++ b/drivers/net/ethernet/wangxun/libwx/wx_hw.c
> @@ -2352,6 +2352,11 @@ void wx_update_stats(struct wx *wx)
> hwstats->b2ogprc += rd32(wx, WX_RDM_BMC2OS_CNT);
> hwstats->rdmdrop += rd32(wx, WX_RDM_DRP_PKT);
>
> + if (wx->mac.type == wx_mac_sp) {
> + hwstats->fdirmatch += rd32(wx, WX_RDB_FDIR_MATCH);
> + hwstats->fdirmiss += rd32(wx, WX_RDB_FDIR_MISS);
> + }
> +
> for (i = 0; i < wx->mac.max_rx_queues; i++)
> hwstats->qmprc += rd32(wx, WX_PX_MPRC(i)); } diff --git
> a/drivers/net/ethernet/wangxun/libwx/wx_type.h
> b/drivers/net/ethernet/wangxun/libwx/wx_type.h
> index b1f9bab06e90..e0b7866f96ec 100644
> --- a/drivers/net/ethernet/wangxun/libwx/wx_type.h
> +++ b/drivers/net/ethernet/wangxun/libwx/wx_type.h
> @@ -157,6 +157,8 @@
> #define WX_RDB_RA_CTL_RSS_IPV6_TCP BIT(21)
> #define WX_RDB_RA_CTL_RSS_IPV4_UDP BIT(22)
> #define WX_RDB_RA_CTL_RSS_IPV6_UDP BIT(23)
> +#define WX_RDB_FDIR_MATCH 0x19558
> +#define WX_RDB_FDIR_MISS 0x1955C
>
> /******************************* PSR Registers
> *******************************/
> /* psr control */
> @@ -1018,6 +1020,8 @@ struct wx_hw_stats {
> u64 crcerrs;
> u64 rlec;
> u64 qmprc;
> + u64 fdirmatch;
> + u64 fdirmiss;
> };
>
> enum wx_state {
> --
> 2.27.0
>
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH net-next v2 3/3] net: txgbe: add FDIR info to ethtool ops
2024-06-05 7:33 ` Hariprasad Kelam
@ 2024-06-06 0:52 ` Jakub Kicinski
0 siblings, 0 replies; 12+ messages in thread
From: Jakub Kicinski @ 2024-06-06 0:52 UTC (permalink / raw)
To: Hariprasad Kelam
Cc: Jiawen Wu, davem@davemloft.net, edumazet@google.com,
pabeni@redhat.com, linux@armlinux.org.uk, horms@kernel.org,
andrew@lunn.ch, netdev@vger.kernel.org, mengyuanlou@net-swift.com
On Wed, 5 Jun 2024 07:33:32 +0000 Hariprasad Kelam wrote:
> > + if (wx->mac.type == wx_mac_sp)
> > + return WX_STATS_LEN + WX_FDIR_STATS_LEN;
> > return WX_STATS_LEN;
>
> Better way is to use ternary operator.
> Return (wx->mac.type == wx_mac_sp) ? WX_STATS_LEN + WX_FDIR_STATS_LEN : WX_STATS_LEN;
I'd leave it be. We prefer lines to be shorter than 80 chars.
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH net-next v2 0/3] add flow director for txgbe
2024-06-05 2:08 [PATCH net-next v2 0/3] add flow director for txgbe Jiawen Wu
` (2 preceding siblings ...)
2024-06-05 2:08 ` [PATCH net-next v2 3/3] net: txgbe: add FDIR info to ethtool ops Jiawen Wu
@ 2024-06-06 3:04 ` Jakub Kicinski
3 siblings, 0 replies; 12+ messages in thread
From: Jakub Kicinski @ 2024-06-06 3:04 UTC (permalink / raw)
To: Jiawen Wu
Cc: davem, edumazet, pabeni, linux, horms, andrew, netdev,
mengyuanlou
On Wed, 5 Jun 2024 10:08:49 +0800 Jiawen Wu wrote:
> Add flow director support for Wangxun 10Gb NICs.
Some nits:
- please wrap the code at 80 chars where possible
- please run ./scripts/kernel-doc -Wall -none $file; and make sure no
new warnings get added:
drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.c:114: warning: No description found for return value of 'txgbe_fdir_add_signature_filter'
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH net-next v2 2/3] net: txgbe: support Flow Director perfect filters
2024-06-05 2:08 ` [PATCH net-next v2 2/3] net: txgbe: support Flow Director perfect filters Jiawen Wu
@ 2024-06-06 8:53 ` Hariprasad Kelam
2024-06-18 8:55 ` Jiawen Wu
2024-06-06 20:49 ` Simon Horman
1 sibling, 1 reply; 12+ messages in thread
From: Hariprasad Kelam @ 2024-06-06 8:53 UTC (permalink / raw)
To: Jiawen Wu, davem@davemloft.net, edumazet@google.com,
kuba@kernel.org, pabeni@redhat.com, linux@armlinux.org.uk,
horms@kernel.org, andrew@lunn.ch, netdev@vger.kernel.org
Cc: mengyuanlou@net-swift.com
> Support the addition and deletion of Flow Director filters.
>
> Supported fields: src-ip, dst-ip, src-port, dst-port Supported flow-types: tcp4,
> udp4, sctp4, ipv4
>
> Signed-off-by: Jiawen Wu <jiawenwu@trustnetic.com>
> ---
> drivers/net/ethernet/wangxun/libwx/wx_lib.c | 31 ++
> .../ethernet/wangxun/txgbe/txgbe_ethtool.c | 417 ++++++++++++++++++
> .../net/ethernet/wangxun/txgbe/txgbe_fdir.c | 334 +++++++++++++-
> .../net/ethernet/wangxun/txgbe/txgbe_fdir.h | 8 +
> .../net/ethernet/wangxun/txgbe/txgbe_main.c | 9 +
> .../net/ethernet/wangxun/txgbe/txgbe_type.h | 26 ++
> 6 files changed, 824 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/net/ethernet/wangxun/libwx/wx_lib.c
> b/drivers/net/ethernet/wangxun/libwx/wx_lib.c
> index 8774206ca496..59317a8a3320 100644
> --- a/drivers/net/ethernet/wangxun/libwx/wx_lib.c
> +++ b/drivers/net/ethernet/wangxun/libwx/wx_lib.c
> @@ -2705,6 +2705,7 @@ int wx_set_features(struct net_device *netdev,
> netdev_features_t features) {
> netdev_features_t changed = netdev->features ^ features;
> struct wx *wx = netdev_priv(netdev);
> + bool need_reset = false;
>
> if (features & NETIF_F_RXHASH) {
> wr32m(wx, WX_RDB_RA_CTL, WX_RDB_RA_CTL_RSS_EN,
> @@ -2722,6 +2723,36 @@ int wx_set_features(struct net_device *netdev,
> netdev_features_t features)
> else if (changed & (NETIF_F_HW_VLAN_CTAG_RX |
> NETIF_F_HW_VLAN_CTAG_FILTER))
> wx_set_rx_mode(netdev);
>
> + if (!(test_bit(WX_FLAG_FDIR_CAPABLE, wx->flags)))
> + return 0;
> +
> + /* Check if Flow Director n-tuple support was enabled or disabled. If
> + * the state changed, we need to reset.
> + */
> + switch (features & NETIF_F_NTUPLE) {
> + case NETIF_F_NTUPLE:
> + /* turn off ATR, enable perfect filters and reset */
> + if (!(test_and_set_bit(WX_FLAG_FDIR_PERFECT, wx->flags)))
> + need_reset = true;
> +
> + clear_bit(WX_FLAG_FDIR_HASH, wx->flags);
> + break;
> + default:
> + /* turn off perfect filters, enable ATR and reset */
> + if (test_and_clear_bit(WX_FLAG_FDIR_PERFECT, wx->flags))
> + need_reset = true;
> +
> + /* We cannot enable ATR if RSS is disabled */
> + if (wx->ring_feature[RING_F_RSS].limit <= 1)
> + break;
> +
> + set_bit(WX_FLAG_FDIR_HASH, wx->flags);
> + break;
> + }
> +
> + if (need_reset)
> + wx->do_reset(netdev);
> +
> return 0;
> }
> EXPORT_SYMBOL(wx_set_features);
> diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_ethtool.c
> b/drivers/net/ethernet/wangxun/txgbe/txgbe_ethtool.c
> index 31fde3fa7c6b..4aac64820eb3 100644
> --- a/drivers/net/ethernet/wangxun/txgbe/txgbe_ethtool.c
> +++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_ethtool.c
> @@ -9,6 +9,7 @@
> #include "../libwx/wx_type.h"
> #include "../libwx/wx_lib.h"
> #include "txgbe_type.h"
> +#include "txgbe_fdir.h"
> #include "txgbe_ethtool.h"
>
> static int txgbe_set_ringparam(struct net_device *netdev, @@ -79,6 +80,420
> @@ static int txgbe_set_channels(struct net_device *dev,
> return txgbe_setup_tc(dev, netdev_get_num_tc(dev)); }
>
> +static int txgbe_get_ethtool_fdir_entry(struct txgbe *txgbe,
> + struct ethtool_rxnfc *cmd)
> +{
> + struct ethtool_rx_flow_spec *fsp = (struct ethtool_rx_flow_spec
> *)&cmd->fs;
> + union txgbe_atr_input *mask = &txgbe->fdir_mask;
> + struct txgbe_fdir_filter *rule = NULL;
> + struct hlist_node *node;
> +
> + /* report total rule count */
> + cmd->data = (1024 << TXGBE_FDIR_PBALLOC_64K) - 2;
> +
> + hlist_for_each_entry_safe(rule, node, &txgbe->fdir_filter_list,
> fdir_node) {
> + if (fsp->location <= rule->sw_idx)
> + break;
> + }
> +
> + if (!rule || fsp->location != rule->sw_idx)
> + return -EINVAL;
> +
> + /* set flow type field */
> + switch (rule->filter.formatted.flow_type) {
> + case TXGBE_ATR_FLOW_TYPE_TCPV4:
> + fsp->flow_type = TCP_V4_FLOW;
> + break;
> + case TXGBE_ATR_FLOW_TYPE_UDPV4:
> + fsp->flow_type = UDP_V4_FLOW;
> + break;
> + case TXGBE_ATR_FLOW_TYPE_SCTPV4:
> + fsp->flow_type = SCTP_V4_FLOW;
> + break;
> + case TXGBE_ATR_FLOW_TYPE_IPV4:
> + fsp->flow_type = IP_USER_FLOW;
> + fsp->h_u.usr_ip4_spec.ip_ver = ETH_RX_NFC_IP4;
> + fsp->h_u.usr_ip4_spec.proto = 0;
> + fsp->m_u.usr_ip4_spec.proto = 0;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + fsp->h_u.tcp_ip4_spec.psrc = rule->filter.formatted.src_port;
> + fsp->m_u.tcp_ip4_spec.psrc = mask->formatted.src_port;
> + fsp->h_u.tcp_ip4_spec.pdst = rule->filter.formatted.dst_port;
> + fsp->m_u.tcp_ip4_spec.pdst = mask->formatted.dst_port;
> + fsp->h_u.tcp_ip4_spec.ip4src = rule->filter.formatted.src_ip[0];
> + fsp->m_u.tcp_ip4_spec.ip4src = mask->formatted.src_ip[0];
> + fsp->h_u.tcp_ip4_spec.ip4dst = rule->filter.formatted.dst_ip[0];
> + fsp->m_u.tcp_ip4_spec.ip4dst = mask->formatted.dst_ip[0];
> + fsp->h_ext.vlan_etype = rule->filter.formatted.flex_bytes;
> + fsp->m_ext.vlan_etype = mask->formatted.flex_bytes;
> + fsp->h_ext.data[1] = htonl(rule->filter.formatted.vm_pool);
> + fsp->m_ext.data[1] = htonl(mask->formatted.vm_pool);
> + fsp->flow_type |= FLOW_EXT;
> +
> + /* record action */
> + if (rule->action == TXGBE_RDB_FDIR_DROP_QUEUE)
> + fsp->ring_cookie = RX_CLS_FLOW_DISC;
> + else
> + fsp->ring_cookie = rule->action;
> +
> + return 0;
> +}
> +
> +static int txgbe_get_ethtool_fdir_all(struct txgbe *txgbe,
> + struct ethtool_rxnfc *cmd,
> + u32 *rule_locs)
> +{
> + struct txgbe_fdir_filter *rule;
> + struct hlist_node *node;
> + int cnt = 0;
> +
> + /* report total rule count */
> + cmd->data = (1024 << TXGBE_FDIR_PBALLOC_64K) - 2;
> +
> + hlist_for_each_entry_safe(rule, node, &txgbe->fdir_filter_list,
> fdir_node) {
> + if (cnt == cmd->rule_cnt)
> + return -EMSGSIZE;
> + rule_locs[cnt] = rule->sw_idx;
> + cnt++;
> + }
> +
> + cmd->rule_cnt = cnt;
> +
> + return 0;
> +}
> +
> +static int txgbe_get_rxnfc(struct net_device *dev, struct ethtool_rxnfc *cmd,
> + u32 *rule_locs)
> +{
> + struct wx *wx = netdev_priv(dev);
> + struct txgbe *txgbe = wx->priv;
> + int ret = -EOPNOTSUPP;
> +
> + switch (cmd->cmd) {
> + case ETHTOOL_GRXRINGS:
> + cmd->data = wx->num_rx_queues;
> + ret = 0;
> + break;
> + case ETHTOOL_GRXCLSRLCNT:
> + cmd->rule_cnt = txgbe->fdir_filter_count;
> + ret = 0;
> + break;
> + case ETHTOOL_GRXCLSRULE:
> + ret = txgbe_get_ethtool_fdir_entry(txgbe, cmd);
> + break;
> + case ETHTOOL_GRXCLSRLALL:
> + ret = txgbe_get_ethtool_fdir_all(txgbe, cmd, (u32 *)rule_locs);
> + break;
> + default:
> + break;
> + }
> +
> + return ret;
> +}
> +
> +static int txgbe_flowspec_to_flow_type(struct ethtool_rx_flow_spec *fsp,
> + u8 *flow_type)
> +{
> + switch (fsp->flow_type & ~FLOW_EXT) {
> + case TCP_V4_FLOW:
> + *flow_type = TXGBE_ATR_FLOW_TYPE_TCPV4;
> + break;
> + case UDP_V4_FLOW:
> + *flow_type = TXGBE_ATR_FLOW_TYPE_UDPV4;
> + break;
> + case SCTP_V4_FLOW:
> + *flow_type = TXGBE_ATR_FLOW_TYPE_SCTPV4;
> + break;
> + case IP_USER_FLOW:
> + switch (fsp->h_u.usr_ip4_spec.proto) {
> + case IPPROTO_TCP:
> + *flow_type = TXGBE_ATR_FLOW_TYPE_TCPV4;
> + break;
> + case IPPROTO_UDP:
> + *flow_type = TXGBE_ATR_FLOW_TYPE_UDPV4;
> + break;
> + case IPPROTO_SCTP:
> + *flow_type = TXGBE_ATR_FLOW_TYPE_SCTPV4;
> + break;
> + case 0:
> + if (!fsp->m_u.usr_ip4_spec.proto) {
> + *flow_type = TXGBE_ATR_FLOW_TYPE_IPV4;
> + break;
> + }
> + fallthrough;
> + default:
> + return -EINVAL;
> + }
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +static bool txgbe_match_ethtool_fdir_entry(struct txgbe *txgbe,
> + struct txgbe_fdir_filter *input) {
> + struct txgbe_fdir_filter *rule = NULL;
> + struct hlist_node *node2;
> +
> + hlist_for_each_entry_safe(rule, node2, &txgbe->fdir_filter_list,
> fdir_node) {
> + if (rule->filter.formatted.bkt_hash ==
> + input->filter.formatted.bkt_hash &&
> + rule->action == input->action) {
> + wx_dbg(txgbe->wx, "FDIR entry already exist\n");
> + return true;
> + }
> + }
> + return false;
> +}
> +
> +static int txgbe_update_ethtool_fdir_entry(struct txgbe *txgbe,
> + struct txgbe_fdir_filter *input,
> + u16 sw_idx)
> +{
> + struct hlist_node *node = NULL, *parent = NULL;
> + struct txgbe_fdir_filter *rule;
> + struct wx *wx = txgbe->wx;
> + bool deleted = false;
> + int err;
> +
> + hlist_for_each_entry_safe(rule, node, &txgbe->fdir_filter_list,
> fdir_node) {
> + /* hash found, or no matching entry */
> + if (rule->sw_idx >= sw_idx)
> + break;
> + parent = node;
> + }
> +
> + /* if there is an old rule occupying our place remove it */
> + if (rule && rule->sw_idx == sw_idx) {
> + /* hardware filters are only configured when interface is up,
> + * and we should not issue filter commands while the
> interface
> + * is down
> + */
> + if (netif_running(wx->netdev) &&
> + (!input || rule->filter.formatted.bkt_hash !=
> + input->filter.formatted.bkt_hash)) {
> + err = txgbe_fdir_erase_perfect_filter(wx, &rule->filter,
> sw_idx);
> + if (err)
> + return -EINVAL;
> + }
> +
> + hlist_del(&rule->fdir_node);
> + kfree(rule);
> + txgbe->fdir_filter_count--;
> + deleted = true;
> + }
> +
> + /* If we weren't given an input, then this was a request to delete a
> + * filter. We should return -EINVAL if the filter wasn't found, but
> + * return 0 if the rule was successfully deleted.
> + */
> + if (!input)
> + return deleted ? 0 : -EINVAL;
> +
> + /* initialize node and set software index */
> + INIT_HLIST_NODE(&input->fdir_node);
> +
> + /* add filter to the list */
> + if (parent)
> + hlist_add_behind(&input->fdir_node, parent);
> + else
> + hlist_add_head(&input->fdir_node,
> + &txgbe->fdir_filter_list);
> +
> + /* update counts */
> + txgbe->fdir_filter_count++;
> +
> + return 0;
> +}
> +
> +static int txgbe_add_ethtool_fdir_entry(struct txgbe *txgbe,
> + struct ethtool_rxnfc *cmd)
> +{
> + struct ethtool_rx_flow_spec *fsp = (struct ethtool_rx_flow_spec
> *)&cmd->fs;
> + struct txgbe_fdir_filter *input;
> + union txgbe_atr_input mask;
> + struct wx *wx = txgbe->wx;
> + u16 ptype = 0;
> + u8 queue;
> + int err;
> +
> + if (!(test_bit(WX_FLAG_FDIR_PERFECT, wx->flags)))
> + return -EOPNOTSUPP;
> +
> + /* ring_cookie is a masked into a set of queues and txgbe pools or
> + * we use drop index
> + */
> + if (fsp->ring_cookie == RX_CLS_FLOW_DISC) {
> + queue = TXGBE_RDB_FDIR_DROP_QUEUE;
> + } else {
> + u32 ring = ethtool_get_flow_spec_ring(fsp->ring_cookie);
> +
> + if (ring >= wx->num_rx_queues)
> + return -EINVAL;
> +
> + /* Map the ring onto the absolute queue index */
> + queue = wx->rx_ring[ring]->reg_idx;
> + }
> +
> + /* Don't allow indexes to exist outside of available space */
> + if (fsp->location >= ((1024 << TXGBE_FDIR_PBALLOC_64K) - 2)) {
> + wx_err(wx, "Location out of range\n");
> + return -EINVAL;
> + }
> +
> + input = kzalloc(sizeof(*input), GFP_ATOMIC);
> + if (!input)
> + return -ENOMEM;
> +
> + memset(&mask, 0, sizeof(union txgbe_atr_input));
> +
> + /* set SW index */
> + input->sw_idx = fsp->location;
> +
> + /* record flow type */
> + if (txgbe_flowspec_to_flow_type(fsp, &input-
> >filter.formatted.flow_type)) {
> + wx_err(wx, "Unrecognized flow type\n");
> + goto err_out;
> + }
> +
> + mask.formatted.flow_type = TXGBE_ATR_L4TYPE_IPV6_MASK |
> + TXGBE_ATR_L4TYPE_MASK;
> +
> + if (input->filter.formatted.flow_type ==
> TXGBE_ATR_FLOW_TYPE_IPV4)
> + mask.formatted.flow_type &=
> TXGBE_ATR_L4TYPE_IPV6_MASK;
> +
> + /* Copy input into formatted structures */
> + input->filter.formatted.src_ip[0] = fsp->h_u.tcp_ip4_spec.ip4src;
> + mask.formatted.src_ip[0] = fsp->m_u.tcp_ip4_spec.ip4src;
> + input->filter.formatted.dst_ip[0] = fsp->h_u.tcp_ip4_spec.ip4dst;
> + mask.formatted.dst_ip[0] = fsp->m_u.tcp_ip4_spec.ip4dst;
> + input->filter.formatted.src_port = fsp->h_u.tcp_ip4_spec.psrc;
> + mask.formatted.src_port = fsp->m_u.tcp_ip4_spec.psrc;
> + input->filter.formatted.dst_port = fsp->h_u.tcp_ip4_spec.pdst;
> + mask.formatted.dst_port = fsp->m_u.tcp_ip4_spec.pdst;
> +
> + if (fsp->flow_type & FLOW_EXT) {
> + input->filter.formatted.vm_pool =
> + (unsigned char)ntohl(fsp->h_ext.data[1]);
> + mask.formatted.vm_pool =
> + (unsigned char)ntohl(fsp->m_ext.data[1]);
> + input->filter.formatted.flex_bytes =
> + fsp->h_ext.vlan_etype;
> + mask.formatted.flex_bytes = fsp->m_ext.vlan_etype;
> + }
> +
> + switch (input->filter.formatted.flow_type) {
> + case TXGBE_ATR_FLOW_TYPE_TCPV4:
> + ptype = WX_PTYPE_L2_IPV4_TCP;
> + break;
> + case TXGBE_ATR_FLOW_TYPE_UDPV4:
> + ptype = WX_PTYPE_L2_IPV4_UDP;
> + break;
> + case TXGBE_ATR_FLOW_TYPE_SCTPV4:
> + ptype = WX_PTYPE_L2_IPV4_SCTP;
> + break;
> + case TXGBE_ATR_FLOW_TYPE_IPV4:
> + ptype = WX_PTYPE_L2_IPV4;
> + break;
> + default:
> + break;
> + }
> +
> + input->filter.formatted.vlan_id = htons(ptype);
> + if (mask.formatted.flow_type & TXGBE_ATR_L4TYPE_MASK)
> + mask.formatted.vlan_id = htons(0xFFFF);
> + else
> + mask.formatted.vlan_id = htons(0xFFF8);
> +
> + /* determine if we need to drop or route the packet */
> + if (fsp->ring_cookie == RX_CLS_FLOW_DISC)
> + input->action = TXGBE_RDB_FDIR_DROP_QUEUE;
> + else
> + input->action = fsp->ring_cookie;
> +
> + spin_lock(&txgbe->fdir_perfect_lock);
ethtool ops is already protected with rtnl_lock , which can be confirmed by calling ASSERT_RTNL().
Why do we need a spin_lock here ?
Thanks
Hariprasad k
> +
> + if (hlist_empty(&txgbe->fdir_filter_list)) {
> + /* save mask and program input mask into HW */
> + memcpy(&txgbe->fdir_mask, &mask, sizeof(mask));
> + err = txgbe_fdir_set_input_mask(wx, &mask);
> + if (err)
> + goto err_unlock;
> + } else if (memcmp(&txgbe->fdir_mask, &mask, sizeof(mask))) {
> + wx_err(wx, "Hardware only supports one mask per port. To
> change the mask you must first delete all the rules.\n");
> + goto err_unlock;
> + }
> +
> + /* apply mask and compute/store hash */
> + txgbe_atr_compute_perfect_hash(&input->filter, &mask);
> +
> + /* check if new entry does not exist on filter list */
> + if (txgbe_match_ethtool_fdir_entry(txgbe, input))
> + goto err_unlock;
> +
> + /* only program filters to hardware if the net device is running, as
> + * we store the filters in the Rx buffer which is not allocated when
> + * the device is down
> + */
> + if (netif_running(wx->netdev)) {
> + err = txgbe_fdir_write_perfect_filter(wx, &input->filter,
> + input->sw_idx, queue);
> + if (err)
> + goto err_unlock;
> + }
> +
> + txgbe_update_ethtool_fdir_entry(txgbe, input, input->sw_idx);
> +
> + spin_unlock(&txgbe->fdir_perfect_lock);
> +
> + return err;
> +err_unlock:
> + spin_unlock(&txgbe->fdir_perfect_lock);
> +err_out:
> + kfree(input);
> + return -EINVAL;
> +}
> +
> +static int txgbe_del_ethtool_fdir_entry(struct txgbe *txgbe,
> + struct ethtool_rxnfc *cmd)
> +{
> + struct ethtool_rx_flow_spec *fsp = (struct ethtool_rx_flow_spec
> *)&cmd->fs;
> + int err = 0;
> +
> + spin_lock(&txgbe->fdir_perfect_lock);
> + err = txgbe_update_ethtool_fdir_entry(txgbe, NULL, fsp->location);
> + spin_unlock(&txgbe->fdir_perfect_lock);
> +
> + return err;
> +}
> +
> +static int txgbe_set_rxnfc(struct net_device *dev, struct ethtool_rxnfc
> +*cmd) {
> + struct wx *wx = netdev_priv(dev);
> + struct txgbe *txgbe = wx->priv;
> + int ret = -EOPNOTSUPP;
> +
> + switch (cmd->cmd) {
> + case ETHTOOL_SRXCLSRLINS:
> + ret = txgbe_add_ethtool_fdir_entry(txgbe, cmd);
> + break;
> + case ETHTOOL_SRXCLSRLDEL:
> + ret = txgbe_del_ethtool_fdir_entry(txgbe, cmd);
> + break;
> + default:
> + break;
> + }
> +
> + return ret;
> +}
> +
> static const struct ethtool_ops txgbe_ethtool_ops = {
> .supported_coalesce_params = ETHTOOL_COALESCE_USECS |
>
> ETHTOOL_COALESCE_TX_MAX_FRAMES_IRQ,
> @@ -100,6 +515,8 @@ static const struct ethtool_ops txgbe_ethtool_ops = {
> .set_coalesce = wx_set_coalesce,
> .get_channels = wx_get_channels,
> .set_channels = txgbe_set_channels,
> + .get_rxnfc = txgbe_get_rxnfc,
> + .set_rxnfc = txgbe_set_rxnfc,
> .get_msglevel = wx_get_msglevel,
> .set_msglevel = wx_set_msglevel,
> };
> diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.c
> b/drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.c
> index b10676c00cea..ac6b690b6da6 100644
> --- a/drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.c
> +++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.c
> @@ -90,6 +90,71 @@ static void txgbe_atr_compute_sig_hash(union
> txgbe_atr_hash_dword input,
> *hash = sig_hash ^ bucket_hash;
> }
>
> +#define TXGBE_COMPUTE_BKT_HASH_ITERATION(_n) \ do { \
> + u32 n = (_n); \
> + if (TXGBE_ATR_BUCKET_HASH_KEY & (0x01 << n)) \
> + bucket_hash ^= lo_hash_dword >> n; \
> + if (TXGBE_ATR_BUCKET_HASH_KEY & (0x01 << (n + 16))) \
> + bucket_hash ^= hi_hash_dword >> n; \
> +} while (0)
> +
> +/**
> + * txgbe_atr_compute_perfect_hash - Compute the perfect filter hash
> + * @input: input bitstream to compute the hash on
> + * @input_mask: mask for the input bitstream
> + *
> + * This function serves two main purposes. First it applies the
> +input_mask
> + * to the atr_input resulting in a cleaned up atr_input data stream.
> + * Secondly it computes the hash and stores it in the bkt_hash field
> +at
> + * the end of the input byte stream. This way it will be available
> +for
> + * future use without needing to recompute the hash.
> + **/
> +void txgbe_atr_compute_perfect_hash(union txgbe_atr_input *input,
> + union txgbe_atr_input *input_mask) {
> + u32 hi_hash_dword, lo_hash_dword, flow_vm_vlan;
> + u32 bucket_hash = 0;
> + __be32 hi_dword = 0;
> + u32 i = 0;
> +
> + /* Apply masks to input data */
> + for (i = 0; i < 11; i++)
> + input->dword_stream[i] &= input_mask->dword_stream[i];
> +
> + /* record the flow_vm_vlan bits as they are a key part to the hash */
> + flow_vm_vlan = ntohl(input->dword_stream[0]);
> +
> + /* generate common hash dword */
> + for (i = 1; i <= 10; i++)
> + hi_dword ^= input->dword_stream[i];
> + hi_hash_dword = ntohl(hi_dword);
> +
> + /* low dword is word swapped version of common */
> + lo_hash_dword = (hi_hash_dword >> 16) | (hi_hash_dword << 16);
> +
> + /* apply flow ID/VM pool/VLAN ID bits to hash words */
> + hi_hash_dword ^= flow_vm_vlan ^ (flow_vm_vlan >> 16);
> +
> + /* Process bits 0 and 16 */
> + TXGBE_COMPUTE_BKT_HASH_ITERATION(0);
> +
> + /* apply flow ID/VM pool/VLAN ID bits to lo hash dword, we had to
> + * delay this because bit 0 of the stream should not be processed
> + * so we do not add the VLAN until after bit 0 was processed
> + */
> + lo_hash_dword ^= flow_vm_vlan ^ (flow_vm_vlan << 16);
> +
> + /* Process remaining 30 bit of the key */
> + for (i = 1; i <= 15; i++)
> + TXGBE_COMPUTE_BKT_HASH_ITERATION(i);
> +
> + /* Limit hash to 13 bits since max bucket count is 8K.
> + * Store result at the end of the input stream.
> + */
> + input->formatted.bkt_hash = (__force __be16)(bucket_hash &
> 0x1FFF); }
> +
> static int txgbe_fdir_check_cmd_complete(struct wx *wx) {
> u32 val;
> @@ -236,6 +301,181 @@ void txgbe_atr(struct wx_ring *ring, struct
> wx_tx_buffer *first, u8 ptype)
> ring->queue_index);
> }
>
> +int txgbe_fdir_set_input_mask(struct wx *wx, union txgbe_atr_input
> +*input_mask) {
> + u32 fdirm = 0, fdirtcpm = 0, flex = 0;
> +
> + /* Program the relevant mask registers. If src/dst_port or
> src/dst_addr
> + * are zero, then assume a full mask for that field. Also assume that
> + * a VLAN of 0 is unspecified, so mask that out as well. L4type
> + * cannot be masked out in this implementation.
> + *
> + * This also assumes IPv4 only. IPv6 masking isn't supported at this
> + * point in time.
> + */
> +
> + /* verify bucket hash is cleared on hash generation */
> + if (input_mask->formatted.bkt_hash)
> + wx_dbg(wx, "bucket hash should always be 0 in mask\n");
> +
> + /* Program FDIRM and verify partial masks */
> + switch (input_mask->formatted.vm_pool & 0x7F) {
> + case 0x0:
> + fdirm |= TXGBE_RDB_FDIR_OTHER_MSK_POOL;
> + break;
> + case 0x7F:
> + break;
> + default:
> + wx_err(wx, "Error on vm pool mask\n");
> + return -EINVAL;
> + }
> +
> + switch (input_mask->formatted.flow_type &
> TXGBE_ATR_L4TYPE_MASK) {
> + case 0x0:
> + fdirm |= TXGBE_RDB_FDIR_OTHER_MSK_L4P;
> + if (input_mask->formatted.dst_port ||
> + input_mask->formatted.src_port) {
> + wx_err(wx, "Error on src/dst port mask\n");
> + return -EINVAL;
> + }
> + break;
> + case TXGBE_ATR_L4TYPE_MASK:
> + break;
> + default:
> + wx_err(wx, "Error on flow type mask\n");
> + return -EINVAL;
> + }
> +
> + /* Now mask VM pool and destination IPv6 - bits 5 and 2 */
> + wr32(wx, TXGBE_RDB_FDIR_OTHER_MSK, fdirm);
> +
> + flex = rd32(wx, TXGBE_RDB_FDIR_FLEX_CFG(0));
> + flex &= ~TXGBE_RDB_FDIR_FLEX_CFG_FIELD0;
> + flex |= (TXGBE_RDB_FDIR_FLEX_CFG_BASE_MAC |
> + TXGBE_RDB_FDIR_FLEX_CFG_OFST(0x6));
> +
> + switch ((__force u16)input_mask->formatted.flex_bytes & 0xFFFF) {
> + case 0x0000:
> + /* Mask Flex Bytes */
> + flex |= TXGBE_RDB_FDIR_FLEX_CFG_MSK;
> + break;
> + case 0xFFFF:
> + break;
> + default:
> + wx_err(wx, "Error on flexible byte mask\n");
> + return -EINVAL;
> + }
> + wr32(wx, TXGBE_RDB_FDIR_FLEX_CFG(0), flex);
> +
> + /* store the TCP/UDP port masks, bit reversed from port layout */
> + fdirtcpm = ntohs(input_mask->formatted.dst_port);
> + fdirtcpm <<= TXGBE_RDB_FDIR_PORT_DESTINATION_SHIFT;
> + fdirtcpm |= ntohs(input_mask->formatted.src_port);
> +
> + /* write both the same so that UDP and TCP use the same mask */
> + wr32(wx, TXGBE_RDB_FDIR_TCP_MSK, ~fdirtcpm);
> + wr32(wx, TXGBE_RDB_FDIR_UDP_MSK, ~fdirtcpm);
> + wr32(wx, TXGBE_RDB_FDIR_SCTP_MSK, ~fdirtcpm);
> +
> + /* store source and destination IP masks (little-enian) */
> + wr32(wx, TXGBE_RDB_FDIR_SA4_MSK, ntohl(~input_mask-
> >formatted.src_ip[0]));
> + wr32(wx, TXGBE_RDB_FDIR_DA4_MSK,
> +ntohl(~input_mask->formatted.dst_ip[0]));
> +
> + return 0;
> +}
> +
> +int txgbe_fdir_write_perfect_filter(struct wx *wx, union txgbe_atr_input
> *input,
> + u16 soft_id, u8 queue)
> +{
> + u32 fdirport, fdirvlan, fdirhash, fdircmd;
> + int err = 0;
> +
> + /* currently IPv6 is not supported, must be programmed with 0 */
> + wr32(wx, TXGBE_RDB_FDIR_IP6(2), ntohl(input-
> >formatted.src_ip[0]));
> + wr32(wx, TXGBE_RDB_FDIR_IP6(1), ntohl(input-
> >formatted.src_ip[1]));
> + wr32(wx, TXGBE_RDB_FDIR_IP6(0), ntohl(input-
> >formatted.src_ip[2]));
> +
> + /* record the source address (little-endian) */
> + wr32(wx, TXGBE_RDB_FDIR_SA, ntohl(input->formatted.src_ip[0]));
> +
> + /* record the first 32 bits of the destination address (little-endian) */
> + wr32(wx, TXGBE_RDB_FDIR_DA, ntohl(input->formatted.dst_ip[0]));
> +
> + /* record source and destination port (little-endian)*/
> + fdirport = ntohs(input->formatted.dst_port);
> + fdirport <<= TXGBE_RDB_FDIR_PORT_DESTINATION_SHIFT;
> + fdirport |= ntohs(input->formatted.src_port);
> + wr32(wx, TXGBE_RDB_FDIR_PORT, fdirport);
> +
> + /* record packet type and flex_bytes (little-endian) */
> + fdirvlan = ntohs(input->formatted.flex_bytes);
> + fdirvlan <<= TXGBE_RDB_FDIR_FLEX_FLEX_SHIFT;
> + fdirvlan |= ntohs(input->formatted.vlan_id);
> + wr32(wx, TXGBE_RDB_FDIR_FLEX, fdirvlan);
> +
> + /* configure FDIRHASH register */
> + fdirhash = (__force u32)input->formatted.bkt_hash |
> + TXGBE_RDB_FDIR_HASH_BUCKET_VALID |
> + TXGBE_RDB_FDIR_HASH_SIG_SW_INDEX(soft_id);
> + wr32(wx, TXGBE_RDB_FDIR_HASH, fdirhash);
> +
> + /* flush all previous writes to make certain registers are
> + * programmed prior to issuing the command
> + */
> + WX_WRITE_FLUSH(wx);
> +
> + /* configure FDIRCMD register */
> + fdircmd = TXGBE_RDB_FDIR_CMD_CMD_ADD_FLOW |
> + TXGBE_RDB_FDIR_CMD_FILTER_UPDATE |
> + TXGBE_RDB_FDIR_CMD_LAST |
> TXGBE_RDB_FDIR_CMD_QUEUE_EN;
> + if (queue == TXGBE_RDB_FDIR_DROP_QUEUE)
> + fdircmd |= TXGBE_RDB_FDIR_CMD_DROP;
> + fdircmd |= TXGBE_RDB_FDIR_CMD_FLOW_TYPE(input-
> >formatted.flow_type);
> + fdircmd |= TXGBE_RDB_FDIR_CMD_RX_QUEUE(queue);
> + fdircmd |= TXGBE_RDB_FDIR_CMD_VT_POOL(input-
> >formatted.vm_pool);
> +
> + wr32(wx, TXGBE_RDB_FDIR_CMD, fdircmd);
> + err = txgbe_fdir_check_cmd_complete(wx);
> + if (err)
> + wx_err(wx, "Flow Director command did not complete!\n");
> +
> + return err;
> +}
> +
> +int txgbe_fdir_erase_perfect_filter(struct wx *wx, union txgbe_atr_input
> *input,
> + u16 soft_id)
> +{
> + u32 fdirhash, fdircmd;
> + int err = 0;
> +
> + /* configure FDIRHASH register */
> + fdirhash = (__force u32)input->formatted.bkt_hash;
> + fdirhash |= TXGBE_RDB_FDIR_HASH_SIG_SW_INDEX(soft_id);
> + wr32(wx, TXGBE_RDB_FDIR_HASH, fdirhash);
> +
> + /* flush hash to HW */
> + WX_WRITE_FLUSH(wx);
> +
> + /* Query if filter is present */
> + wr32(wx, TXGBE_RDB_FDIR_CMD,
> TXGBE_RDB_FDIR_CMD_CMD_QUERY_REM_FILT);
> +
> + err = txgbe_fdir_check_cmd_complete(wx);
> + if (err) {
> + wx_err(wx, "Flow Director command did not complete!\n");
> + return err;
> + }
> +
> + fdircmd = rd32(wx, TXGBE_RDB_FDIR_CMD);
> + /* if filter exists in hardware then remove it */
> + if (fdircmd & TXGBE_RDB_FDIR_CMD_FILTER_VALID) {
> + wr32(wx, TXGBE_RDB_FDIR_HASH, fdirhash);
> + WX_WRITE_FLUSH(wx);
> + wr32(wx, TXGBE_RDB_FDIR_CMD,
> TXGBE_RDB_FDIR_CMD_CMD_REMOVE_FLOW);
> + }
> +
> + return 0;
> +}
> +
> /**
> * txgbe_fdir_enable - Initialize Flow Director control registers
> * @wx: pointer to hardware structure
> @@ -288,12 +528,104 @@ static void txgbe_init_fdir_signature(struct wx
> *wx)
> txgbe_fdir_enable(wx, fdirctrl);
> }
>
> +/**
> + * txgbe_init_fdir_perfect - Initialize Flow Director perfect filters
> + * @wx: pointer to hardware structure
> + **/
> +static void txgbe_init_fdir_perfect(struct wx *wx) {
> + u32 fdirctrl = TXGBE_FDIR_PBALLOC_64K;
> +
> + /* Continue setup of fdirctrl register bits:
> + * Turn perfect match filtering on
> + * Report hash in RSS field of Rx wb descriptor
> + * Initialize the drop queue
> + * Move the flexible bytes to use the ethertype - shift 6 words
> + * Set the maximum length per hash bucket to 0xA filters
> + * Send interrupt when 64 (0x4 * 16) filters are left
> + */
> + fdirctrl |= TXGBE_RDB_FDIR_CTL_PERFECT_MATCH |
> +
> TXGBE_RDB_FDIR_CTL_DROP_Q(TXGBE_RDB_FDIR_DROP_QUEUE) |
> + TXGBE_RDB_FDIR_CTL_HASH_BITS(0xF) |
> + TXGBE_RDB_FDIR_CTL_MAX_LENGTH(0xA) |
> + TXGBE_RDB_FDIR_CTL_FULL_THRESH(4);
> +
> + /* write hashes and fdirctrl register, poll for completion */
> + txgbe_fdir_enable(wx, fdirctrl);
> +}
> +
> +static void txgbe_fdir_filter_restore(struct wx *wx) {
> + struct txgbe_fdir_filter *filter;
> + struct txgbe *txgbe = wx->priv;
> + struct hlist_node *node;
> + u8 queue = 0;
> + int ret = 0;
> +
> + spin_lock(&txgbe->fdir_perfect_lock);
> +
> + if (!hlist_empty(&txgbe->fdir_filter_list))
> + ret = txgbe_fdir_set_input_mask(wx, &txgbe->fdir_mask);
> +
> + if (ret)
> + goto unlock;
> +
> + hlist_for_each_entry_safe(filter, node,
> + &txgbe->fdir_filter_list, fdir_node) {
> + if (filter->action == TXGBE_RDB_FDIR_DROP_QUEUE) {
> + queue = TXGBE_RDB_FDIR_DROP_QUEUE;
> + } else {
> + u32 ring = ethtool_get_flow_spec_ring(filter->action);
> +
> + if (ring >= wx->num_rx_queues) {
> + wx_err(wx, "FDIR restore failed, ring:%u\n",
> ring);
> + continue;
> + }
> +
> + /* Map the ring onto the absolute queue index */
> + queue = wx->rx_ring[ring]->reg_idx;
> + }
> +
> + ret = txgbe_fdir_write_perfect_filter(wx,
> + &filter->filter,
> + filter->sw_idx,
> + queue);
> + if (ret)
> + wx_err(wx, "FDIR restore failed, index:%u\n", filter-
> >sw_idx);
> + }
> +
> +unlock:
> + spin_unlock(&txgbe->fdir_perfect_lock);
> +}
> +
> void txgbe_configure_fdir(struct wx *wx) {
> wx_disable_sec_rx_path(wx);
>
> - if (test_bit(WX_FLAG_FDIR_HASH, wx->flags))
> + if (test_bit(WX_FLAG_FDIR_HASH, wx->flags)) {
> txgbe_init_fdir_signature(wx);
> + } else if (test_bit(WX_FLAG_FDIR_PERFECT, wx->flags)) {
> + txgbe_init_fdir_perfect(wx);
> + txgbe_fdir_filter_restore(wx);
> + }
>
> wx_enable_sec_rx_path(wx);
> }
> +
> +void txgbe_fdir_filter_exit(struct wx *wx) {
> + struct txgbe_fdir_filter *filter;
> + struct txgbe *txgbe = wx->priv;
> + struct hlist_node *node;
> +
> + spin_lock(&txgbe->fdir_perfect_lock);
> +
> + hlist_for_each_entry_safe(filter, node,
> + &txgbe->fdir_filter_list, fdir_node) {
> + hlist_del(&filter->fdir_node);
> + kfree(filter);
> + }
> + txgbe->fdir_filter_count = 0;
> +
> + spin_unlock(&txgbe->fdir_perfect_lock);
> +}
> diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.h
> b/drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.h
> index ed245b66dc2a..ce89b54a44f7 100644
> --- a/drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.h
> +++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.h
> @@ -4,7 +4,15 @@
> #ifndef _TXGBE_FDIR_H_
> #define _TXGBE_FDIR_H_
>
> +void txgbe_atr_compute_perfect_hash(union txgbe_atr_input *input,
> + union txgbe_atr_input *input_mask);
> void txgbe_atr(struct wx_ring *ring, struct wx_tx_buffer *first, u8 ptype);
> +int txgbe_fdir_set_input_mask(struct wx *wx, union txgbe_atr_input
> +*input_mask); int txgbe_fdir_write_perfect_filter(struct wx *wx, union
> txgbe_atr_input *input,
> + u16 soft_id, u8 queue);
> +int txgbe_fdir_erase_perfect_filter(struct wx *wx, union txgbe_atr_input
> *input,
> + u16 soft_id);
> void txgbe_configure_fdir(struct wx *wx);
> +void txgbe_fdir_filter_exit(struct wx *wx);
>
> #endif /* _TXGBE_FDIR_H_ */
> diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
> b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
> index ce49fb725541..41e9ebf11e41 100644
> --- a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
> +++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
> @@ -283,6 +283,12 @@ static int txgbe_sw_init(struct wx *wx)
> return 0;
> }
>
> +static void txgbe_init_fdir(struct txgbe *txgbe) {
> + txgbe->fdir_filter_count = 0;
> + spin_lock_init(&txgbe->fdir_perfect_lock);
> +}
> +
> /**
> * txgbe_open - Called when a network interface is made active
> * @netdev: network interface device structure @@ -361,6 +367,7 @@ static
> int txgbe_close(struct net_device *netdev)
> txgbe_down(wx);
> wx_free_irq(wx);
> wx_free_resources(wx);
> + txgbe_fdir_filter_exit(wx);
> wx_control_hw(wx, false);
>
> return 0;
> @@ -669,6 +676,8 @@ static int txgbe_probe(struct pci_dev *pdev,
> txgbe->wx = wx;
> wx->priv = txgbe;
>
> + txgbe_init_fdir(txgbe);
> +
> err = txgbe_setup_misc_irq(txgbe);
> if (err)
> goto err_release_hw;
> diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_type.h
> b/drivers/net/ethernet/wangxun/txgbe/txgbe_type.h
> index 5b8c55df35fe..63bd034e0f0e 100644
> --- a/drivers/net/ethernet/wangxun/txgbe/txgbe_type.h
> +++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_type.h
> @@ -90,6 +90,7 @@
> #define TXGBE_XPCS_IDA_DATA 0x13004
>
> /********************************* Flow Director
> *****************************/
> +#define TXGBE_RDB_FDIR_DROP_QUEUE 127
> #define TXGBE_RDB_FDIR_CTL 0x19500
> #define TXGBE_RDB_FDIR_CTL_INIT_DONE BIT(3)
> #define TXGBE_RDB_FDIR_CTL_PERFECT_MATCH BIT(4)
> @@ -97,6 +98,13 @@
> #define TXGBE_RDB_FDIR_CTL_HASH_BITS(v) FIELD_PREP(GENMASK(23,
> 20), v)
> #define TXGBE_RDB_FDIR_CTL_MAX_LENGTH(v)
> FIELD_PREP(GENMASK(27, 24), v)
> #define TXGBE_RDB_FDIR_CTL_FULL_THRESH(v)
> FIELD_PREP(GENMASK(31, 28), v)
> +#define TXGBE_RDB_FDIR_IP6(_i) (0x1950C + ((_i) * 4)) /* 0-2 */
> +#define TXGBE_RDB_FDIR_SA 0x19518
> +#define TXGBE_RDB_FDIR_DA 0x1951C
> +#define TXGBE_RDB_FDIR_PORT 0x19520
> +#define TXGBE_RDB_FDIR_PORT_DESTINATION_SHIFT 16
> +#define TXGBE_RDB_FDIR_FLEX 0x19524
> +#define TXGBE_RDB_FDIR_FLEX_FLEX_SHIFT 16
> #define TXGBE_RDB_FDIR_HASH 0x19528
> #define TXGBE_RDB_FDIR_HASH_SIG_SW_INDEX(v)
> FIELD_PREP(GENMASK(31, 16), v)
> #define TXGBE_RDB_FDIR_HASH_BUCKET_VALID BIT(15)
> @@ -114,8 +122,16 @@
> #define TXGBE_RDB_FDIR_CMD_QUEUE_EN BIT(15)
> #define TXGBE_RDB_FDIR_CMD_RX_QUEUE(v)
> FIELD_PREP(GENMASK(22, 16), v)
> #define TXGBE_RDB_FDIR_CMD_VT_POOL(v)
> FIELD_PREP(GENMASK(29, 24), v)
> +#define TXGBE_RDB_FDIR_DA4_MSK 0x1953C
> +#define TXGBE_RDB_FDIR_SA4_MSK 0x19540
> +#define TXGBE_RDB_FDIR_TCP_MSK 0x19544
> +#define TXGBE_RDB_FDIR_UDP_MSK 0x19548
> +#define TXGBE_RDB_FDIR_SCTP_MSK 0x19560
> #define TXGBE_RDB_FDIR_HKEY 0x19568
> #define TXGBE_RDB_FDIR_SKEY 0x1956C
> +#define TXGBE_RDB_FDIR_OTHER_MSK 0x19570
> +#define TXGBE_RDB_FDIR_OTHER_MSK_POOL BIT(2)
> +#define TXGBE_RDB_FDIR_OTHER_MSK_L4P BIT(3)
> #define TXGBE_RDB_FDIR_FLEX_CFG(_i) (0x19580 + ((_i) * 4))
> #define TXGBE_RDB_FDIR_FLEX_CFG_FIELD0 GENMASK(7, 0)
> #define TXGBE_RDB_FDIR_FLEX_CFG_BASE_MAC
> FIELD_PREP(GENMASK(1, 0), 0)
> @@ -230,6 +246,13 @@ enum txgbe_fdir_pballoc_type {
> TXGBE_FDIR_PBALLOC_256K = 3,
> };
>
> +struct txgbe_fdir_filter {
> + struct hlist_node fdir_node;
> + union txgbe_atr_input filter;
> + u16 sw_idx;
> + u16 action;
> +};
> +
> /* TX/RX descriptor defines */
> #define TXGBE_DEFAULT_TXD 512
> #define TXGBE_DEFAULT_TX_WORK 256
> @@ -316,7 +339,10 @@ struct txgbe {
> unsigned int link_irq;
>
> /* flow director */
> + struct hlist_head fdir_filter_list;
> union txgbe_atr_input fdir_mask;
> + int fdir_filter_count;
> + spinlock_t fdir_perfect_lock; /*spinlock for FDIR */
> };
>
> #endif /* _TXGBE_TYPE_H_ */
> --
> 2.27.0
>
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH net-next v2 2/3] net: txgbe: support Flow Director perfect filters
2024-06-05 2:08 ` [PATCH net-next v2 2/3] net: txgbe: support Flow Director perfect filters Jiawen Wu
2024-06-06 8:53 ` Hariprasad Kelam
@ 2024-06-06 20:49 ` Simon Horman
2024-06-18 9:02 ` Jiawen Wu
1 sibling, 1 reply; 12+ messages in thread
From: Simon Horman @ 2024-06-06 20:49 UTC (permalink / raw)
To: Jiawen Wu
Cc: davem, edumazet, kuba, pabeni, linux, andrew, netdev, mengyuanlou
On Wed, Jun 05, 2024 at 10:08:51AM +0800, Jiawen Wu wrote:
> Support the addition and deletion of Flow Director filters.
>
> Supported fields: src-ip, dst-ip, src-port, dst-port
> Supported flow-types: tcp4, udp4, sctp4, ipv4
>
> Signed-off-by: Jiawen Wu <jiawenwu@trustnetic.com>
...
> +static int txgbe_add_ethtool_fdir_entry(struct txgbe *txgbe,
> + struct ethtool_rxnfc *cmd)
> +{
> + struct ethtool_rx_flow_spec *fsp = (struct ethtool_rx_flow_spec *)&cmd->fs;
> + struct txgbe_fdir_filter *input;
> + union txgbe_atr_input mask;
> + struct wx *wx = txgbe->wx;
> + u16 ptype = 0;
> + u8 queue;
> + int err;
> +
> + if (!(test_bit(WX_FLAG_FDIR_PERFECT, wx->flags)))
> + return -EOPNOTSUPP;
> +
> + /* ring_cookie is a masked into a set of queues and txgbe pools or
> + * we use drop index
> + */
> + if (fsp->ring_cookie == RX_CLS_FLOW_DISC) {
> + queue = TXGBE_RDB_FDIR_DROP_QUEUE;
> + } else {
> + u32 ring = ethtool_get_flow_spec_ring(fsp->ring_cookie);
> +
> + if (ring >= wx->num_rx_queues)
> + return -EINVAL;
> +
> + /* Map the ring onto the absolute queue index */
> + queue = wx->rx_ring[ring]->reg_idx;
> + }
> +
> + /* Don't allow indexes to exist outside of available space */
> + if (fsp->location >= ((1024 << TXGBE_FDIR_PBALLOC_64K) - 2)) {
> + wx_err(wx, "Location out of range\n");
> + return -EINVAL;
> + }
> +
> + input = kzalloc(sizeof(*input), GFP_ATOMIC);
> + if (!input)
> + return -ENOMEM;
> +
> + memset(&mask, 0, sizeof(union txgbe_atr_input));
> +
> + /* set SW index */
> + input->sw_idx = fsp->location;
> +
> + /* record flow type */
> + if (txgbe_flowspec_to_flow_type(fsp, &input->filter.formatted.flow_type)) {
> + wx_err(wx, "Unrecognized flow type\n");
> + goto err_out;
> + }
> +
> + mask.formatted.flow_type = TXGBE_ATR_L4TYPE_IPV6_MASK |
> + TXGBE_ATR_L4TYPE_MASK;
> +
> + if (input->filter.formatted.flow_type == TXGBE_ATR_FLOW_TYPE_IPV4)
> + mask.formatted.flow_type &= TXGBE_ATR_L4TYPE_IPV6_MASK;
> +
> + /* Copy input into formatted structures */
> + input->filter.formatted.src_ip[0] = fsp->h_u.tcp_ip4_spec.ip4src;
> + mask.formatted.src_ip[0] = fsp->m_u.tcp_ip4_spec.ip4src;
> + input->filter.formatted.dst_ip[0] = fsp->h_u.tcp_ip4_spec.ip4dst;
> + mask.formatted.dst_ip[0] = fsp->m_u.tcp_ip4_spec.ip4dst;
> + input->filter.formatted.src_port = fsp->h_u.tcp_ip4_spec.psrc;
> + mask.formatted.src_port = fsp->m_u.tcp_ip4_spec.psrc;
> + input->filter.formatted.dst_port = fsp->h_u.tcp_ip4_spec.pdst;
> + mask.formatted.dst_port = fsp->m_u.tcp_ip4_spec.pdst;
> +
> + if (fsp->flow_type & FLOW_EXT) {
> + input->filter.formatted.vm_pool =
> + (unsigned char)ntohl(fsp->h_ext.data[1]);
> + mask.formatted.vm_pool =
> + (unsigned char)ntohl(fsp->m_ext.data[1]);
> + input->filter.formatted.flex_bytes =
> + fsp->h_ext.vlan_etype;
> + mask.formatted.flex_bytes = fsp->m_ext.vlan_etype;
> + }
> +
> + switch (input->filter.formatted.flow_type) {
> + case TXGBE_ATR_FLOW_TYPE_TCPV4:
> + ptype = WX_PTYPE_L2_IPV4_TCP;
> + break;
> + case TXGBE_ATR_FLOW_TYPE_UDPV4:
> + ptype = WX_PTYPE_L2_IPV4_UDP;
> + break;
> + case TXGBE_ATR_FLOW_TYPE_SCTPV4:
> + ptype = WX_PTYPE_L2_IPV4_SCTP;
> + break;
> + case TXGBE_ATR_FLOW_TYPE_IPV4:
> + ptype = WX_PTYPE_L2_IPV4;
> + break;
> + default:
> + break;
> + }
> +
> + input->filter.formatted.vlan_id = htons(ptype);
> + if (mask.formatted.flow_type & TXGBE_ATR_L4TYPE_MASK)
> + mask.formatted.vlan_id = htons(0xFFFF);
> + else
> + mask.formatted.vlan_id = htons(0xFFF8);
> +
> + /* determine if we need to drop or route the packet */
> + if (fsp->ring_cookie == RX_CLS_FLOW_DISC)
> + input->action = TXGBE_RDB_FDIR_DROP_QUEUE;
> + else
> + input->action = fsp->ring_cookie;
> +
> + spin_lock(&txgbe->fdir_perfect_lock);
> +
> + if (hlist_empty(&txgbe->fdir_filter_list)) {
> + /* save mask and program input mask into HW */
> + memcpy(&txgbe->fdir_mask, &mask, sizeof(mask));
> + err = txgbe_fdir_set_input_mask(wx, &mask);
> + if (err)
> + goto err_unlock;
> + } else if (memcmp(&txgbe->fdir_mask, &mask, sizeof(mask))) {
> + wx_err(wx, "Hardware only supports one mask per port. To change the mask you must first delete all the rules.\n");
> + goto err_unlock;
> + }
> +
> + /* apply mask and compute/store hash */
> + txgbe_atr_compute_perfect_hash(&input->filter, &mask);
> +
> + /* check if new entry does not exist on filter list */
> + if (txgbe_match_ethtool_fdir_entry(txgbe, input))
> + goto err_unlock;
> +
> + /* only program filters to hardware if the net device is running, as
> + * we store the filters in the Rx buffer which is not allocated when
> + * the device is down
> + */
> + if (netif_running(wx->netdev)) {
> + err = txgbe_fdir_write_perfect_filter(wx, &input->filter,
> + input->sw_idx, queue);
> + if (err)
> + goto err_unlock;
> + }
> +
> + txgbe_update_ethtool_fdir_entry(txgbe, input, input->sw_idx);
> +
> + spin_unlock(&txgbe->fdir_perfect_lock);
> +
> + return err;
Hi Jiawen Wu,
Smatch flags that err may be used uninitialised here.
I'm unsure if that can occur in practice, but perhaps it
would be nicer to simply return 0 here.
> +err_unlock:
> + spin_unlock(&txgbe->fdir_perfect_lock);
> +err_out:
> + kfree(input);
> + return -EINVAL;
And conversely, perhaps it would be nicer to return err here - ensuring is
it always set. F.e. this would propagate the error code returned by
txgbe_fdir_write_perfect_filter().
> +}
...
^ permalink raw reply [flat|nested] 12+ messages in thread
* RE: [PATCH net-next v2 2/3] net: txgbe: support Flow Director perfect filters
2024-06-06 8:53 ` Hariprasad Kelam
@ 2024-06-18 8:55 ` Jiawen Wu
0 siblings, 0 replies; 12+ messages in thread
From: Jiawen Wu @ 2024-06-18 8:55 UTC (permalink / raw)
To: 'Hariprasad Kelam', davem, edumazet, kuba, pabeni, linux,
horms, andrew, netdev
Cc: mengyuanlou
> > + /* determine if we need to drop or route the packet */
> > + if (fsp->ring_cookie == RX_CLS_FLOW_DISC)
> > + input->action = TXGBE_RDB_FDIR_DROP_QUEUE;
> > + else
> > + input->action = fsp->ring_cookie;
> > +
> > + spin_lock(&txgbe->fdir_perfect_lock);
>
> ethtool ops is already protected with rtnl_lock , which can be confirmed by calling ASSERT_RTNL().
> Why do we need a spin_lock here ?
When driver performs reset function, it needs to restore FDIR configuration, there is no rtnl_lock.
^ permalink raw reply [flat|nested] 12+ messages in thread
* RE: [PATCH net-next v2 2/3] net: txgbe: support Flow Director perfect filters
2024-06-06 20:49 ` Simon Horman
@ 2024-06-18 9:02 ` Jiawen Wu
2024-06-18 9:07 ` Jiawen Wu
0 siblings, 1 reply; 12+ messages in thread
From: Jiawen Wu @ 2024-06-18 9:02 UTC (permalink / raw)
To: 'Simon Horman'
Cc: davem, edumazet, kuba, pabeni, linux, andrew, netdev, mengyuanlou
> > + /* only program filters to hardware if the net device is running, as
> > + * we store the filters in the Rx buffer which is not allocated when
> > + * the device is down
> > + */
> > + if (netif_running(wx->netdev)) {
> > + err = txgbe_fdir_write_perfect_filter(wx, &input->filter,
> > + input->sw_idx, queue);
> > + if (err)
> > + goto err_unlock;
> > + }
> > +
> > + txgbe_update_ethtool_fdir_entry(txgbe, input, input->sw_idx);
> > +
> > + spin_unlock(&txgbe->fdir_perfect_lock);
> > +
> > + return err;
>
> Hi Jiawen Wu,
>
> Smatch flags that err may be used uninitialised here.
> I'm unsure if that can occur in practice, but perhaps it
> would be nicer to simply return 0 here.
>
> > +err_unlock:
> > + spin_unlock(&txgbe->fdir_perfect_lock);
> > +err_out:
> > + kfree(input);
> > + return -EINVAL;
>
> And conversely, perhaps it would be nicer to return err here - ensuring is
> it always set. F.e. this would propagate the error code returned by
> txgbe_fdir_write_perfect_filter().
I think it can be changed to initialize err = 0, and return err in these two places.
^ permalink raw reply [flat|nested] 12+ messages in thread
* RE: [PATCH net-next v2 2/3] net: txgbe: support Flow Director perfect filters
2024-06-18 9:02 ` Jiawen Wu
@ 2024-06-18 9:07 ` Jiawen Wu
0 siblings, 0 replies; 12+ messages in thread
From: Jiawen Wu @ 2024-06-18 9:07 UTC (permalink / raw)
To: 'Simon Horman'
Cc: davem, edumazet, kuba, pabeni, linux, andrew, netdev, mengyuanlou
> > > + /* only program filters to hardware if the net device is running, as
> > > + * we store the filters in the Rx buffer which is not allocated when
> > > + * the device is down
> > > + */
> > > + if (netif_running(wx->netdev)) {
> > > + err = txgbe_fdir_write_perfect_filter(wx, &input->filter,
> > > + input->sw_idx, queue);
> > > + if (err)
> > > + goto err_unlock;
> > > + }
> > > +
> > > + txgbe_update_ethtool_fdir_entry(txgbe, input, input->sw_idx);
> > > +
> > > + spin_unlock(&txgbe->fdir_perfect_lock);
> > > +
> > > + return err;
> >
> > Hi Jiawen Wu,
> >
> > Smatch flags that err may be used uninitialised here.
> > I'm unsure if that can occur in practice, but perhaps it
> > would be nicer to simply return 0 here.
Perhaps initialize err = -EINVAL, and return 0 here.
> >
> > > +err_unlock:
> > > + spin_unlock(&txgbe->fdir_perfect_lock);
> > > +err_out:
> > > + kfree(input);
> > > + return -EINVAL;
> >
> > And conversely, perhaps it would be nicer to return err here - ensuring is
> > it always set. F.e. this would propagate the error code returned by
> > txgbe_fdir_write_perfect_filter().
>
> I think it can be changed to initialize err = 0, and return err in these two places.
Then return err here.
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2024-06-18 9:09 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-06-05 2:08 [PATCH net-next v2 0/3] add flow director for txgbe Jiawen Wu
2024-06-05 2:08 ` [PATCH net-next v2 1/3] net: txgbe: add FDIR ATR support Jiawen Wu
2024-06-05 2:08 ` [PATCH net-next v2 2/3] net: txgbe: support Flow Director perfect filters Jiawen Wu
2024-06-06 8:53 ` Hariprasad Kelam
2024-06-18 8:55 ` Jiawen Wu
2024-06-06 20:49 ` Simon Horman
2024-06-18 9:02 ` Jiawen Wu
2024-06-18 9:07 ` Jiawen Wu
2024-06-05 2:08 ` [PATCH net-next v2 3/3] net: txgbe: add FDIR info to ethtool ops Jiawen Wu
2024-06-05 7:33 ` Hariprasad Kelam
2024-06-06 0:52 ` Jakub Kicinski
2024-06-06 3:04 ` [PATCH net-next v2 0/3] add flow director for txgbe Jakub Kicinski
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.