From 08d61139be0a96ae6a513cc4d00ac39d766194ed Mon Sep 17 00:00:00 2001 From: Jiawen Wu Date: Fri, 18 Dec 2020 17:36:44 +0800 Subject: [PATCH] net/txgbe: support flow director filter add and delete Support add and delete operations on flow director filter. Signed-off-by: Jiawen Wu --- drivers/net/txgbe/base/txgbe_type.h | 11 + drivers/net/txgbe/txgbe_ethdev.c | 1 + drivers/net/txgbe/txgbe_ethdev.h | 18 + drivers/net/txgbe/txgbe_fdir.c | 506 +++++++++++++++++++++++++++- 4 files changed, 535 insertions(+), 1 deletion(-) diff --git a/drivers/net/txgbe/base/txgbe_type.h b/drivers/net/txgbe/base/txgbe_type.h index 633692cd7b..160d5253af 100644 --- a/drivers/net/txgbe/base/txgbe_type.h +++ b/drivers/net/txgbe/base/txgbe_type.h @@ -22,6 +22,7 @@ #define TXGBE_MAX_UTA 128 #define TXGBE_FDIR_INIT_DONE_POLL 10 +#define TXGBE_FDIRCMD_CMD_POLL 10 #define TXGBE_ALIGN 128 /* as intel did */ @@ -71,7 +72,17 @@ enum { #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_L3TYPE_MASK 0x4 +#define TXGBE_ATR_L3TYPE_IPV4 0x0 +#define TXGBE_ATR_L3TYPE_IPV6 0x4 +#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_TUNNEL_MASK 0x10 +#define TXGBE_ATR_TUNNEL_ANY 0x10 /* Flow Director ATR input struct. */ struct txgbe_atr_input { diff --git a/drivers/net/txgbe/txgbe_ethdev.c b/drivers/net/txgbe/txgbe_ethdev.c index 70abc3ec93..b2fbb6ef9d 100644 --- a/drivers/net/txgbe/txgbe_ethdev.c +++ b/drivers/net/txgbe/txgbe_ethdev.c @@ -4916,6 +4916,7 @@ txgbe_filter_restore(struct rte_eth_dev *dev) txgbe_ntuple_filter_restore(dev); txgbe_ethertype_filter_restore(dev); txgbe_syn_filter_restore(dev); + txgbe_fdir_filter_restore(dev); txgbe_l2_tn_filter_restore(dev); return 0; diff --git a/drivers/net/txgbe/txgbe_ethdev.h b/drivers/net/txgbe/txgbe_ethdev.h index b49a9e4756..79e40caf56 100644 --- a/drivers/net/txgbe/txgbe_ethdev.h +++ b/drivers/net/txgbe/txgbe_ethdev.h @@ -93,6 +93,18 @@ struct txgbe_fdir_filter { /* list of fdir filters */ TAILQ_HEAD(txgbe_fdir_filter_list, txgbe_fdir_filter); +struct txgbe_fdir_rule { + struct txgbe_hw_fdir_mask mask; + struct txgbe_atr_input input; /* key of fdir filter */ + bool b_spec; /* If TRUE, input, fdirflags, queue have meaning. */ + bool b_mask; /* If TRUE, mask has meaning. */ + enum rte_fdir_mode mode; /* IP, MAC VLAN, Tunnel */ + uint32_t fdirflags; /* drop or forward */ + uint32_t soft_id; /* an unique value for this rule */ + uint8_t queue; /* assigned rx queue */ + uint8_t flex_bytes_offset; +}; + struct txgbe_hw_fdir_info { struct txgbe_hw_fdir_mask mask; uint8_t flex_bytes_offset; @@ -440,6 +452,10 @@ int txgbe_fdir_configure(struct rte_eth_dev *dev); int txgbe_fdir_set_input_mask(struct rte_eth_dev *dev); int txgbe_fdir_set_flexbytes_offset(struct rte_eth_dev *dev, uint16_t offset); +int txgbe_fdir_filter_program(struct rte_eth_dev *dev, + struct txgbe_fdir_rule *rule, + bool del, bool update); + void txgbe_configure_pb(struct rte_eth_dev *dev); void txgbe_configure_port(struct rte_eth_dev *dev); void txgbe_configure_dcb(struct rte_eth_dev *dev); @@ -457,6 +473,8 @@ int txgbe_pf_host_configure(struct rte_eth_dev *eth_dev); uint32_t txgbe_convert_vm_rx_mask_to_val(uint16_t rx_mask, uint32_t orig_val); +void txgbe_fdir_filter_restore(struct rte_eth_dev *dev); + extern const struct rte_flow_ops txgbe_flow_ops; int txgbe_set_vf_rate_limit(struct rte_eth_dev *dev, uint16_t vf, diff --git a/drivers/net/txgbe/txgbe_fdir.c b/drivers/net/txgbe/txgbe_fdir.c index c319b7f264..2342cf6810 100644 --- a/drivers/net/txgbe/txgbe_fdir.c +++ b/drivers/net/txgbe/txgbe_fdir.c @@ -7,7 +7,7 @@ #include #include #include - +#include #include "txgbe_logs.h" #include "base/txgbe.h" @@ -15,6 +15,7 @@ #define TXGBE_DEFAULT_FLEXBYTES_OFFSET 12 /*default flexbytes offset in bytes*/ #define TXGBE_MAX_FLX_SOURCE_OFF 62 +#define TXGBE_FDIRCMD_CMD_INTERVAL_US 10 #define IPV6_ADDR_TO_MASK(ipaddr, ipv6m) do { \ uint8_t ipv6_addr[16]; \ @@ -432,3 +433,506 @@ txgbe_fdir_configure(struct rte_eth_dev *dev) return 0; } +/* + * Note that the bkt_hash field in the txgbe_atr_input structure is also never + * set. + * + * Compute the hashes for SW ATR + * @stream: input bitstream to compute the hash on + * @key: 32-bit hash key + **/ +static uint32_t +txgbe_atr_compute_hash(struct txgbe_atr_input *atr_input, + uint32_t key) +{ + /* + * The algorithm is as follows: + * Hash[15:0] = Sum { S[n] x K[n+16] }, n = 0...350 + * where Sum {A[n]}, n = 0...n is bitwise XOR of A[0], A[1]...A[n] + * and A[n] x B[n] is bitwise AND between same length strings + * + * K[n] is 16 bits, defined as: + * for n modulo 32 >= 15, K[n] = K[n % 32 : (n % 32) - 15] + * for n modulo 32 < 15, K[n] = + * K[(n % 32:0) | (31:31 - (14 - (n % 32)))] + * + * S[n] is 16 bits, defined as: + * for n >= 15, S[n] = S[n:n - 15] + * for n < 15, S[n] = S[(n:0) | (350:350 - (14 - n))] + * + * To simplify for programming, the algorithm is implemented + * in software this way: + * + * key[31:0], hi_hash_dword[31:0], lo_hash_dword[31:0], hash[15:0] + * + * for (i = 0; i < 352; i+=32) + * hi_hash_dword[31:0] ^= Stream[(i+31):i]; + * + * lo_hash_dword[15:0] ^= Stream[15:0]; + * lo_hash_dword[15:0] ^= hi_hash_dword[31:16]; + * lo_hash_dword[31:16] ^= hi_hash_dword[15:0]; + * + * hi_hash_dword[31:0] ^= Stream[351:320]; + * + * if (key[0]) + * hash[15:0] ^= Stream[15:0]; + * + * for (i = 0; i < 16; i++) { + * if (key[i]) + * hash[15:0] ^= lo_hash_dword[(i+15):i]; + * if (key[i + 16]) + * hash[15:0] ^= hi_hash_dword[(i+15):i]; + * } + * + */ + __be32 *dword_stream = (__be32 *)atr_input; + __be32 common_hash_dword = 0; + u32 hi_hash_dword, lo_hash_dword, flow_pool_ptid; + u32 hash_result = 0; + u8 i; + + /* record the flow_vm_vlan bits as they are a key part to the hash */ + flow_pool_ptid = be_to_cpu32(dword_stream[0]); + + /* generate common hash dword */ + for (i = 1; i <= 10; i++) + common_hash_dword ^= dword_stream[i]; + + hi_hash_dword = be_to_cpu32(common_hash_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/Packet Type) bits to hash words */ + hi_hash_dword ^= flow_pool_ptid ^ (flow_pool_ptid >> 16); + + /* Process bits 0 and 16 */ + if (key & 0x0001) + hash_result ^= lo_hash_dword; + if (key & 0x00010000) + hash_result ^= hi_hash_dword; + + /* + * 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_pool_ptid ^ (flow_pool_ptid << 16); + + /* process the remaining 30 bits in the key 2 bits at a time */ + for (i = 15; i; i--) { + if (key & (0x0001 << i)) + hash_result ^= lo_hash_dword >> i; + if (key & (0x00010000 << i)) + hash_result ^= hi_hash_dword >> i; + } + + return hash_result; +} + +static uint32_t +atr_compute_perfect_hash(struct txgbe_atr_input *input, + enum rte_fdir_pballoc_type pballoc) +{ + uint32_t bucket_hash; + + bucket_hash = txgbe_atr_compute_hash(input, + TXGBE_ATR_BUCKET_HASH_KEY); + if (pballoc == RTE_FDIR_PBALLOC_256K) + bucket_hash &= PERFECT_BUCKET_256KB_HASH_MASK; + else if (pballoc == RTE_FDIR_PBALLOC_128K) + bucket_hash &= PERFECT_BUCKET_128KB_HASH_MASK; + else + bucket_hash &= PERFECT_BUCKET_64KB_HASH_MASK; + + return TXGBE_FDIRPIHASH_BKT(bucket_hash); +} + +/** + * txgbe_fdir_check_cmd_complete - poll to check whether FDIRPICMD is complete + * @hw: pointer to hardware structure + */ +static inline int +txgbe_fdir_check_cmd_complete(struct txgbe_hw *hw, uint32_t *fdircmd) +{ + int i; + + for (i = 0; i < TXGBE_FDIRCMD_CMD_POLL; i++) { + *fdircmd = rd32(hw, TXGBE_FDIRPICMD); + if (!(*fdircmd & TXGBE_FDIRPICMD_OP_MASK)) + return 0; + rte_delay_us(TXGBE_FDIRCMD_CMD_INTERVAL_US); + } + + return -ETIMEDOUT; +} + +/* + * Calculate the hash value needed for signature-match filters. In the FreeBSD + * driver, this is done by the optimised function + * txgbe_atr_compute_sig_hash_raptor(). However that can't be used here as it + * doesn't support calculating a hash for an IPv6 filter. + */ +static uint32_t +atr_compute_signature_hash(struct txgbe_atr_input *input, + enum rte_fdir_pballoc_type pballoc) +{ + uint32_t bucket_hash, sig_hash; + + bucket_hash = txgbe_atr_compute_hash(input, + TXGBE_ATR_BUCKET_HASH_KEY); + if (pballoc == RTE_FDIR_PBALLOC_256K) + bucket_hash &= SIG_BUCKET_256KB_HASH_MASK; + else if (pballoc == RTE_FDIR_PBALLOC_128K) + bucket_hash &= SIG_BUCKET_128KB_HASH_MASK; + else + bucket_hash &= SIG_BUCKET_64KB_HASH_MASK; + + sig_hash = txgbe_atr_compute_hash(input, + TXGBE_ATR_SIGNATURE_HASH_KEY); + + return TXGBE_FDIRPIHASH_SIG(sig_hash) | + TXGBE_FDIRPIHASH_BKT(bucket_hash); +} + +/** + * With the ability to set extra flags in FDIRPICMD register + * added, and IPv6 support also added. The hash value is also pre-calculated + * as the pballoc value is needed to do it. + */ +static int +fdir_write_perfect_filter(struct txgbe_hw *hw, + struct txgbe_atr_input *input, uint8_t queue, + uint32_t fdircmd, uint32_t fdirhash, + enum rte_fdir_mode mode) +{ + uint32_t fdirport, fdirflex; + int err = 0; + + UNREFERENCED_PARAMETER(mode); + + /* record the IPv4 address (little-endian) + * can not use wr32. + */ + wr32(hw, TXGBE_FDIRPISIP4, be_to_le32(input->src_ip[0])); + wr32(hw, TXGBE_FDIRPIDIP4, be_to_le32(input->dst_ip[0])); + + /* record source and destination port (little-endian)*/ + fdirport = TXGBE_FDIRPIPORT_DST(be_to_le16(input->dst_port)); + fdirport |= TXGBE_FDIRPIPORT_SRC(be_to_le16(input->src_port)); + wr32(hw, TXGBE_FDIRPIPORT, fdirport); + + /* record pkt_type (little-endian) and flex_bytes(big-endian) */ + fdirflex = TXGBE_FDIRPIFLEX_FLEX(be_to_npu16(input->flex_bytes)); + fdirflex |= TXGBE_FDIRPIFLEX_PTYPE(be_to_le16(input->pkt_type)); + wr32(hw, TXGBE_FDIRPIFLEX, fdirflex); + + /* configure FDIRHASH register */ + fdirhash |= TXGBE_FDIRPIHASH_VLD; + wr32(hw, TXGBE_FDIRPIHASH, fdirhash); + + /* + * flush all previous writes to make certain registers are + * programmed prior to issuing the command + */ + txgbe_flush(hw); + + /* configure FDIRPICMD register */ + fdircmd |= TXGBE_FDIRPICMD_OP_ADD | + TXGBE_FDIRPICMD_UPD | + TXGBE_FDIRPICMD_LAST | + TXGBE_FDIRPICMD_QPENA; + fdircmd |= TXGBE_FDIRPICMD_FT(input->flow_type); + fdircmd |= TXGBE_FDIRPICMD_QP(queue); + fdircmd |= TXGBE_FDIRPICMD_POOL(input->vm_pool); + + wr32(hw, TXGBE_FDIRPICMD, fdircmd); + + PMD_DRV_LOG(DEBUG, "Rx Queue=%x hash=%x", queue, fdirhash); + + err = txgbe_fdir_check_cmd_complete(hw, &fdircmd); + if (err < 0) + PMD_DRV_LOG(ERR, "Timeout writing flow director filter."); + + return err; +} + +/** + * This function supports setting extra fields in the FDIRPICMD register, and + * removes the code that was verifying the flow_type field. According to the + * documentation, a flow type of 00 (i.e. not TCP, UDP, or SCTP) is not + * supported, however it appears to work ok... + * Adds a signature hash filter + * @hw: pointer to hardware structure + * @input: unique input dword + * @queue: queue index to direct traffic to + * @fdircmd: any extra flags to set in fdircmd register + * @fdirhash: pre-calculated hash value for the filter + **/ +static int +fdir_add_signature_filter(struct txgbe_hw *hw, + struct txgbe_atr_input *input, uint8_t queue, uint32_t fdircmd, + uint32_t fdirhash) +{ + int err = 0; + + PMD_INIT_FUNC_TRACE(); + + /* configure FDIRPICMD register */ + fdircmd |= TXGBE_FDIRPICMD_OP_ADD | + TXGBE_FDIRPICMD_UPD | + TXGBE_FDIRPICMD_LAST | + TXGBE_FDIRPICMD_QPENA; + fdircmd |= TXGBE_FDIRPICMD_FT(input->flow_type); + fdircmd |= TXGBE_FDIRPICMD_QP(queue); + + fdirhash |= TXGBE_FDIRPIHASH_VLD; + wr32(hw, TXGBE_FDIRPIHASH, fdirhash); + wr32(hw, TXGBE_FDIRPICMD, fdircmd); + + PMD_DRV_LOG(DEBUG, "Rx Queue=%x hash=%x", queue, fdirhash); + + err = txgbe_fdir_check_cmd_complete(hw, &fdircmd); + if (err < 0) + PMD_DRV_LOG(ERR, "Timeout writing flow director filter."); + + return err; +} + +/* + * This is modified to take in the hash as a parameter so that + * it can be used for removing signature and perfect filters. + */ +static int +fdir_erase_filter_raptor(struct txgbe_hw *hw, uint32_t fdirhash) +{ + uint32_t fdircmd = 0; + int err = 0; + + wr32(hw, TXGBE_FDIRPIHASH, fdirhash); + + /* flush hash to HW */ + txgbe_flush(hw); + + /* Query if filter is present */ + wr32(hw, TXGBE_FDIRPICMD, TXGBE_FDIRPICMD_OP_QRY); + + err = txgbe_fdir_check_cmd_complete(hw, &fdircmd); + if (err < 0) { + PMD_INIT_LOG(ERR, "Timeout querying for flow director filter."); + return err; + } + + /* if filter exists in hardware then remove it */ + if (fdircmd & TXGBE_FDIRPICMD_VLD) { + wr32(hw, TXGBE_FDIRPIHASH, fdirhash); + txgbe_flush(hw); + wr32(hw, TXGBE_FDIRPICMD, TXGBE_FDIRPICMD_OP_REM); + } + + err = txgbe_fdir_check_cmd_complete(hw, &fdircmd); + if (err < 0) + PMD_INIT_LOG(ERR, "Timeout erasing flow director filter."); + + return err; +} + +static inline struct txgbe_fdir_filter * +txgbe_fdir_filter_lookup(struct txgbe_hw_fdir_info *fdir_info, + struct txgbe_atr_input *input) +{ + int ret; + + ret = rte_hash_lookup(fdir_info->hash_handle, (const void *)input); + if (ret < 0) + return NULL; + + return fdir_info->hash_map[ret]; +} + +static inline int +txgbe_insert_fdir_filter(struct txgbe_hw_fdir_info *fdir_info, + struct txgbe_fdir_filter *fdir_filter) +{ + int ret; + + ret = rte_hash_add_key(fdir_info->hash_handle, &fdir_filter->input); + if (ret < 0) { + PMD_DRV_LOG(ERR, + "Failed to insert fdir filter to hash table %d!", + ret); + return ret; + } + + fdir_info->hash_map[ret] = fdir_filter; + + TAILQ_INSERT_TAIL(&fdir_info->fdir_list, fdir_filter, entries); + + return 0; +} + +static inline int +txgbe_remove_fdir_filter(struct txgbe_hw_fdir_info *fdir_info, + struct txgbe_atr_input *input) +{ + int ret; + struct txgbe_fdir_filter *fdir_filter; + + ret = rte_hash_del_key(fdir_info->hash_handle, input); + if (ret < 0) + return ret; + + fdir_filter = fdir_info->hash_map[ret]; + fdir_info->hash_map[ret] = NULL; + + TAILQ_REMOVE(&fdir_info->fdir_list, fdir_filter, entries); + rte_free(fdir_filter); + + return 0; +} + +int +txgbe_fdir_filter_program(struct rte_eth_dev *dev, + struct txgbe_fdir_rule *rule, + bool del, + bool update) +{ + struct txgbe_hw *hw = TXGBE_DEV_HW(dev); + uint32_t fdirhash; + uint8_t queue; + bool is_perfect = FALSE; + int err; + struct txgbe_hw_fdir_info *info = TXGBE_DEV_FDIR(dev); + enum rte_fdir_mode fdir_mode = dev->data->dev_conf.fdir_conf.mode; + struct txgbe_fdir_filter *node; + + if (fdir_mode == RTE_FDIR_MODE_NONE || + fdir_mode != rule->mode) + return -ENOTSUP; + + if (fdir_mode >= RTE_FDIR_MODE_PERFECT) + is_perfect = TRUE; + + if (is_perfect) { + if (rule->input.flow_type & TXGBE_ATR_L3TYPE_IPV6) { + PMD_DRV_LOG(ERR, "IPv6 is not supported in" + " perfect mode!"); + return -ENOTSUP; + } + fdirhash = atr_compute_perfect_hash(&rule->input, + dev->data->dev_conf.fdir_conf.pballoc); + fdirhash |= TXGBE_FDIRPIHASH_IDX(rule->soft_id); + } else { + fdirhash = atr_compute_signature_hash(&rule->input, + dev->data->dev_conf.fdir_conf.pballoc); + } + + if (del) { + err = txgbe_remove_fdir_filter(info, &rule->input); + if (err < 0) { + PMD_DRV_LOG(ERR, + "No such fdir filter to delete %d!", err); + return err; + } + + err = fdir_erase_filter_raptor(hw, fdirhash); + if (err < 0) + PMD_DRV_LOG(ERR, "Fail to delete FDIR filter!"); + else + PMD_DRV_LOG(DEBUG, "Success to delete FDIR filter!"); + return err; + } + + /* add or update an fdir filter*/ + if (rule->fdirflags & TXGBE_FDIRPICMD_DROP) { + if (!is_perfect) { + PMD_DRV_LOG(ERR, "Drop option is not supported in" + " signature mode."); + return -EINVAL; + } + queue = dev->data->dev_conf.fdir_conf.drop_queue; + } else if (rule->queue < TXGBE_MAX_RX_QUEUE_NUM) { + queue = rule->queue; + } else { + return -EINVAL; + } + + node = txgbe_fdir_filter_lookup(info, &rule->input); + if (node) { + if (!update) { + PMD_DRV_LOG(ERR, "Conflict with existing fdir filter!"); + return -EINVAL; + } + node->fdirflags = rule->fdirflags; + node->fdirhash = fdirhash; + node->queue = queue; + } else { + node = rte_zmalloc("txgbe_fdir", + sizeof(struct txgbe_fdir_filter), 0); + if (!node) + return -ENOMEM; + rte_memcpy(&node->input, &rule->input, + sizeof(struct txgbe_atr_input)); + node->fdirflags = rule->fdirflags; + node->fdirhash = fdirhash; + node->queue = queue; + + err = txgbe_insert_fdir_filter(info, node); + if (err < 0) { + rte_free(node); + return err; + } + } + + if (is_perfect) + err = fdir_write_perfect_filter(hw, &node->input, + node->queue, node->fdirflags, + node->fdirhash, fdir_mode); + else + err = fdir_add_signature_filter(hw, &node->input, + node->queue, node->fdirflags, + node->fdirhash); + if (err < 0) { + PMD_DRV_LOG(ERR, "Fail to add FDIR filter!"); + txgbe_remove_fdir_filter(info, &rule->input); + } else { + PMD_DRV_LOG(DEBUG, "Success to add FDIR filter"); + } + + return err; +} + +/* restore flow director filter */ +void +txgbe_fdir_filter_restore(struct rte_eth_dev *dev) +{ + struct txgbe_hw *hw = TXGBE_DEV_HW(dev); + struct txgbe_hw_fdir_info *fdir_info = TXGBE_DEV_FDIR(dev); + struct txgbe_fdir_filter *node; + bool is_perfect = FALSE; + enum rte_fdir_mode fdir_mode = dev->data->dev_conf.fdir_conf.mode; + + if (fdir_mode >= RTE_FDIR_MODE_PERFECT && + fdir_mode <= RTE_FDIR_MODE_PERFECT_TUNNEL) + is_perfect = TRUE; + + if (is_perfect) { + TAILQ_FOREACH(node, &fdir_info->fdir_list, entries) { + (void)fdir_write_perfect_filter(hw, + &node->input, + node->queue, + node->fdirflags, + node->fdirhash, + fdir_mode); + } + } else { + TAILQ_FOREACH(node, &fdir_info->fdir_list, entries) { + (void)fdir_add_signature_filter(hw, + &node->input, + node->queue, + node->fdirflags, + node->fdirhash); + } + } +} + -- 2.20.1