[PATCH net-next 5/6] net: hns3: debugfs support for dumping fd rules

From: Jijie Shao

Date: Mon May 18 2026 - 05:41:34 EST


Currently, the tc tool only supports adding and deleting rules from
the driver but does not support querying rules from the driver.

This patch adds a rule dump file in debugfs to check whether the driver's
configuration matches the configuration issued by tc flow.

Signed-off-by: Jijie Shao <shaojijie@xxxxxxxxxx>
---
drivers/net/ethernet/hisilicon/hns3/hnae3.h | 1 +
.../ethernet/hisilicon/hns3/hns3_debugfs.c | 6 +
.../hisilicon/hns3/hns3pf/hclge_debugfs.c | 156 ++++++++++++++++++
3 files changed, 163 insertions(+)

diff --git a/drivers/net/ethernet/hisilicon/hns3/hnae3.h b/drivers/net/ethernet/hisilicon/hns3/hnae3.h
index a724935b655a..a8798eecd9fb 100644
--- a/drivers/net/ethernet/hisilicon/hns3/hnae3.h
+++ b/drivers/net/ethernet/hisilicon/hns3/hnae3.h
@@ -331,6 +331,7 @@ enum hnae3_dbg_cmd {
HNAE3_DBG_CMD_TX_QUEUE_INFO,
HNAE3_DBG_CMD_FD_TCAM,
HNAE3_DBG_CMD_FD_COUNTER,
+ HNAE3_DBG_CMD_FD_RULE,
HNAE3_DBG_CMD_MAC_TNL_STATUS,
HNAE3_DBG_CMD_SERV_INFO,
HNAE3_DBG_CMD_UMV_INFO,
diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3_debugfs.c b/drivers/net/ethernet/hisilicon/hns3/hns3_debugfs.c
index 4cce4f4ba6b0..1347edac7699 100644
--- a/drivers/net/ethernet/hisilicon/hns3/hns3_debugfs.c
+++ b/drivers/net/ethernet/hisilicon/hns3/hns3_debugfs.c
@@ -273,6 +273,12 @@ static struct hns3_dbg_cmd_info hns3_dbg_cmd[] = {
.dentry = HNS3_DBG_DENTRY_FD,
.init = hns3_dbg_common_init_t2,
},
+ {
+ .name = "fd_rule",
+ .cmd = HNAE3_DBG_CMD_FD_RULE,
+ .dentry = HNS3_DBG_DENTRY_FD,
+ .init = hns3_dbg_common_init_t2,
+ },
{
.name = "service_task_info",
.cmd = HNAE3_DBG_CMD_SERV_INFO,
diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_debugfs.c b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_debugfs.c
index 3dab3a271aa6..d5009809420c 100644
--- a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_debugfs.c
+++ b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_debugfs.c
@@ -2121,6 +2121,158 @@ static int hclge_dbg_dump_fd_counter(struct seq_file *s, void *data)
return 0;
}

+static void hclge_fd_dump_u32(struct seq_file *s,
+ struct hclge_fd_rule *rule,
+ u32 type, const char *name,
+ const char *fmt, u32 key, u32 mask)
+{
+ if (rule->unused_tuple & BIT(type))
+ return;
+
+ seq_printf(s, "\t\t%s: ", name);
+ seq_printf(s, fmt, key);
+ seq_putc(s, '\n');
+
+ seq_printf(s, "\t\t%s_mask: ", name);
+ seq_printf(s, fmt, mask);
+ seq_putc(s, '\n');
+}
+
+static void hclge_fd_dump_ptr(struct seq_file *s,
+ struct hclge_fd_rule *rule,
+ u32 type, const char *name,
+ const char *fmt,
+ const void *key, const void *mask)
+{
+ if (rule->unused_tuple & BIT(type))
+ return;
+
+ seq_printf(s, "\t\t%s: ", name);
+ seq_printf(s, fmt, key);
+ seq_putc(s, '\n');
+
+ seq_printf(s, "\t\t%s_mask: ", name);
+ seq_printf(s, fmt, mask);
+ seq_putc(s, '\n');
+}
+
+#define HCLGE_IS_IPV4(ip) ({ \
+ typeof(ip) _ip = (ip); \
+ (!((_ip)[0]) && !((_ip)[1]) && !((_ip)[2]) && (_ip)[IPV4_INDEX]); })
+
+static void hclge_fd_dump_ip(struct seq_file *s,
+ struct hclge_fd_rule *rule,
+ u32 type, const char *name,
+ const u32 *ip, const u32 *mask)
+{
+ u32 be_mask[IPV6_ADDR_WORDS];
+ u32 be_ip[IPV6_ADDR_WORDS];
+
+ if (rule->unused_tuple & BIT(type))
+ return;
+
+ ipv6_addr_cpu_to_be32(be_ip, ip);
+ ipv6_addr_cpu_to_be32(be_mask, mask);
+
+ if (HCLGE_IS_IPV4(ip))
+ hclge_fd_dump_ptr(s, rule, type, name, "%pI4",
+ &be_ip[IPV4_INDEX], &be_mask[IPV4_INDEX]);
+ else
+ hclge_fd_dump_ptr(s, rule, type, name, "%pI6",
+ be_ip, be_mask);
+}
+
+static void hclge_dbg_dump_fd_tuples(struct seq_file *s,
+ struct hclge_fd_rule *rule)
+{
+ seq_puts(s, "\trule tuples:\n");
+
+ hclge_fd_dump_ptr(s, rule, INNER_DST_MAC, "dst_mac", "%pM",
+ rule->tuples.dst_mac, rule->tuples_mask.dst_mac);
+ hclge_fd_dump_ptr(s, rule, INNER_SRC_MAC, "src_mac", "%pM",
+ rule->tuples.src_mac, rule->tuples_mask.src_mac);
+ hclge_fd_dump_u32(s, rule, INNER_VLAN_TAG_FST, "vlan_tag", "0x%04x",
+ rule->tuples.vlan_tag1, rule->tuples_mask.vlan_tag1);
+ hclge_fd_dump_u32(s, rule, INNER_ETH_TYPE, "ether_proto", "0x%04x",
+ rule->tuples.ether_proto,
+ rule->tuples_mask.ether_proto);
+ hclge_fd_dump_u32(s, rule, INNER_L2_RSV, "l2_user_def", "0x%04x",
+ rule->tuples.l2_user_def,
+ rule->tuples_mask.l2_user_def);
+ hclge_fd_dump_ip(s, rule, INNER_SRC_IP, "src_ip",
+ rule->tuples.src_ip, rule->tuples_mask.src_ip);
+ hclge_fd_dump_ip(s, rule, INNER_DST_IP, "dst_ip",
+ rule->tuples.dst_ip, rule->tuples_mask.dst_ip);
+ hclge_fd_dump_u32(s, rule, INNER_IP_TOS, "ip_tos", "0x%02x",
+ rule->tuples.ip_tos, rule->tuples_mask.ip_tos);
+ hclge_fd_dump_u32(s, rule, INNER_IP_PROTO, "ip_proto", "0x%02x",
+ rule->tuples.ip_proto, rule->tuples_mask.ip_proto);
+ hclge_fd_dump_u32(s, rule, INNER_L3_RSV, "l3_user_def", "0x%04x",
+ rule->tuples.l3_user_def,
+ rule->tuples_mask.l3_user_def);
+ hclge_fd_dump_u32(s, rule, INNER_SRC_PORT, "src_port", "0x%04x",
+ rule->tuples.src_port, rule->tuples_mask.src_port);
+ hclge_fd_dump_u32(s, rule, INNER_DST_PORT, "dst_port", "0x%04x",
+ rule->tuples.dst_port, rule->tuples_mask.dst_port);
+ hclge_fd_dump_u32(s, rule, INNER_L4_RSV, "l4_user_def", "0x%08x",
+ rule->tuples.l4_user_def,
+ rule->tuples_mask.l4_user_def);
+ hclge_fd_dump_u32(s, rule, OUTER_TUN_VNI, "outer_tun_vni", "0x%06x",
+ rule->tuples.outer_tun_vni,
+ rule->tuples_mask.outer_tun_vni);
+}
+
+static void hclge_dbg_dump_fd_action(struct seq_file *s,
+ struct hclge_fd_rule *rule)
+{
+ static const char * const action_str[] = {
+ [HCLGE_FD_ACTION_SELECT_QUEUE] = "select_queue",
+ [HCLGE_FD_ACTION_DROP_PACKET] = "drop_packet",
+ [HCLGE_FD_ACTION_SELECT_TC] = "select_tc",
+ };
+
+ seq_printf(s, "\taction: %s\n", action_str[rule->action]);
+
+ if (rule->action == HCLGE_FD_ACTION_SELECT_QUEUE)
+ seq_printf(s, "\tqueue_id: %u\n", rule->queue_id);
+ else if (rule->action == HCLGE_FD_ACTION_SELECT_TC)
+ seq_printf(s, "\ttc: %u\n", rule->cls_flower.tc);
+}
+
+static void hclge_dbg_dump_fd_type(struct hclge_dev *hdev, struct seq_file *s)
+{
+ static const char *const rule_type_str[] = {
+ [HCLGE_FD_RULE_NONE] = "none",
+ [HCLGE_FD_ARFS_ACTIVE] = "arfs",
+ [HCLGE_FD_EP_ACTIVE] = "ep",
+ [HCLGE_FD_TC_FLOWER_ACTIVE] = "tc_flow"
+ };
+
+ seq_printf(s, "fd type: %s\n", rule_type_str[hdev->fd_active_type]);
+}
+
+static int hclge_dbg_dump_fd_rule(struct seq_file *s, void *data)
+{
+ struct hclge_dev *hdev = hclge_seq_file_to_hdev(s);
+ struct hclge_fd_rule *rule;
+
+ hclge_dbg_dump_fd_type(hdev, s);
+
+ spin_lock_bh(&hdev->fd_rule_lock);
+ hlist_for_each_entry(rule, &hdev->fd_rule_list, rule_node) {
+ if (rule->state != HCLGE_FD_ACTIVE)
+ continue;
+
+ seq_printf(s, "location: %u\n", rule->location);
+ seq_printf(s, "vport_id: %u\n", rule->vf_id);
+ hclge_dbg_dump_fd_action(s, rule);
+ hclge_dbg_dump_fd_tuples(s, rule);
+ }
+ spin_unlock_bh(&hdev->fd_rule_lock);
+
+ return 0;
+}
+
static const struct hclge_dbg_status_dfx_info hclge_dbg_rst_info[] = {
{HCLGE_MISC_VECTOR_REG_BASE, "vector0 interrupt enable status"},
{HCLGE_MISC_RESET_STS_REG, "reset interrupt source"},
@@ -2913,6 +3065,10 @@ static const struct hclge_dbg_func hclge_dbg_cmd_func[] = {
.cmd = HNAE3_DBG_CMD_FD_TCAM,
.dbg_read_func = hclge_dbg_dump_fd_tcam,
},
+ {
+ .cmd = HNAE3_DBG_CMD_FD_RULE,
+ .dbg_read_func = hclge_dbg_dump_fd_rule,
+ },
{
.cmd = HNAE3_DBG_CMD_MAC_TNL_STATUS,
.dbg_read_func = hclge_dbg_dump_mac_tnl_status,
--
2.33.0