From patchwork Wed Nov 11 06:49:14 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jiawen Wu X-Patchwork-Id: 83957 X-Patchwork-Delegate: ferruh.yigit@amd.com Return-Path: X-Original-To: patchwork@inbox.dpdk.org Delivered-To: patchwork@inbox.dpdk.org Received: from dpdk.org (dpdk.org [92.243.14.124]) by inbox.dpdk.org (Postfix) with ESMTP id 03237A09D2; Wed, 11 Nov 2020 07:52:39 +0100 (CET) Received: from [92.243.14.124] (localhost [127.0.0.1]) by dpdk.org (Postfix) with ESMTP id 8212ABC66; Wed, 11 Nov 2020 07:47:56 +0100 (CET) Received: from smtpbgeu1.qq.com (smtpbgeu1.qq.com [52.59.177.22]) by dpdk.org (Postfix) with ESMTP id 9A1936947 for ; Wed, 11 Nov 2020 07:47:45 +0100 (CET) X-QQ-mid: bizesmtp27t1605077260t37v2g24 Received: from localhost.localdomain.com (unknown [183.129.236.74]) by esmtp10.qq.com (ESMTP) with id ; Wed, 11 Nov 2020 14:47:39 +0800 (CST) X-QQ-SSF: 01400000000000C0C000B00A0000000 X-QQ-FEAT: mJep2VbaKxYjU00PrJ4XjeluoURoSIKYIizf2URkGTaw3zujzpymGV38PeC2m DBYR6JGUciS5A0vh2eu5FbAJUgjS7H4xIUG03xiONaa6G2LW4C8X4E6kYQ9QuQqv1J6fIsB wTb3kxajLjY/WpMXr7qtCG9kF1WoAncKQKeuBv5olh8sKkI2t3omKaYFS1t+YpFLRSp52gP StsinVIzrHhM5XxMbWZW8X1m9Zocmp6spujcXpb9I+IzicKormFJ/j/kId2SwPpAkvt9m91 9IB/njrQVfL3sANxZ9od1gzuGdpxsdHIfDYOhiO2Y50WaSaqjj5OY2VfDZMeqQOZ5CHVMr8 gyiLunG5GJx1ZzDHrcUhVxBkXnIBw== X-QQ-GoodBg: 2 From: Jiawen Wu To: dev@dpdk.org Cc: Jiawen Wu Date: Wed, 11 Nov 2020 14:49:14 +0800 Message-Id: <20201111064936.768604-16-jiawenwu@trustnetic.com> X-Mailer: git-send-email 2.18.4 In-Reply-To: <20201111064936.768604-1-jiawenwu@trustnetic.com> References: <20201111064936.768604-1-jiawenwu@trustnetic.com> X-QQ-SENDSIZE: 520 Feedback-ID: bizesmtp:trustnetic.com:qybgforeign:qybgforeign7 X-QQ-Bgrelay: 1 Subject: [dpdk-dev] [PATCH v2 15/37] net/txgbe: support FDIR add and delete operations X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org Sender: "dev" Support add and delete operations on flow director. Signed-off-by: Jiawen Wu --- drivers/net/txgbe/base/txgbe_type.h | 11 + drivers/net/txgbe/txgbe_ethdev.h | 16 + drivers/net/txgbe/txgbe_fdir.c | 472 +++++++++++++++++++++++++++- 3 files changed, 498 insertions(+), 1 deletion(-) diff --git a/drivers/net/txgbe/base/txgbe_type.h b/drivers/net/txgbe/base/txgbe_type.h index 633692cd7..160d5253a 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.h b/drivers/net/txgbe/txgbe_ethdev.h index cd24efeea..ad72e1750 100644 --- a/drivers/net/txgbe/txgbe_ethdev.h +++ b/drivers/net/txgbe/txgbe_ethdev.h @@ -91,6 +91,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; @@ -436,6 +448,10 @@ void txgbe_set_ivar_map(struct txgbe_hw *hw, int8_t direction, */ int txgbe_fdir_configure(struct rte_eth_dev *dev); int txgbe_fdir_set_input_mask(struct rte_eth_dev *dev); +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); diff --git a/drivers/net/txgbe/txgbe_fdir.c b/drivers/net/txgbe/txgbe_fdir.c index df6125d4a..d38e21e9e 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]; \ @@ -405,3 +406,472 @@ 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; +} +