* [PATCH v2 net-next 1/9] octeontx2-af: switch: Add AF to switch mbox and skeleton files
From: Ratheesh Kannoth @ 2026-07-02 4:50 UTC (permalink / raw)
To: linux-kernel, netdev
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, sgoutham,
Ratheesh Kannoth
In-Reply-To: <20260702045026.2914748-1-rkannoth@marvell.com>
The Marvell switch hardware runs on a Linux OS. This OS receives
various messages, which are parsed to create flow rules that can be
installed on HW. The switch is capable of accelerating both L2 and
L3 flows.
This commit adds various mailbox messages used by the Linux OS
(on arm64) to send events to the switch hardware.
fdb messages: Linux bridge FDB messages
fib messages: Linux routing table messages
status messages: Packet status updates sent to Host
Linux to keep flows active
for connection-tracked flows.
Signed-off-by: Ratheesh Kannoth <rkannoth@marvell.com>
---
.../ethernet/marvell/octeontx2/af/Makefile | 3 +-
.../net/ethernet/marvell/octeontx2/af/mbox.h | 104 ++++++++++++++++++
.../marvell/octeontx2/af/switch/rvu_sw_fl.c | 21 ++++
.../marvell/octeontx2/af/switch/rvu_sw_fl.h | 11 ++
.../marvell/octeontx2/af/switch/rvu_sw_l2.c | 14 +++
.../marvell/octeontx2/af/switch/rvu_sw_l2.h | 11 ++
.../marvell/octeontx2/af/switch/rvu_sw_l3.c | 14 +++
.../marvell/octeontx2/af/switch/rvu_sw_l3.h | 11 ++
8 files changed, 188 insertions(+), 1 deletion(-)
create mode 100644 drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_fl.c
create mode 100644 drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_fl.h
create mode 100644 drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l2.c
create mode 100644 drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l2.h
create mode 100644 drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l3.c
create mode 100644 drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l3.h
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/Makefile b/drivers/net/ethernet/marvell/octeontx2/af/Makefile
index 91b7d6e96a61..82dd387308c9 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/Makefile
+++ b/drivers/net/ethernet/marvell/octeontx2/af/Makefile
@@ -3,7 +3,7 @@
# Makefile for Marvell's RVU Admin Function driver
#
-ccflags-y += -I$(src)
+ccflags-y += -I$(src) -I$(src)/switch/
obj-$(CONFIG_OCTEONTX2_MBOX) += rvu_mbox.o
obj-$(CONFIG_OCTEONTX2_AF) += rvu_af.o
@@ -12,5 +12,6 @@ rvu_af-y := cgx.o rvu.o rvu_cgx.o rvu_npa.o rvu_nix.o \
rvu_reg.o rvu_npc.o rvu_debugfs.o ptp.o rvu_npc_fs.o \
rvu_cpt.o rvu_devlink.o rpm.o rvu_cn10k.o rvu_switch.o \
rvu_sdp.o rvu_npc_hash.o mcs.o mcs_rvu_if.o mcs_cnf10kb.o \
+ switch/rvu_sw_l2.o switch/rvu_sw_l3.o switch/rvu_sw_fl.o\
rvu_rep.o cn20k/mbox_init.o cn20k/nix.o cn20k/debugfs.o \
cn20k/npa.o cn20k/npc.o
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/mbox.h b/drivers/net/ethernet/marvell/octeontx2/af/mbox.h
index 714e47f68d93..821895a7014e 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/mbox.h
+++ b/drivers/net/ethernet/marvell/octeontx2/af/mbox.h
@@ -164,6 +164,14 @@ M(PTP_GET_CAP, 0x00c, ptp_get_cap, msg_req, ptp_get_cap_rsp) \
M(GET_REP_CNT, 0x00d, get_rep_cnt, msg_req, get_rep_cnt_rsp) \
M(ESW_CFG, 0x00e, esw_cfg, esw_cfg_req, msg_rsp) \
M(REP_EVENT_NOTIFY, 0x00f, rep_event_notify, rep_event, msg_rsp) \
+M(FDB_NOTIFY, 0x010, fdb_notify, \
+ fdb_notify_req, msg_rsp) \
+M(FIB_NOTIFY, 0x011, fib_notify, \
+ fib_notify_req, msg_rsp) \
+M(FL_NOTIFY, 0x012, fl_notify, \
+ fl_notify_req, msg_rsp) \
+M(FL_GET_STATS, 0x013, fl_get_stats, \
+ fl_get_stats_req, fl_get_stats_rsp) \
/* CGX mbox IDs (range 0x200 - 0x3FF) */ \
M(CGX_START_RXTX, 0x200, cgx_start_rxtx, msg_req, msg_rsp) \
M(CGX_STOP_RXTX, 0x201, cgx_stop_rxtx, msg_req, msg_rsp) \
@@ -1805,6 +1813,102 @@ struct rep_event {
struct rep_evt_data evt_data;
};
+#define FDB_ADD BIT_ULL(0)
+#define FDB_DEL BIT_ULL(1)
+#define FIB_CMD BIT_ULL(2)
+#define FL_ADD BIT_ULL(3)
+#define FL_DEL BIT_ULL(4)
+#define DP_ADD BIT_ULL(5)
+
+struct fdb_notify_req {
+ struct mbox_msghdr hdr;
+ u64 flags;
+ u8 mac[ETH_ALEN];
+};
+
+struct fib_entry {
+ u64 cmd;
+ u64 gw_valid : 1;
+ u64 mac_valid : 1;
+ u64 vlan_valid: 1;
+ u64 host : 1;
+ u64 bridge : 1;
+ u64 ipv6 : 1;
+ u16 vlan_tag;
+ u32 dst_len;
+ u8 dst6_plen;
+ u8 gw6_plen;
+ union {
+ u32 dst;
+ u32 dst6[4];
+ };
+ union {
+ u32 gw;
+ u32 gw6[4];
+ };
+ u16 port_id;
+ u8 nud_state;
+ u8 mac[ETH_ALEN];
+};
+
+struct fib_notify_req {
+ struct mbox_msghdr hdr;
+ u16 cnt;
+ struct fib_entry entry[16];
+};
+
+struct fl_tuple {
+ __be32 ip4src;
+ __be32 m_ip4src;
+ __be32 ip4dst;
+ __be32 m_ip4dst;
+ __be16 sport;
+ __be16 m_sport;
+ __be16 dport;
+ __be16 m_dport;
+ __be16 eth_type;
+ __be16 m_eth_type;
+ u8 proto;
+ u8 smac[6];
+ u8 m_smac[6];
+ u8 dmac[6];
+ u8 m_dmac[6];
+ u64 is_xdev_br : 1;
+ u64 is_indev_br : 1;
+ u64 uni_di : 1;
+ u16 in_pf;
+ u16 xmit_pf;
+ u64 features;
+ struct { /* FLOW_ACTION_MANGLE */
+ u8 offset;
+ u8 type;
+ u32 mask;
+ u32 val;
+#define MANGLE_ARR_SZ 9
+ } mangle[MANGLE_ARR_SZ]; /* 2 for ETH, 1 for VLAN, 4 for IPv6, 2 for L4. */
+#define MANGLE_LAYER_CNT 4
+ u8 mangle_map[MANGLE_LAYER_CNT]; /* 1 for ETH, 1 for VLAN, 1 for L3, 1 for L4 */
+ u8 mangle_cnt;
+};
+
+struct fl_notify_req {
+ struct mbox_msghdr hdr;
+ unsigned long cookie;
+ u64 flags;
+ u64 features;
+ struct fl_tuple tuple;
+};
+
+struct fl_get_stats_req {
+ struct mbox_msghdr hdr;
+ unsigned long cookie;
+};
+
+struct fl_get_stats_rsp {
+ struct mbox_msghdr hdr;
+ u64 pkts_diff;
+};
+
struct flow_msg {
unsigned char dmac[6];
unsigned char smac[6];
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_fl.c b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_fl.c
new file mode 100644
index 000000000000..1f8b82a84a5d
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_fl.c
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Marvell RVU Admin Function driver
+ *
+ * Copyright (C) 2026 Marvell.
+ *
+ */
+#include "rvu.h"
+
+int rvu_mbox_handler_fl_get_stats(struct rvu *rvu,
+ struct fl_get_stats_req *req,
+ struct fl_get_stats_rsp *rsp)
+{
+ return 0;
+}
+
+int rvu_mbox_handler_fl_notify(struct rvu *rvu,
+ struct fl_notify_req *req,
+ struct msg_rsp *rsp)
+{
+ return 0;
+}
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_fl.h b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_fl.h
new file mode 100644
index 000000000000..cf3e5b884f77
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_fl.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Marvell RVU Admin Function driver
+ *
+ * Copyright (C) 2026 Marvell.
+ *
+ */
+
+#ifndef RVU_SW_FL_H
+#define RVU_SW_FL_H
+
+#endif
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l2.c b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l2.c
new file mode 100644
index 000000000000..5f805bfa81ed
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l2.c
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Marvell RVU Admin Function driver
+ *
+ * Copyright (C) 2026 Marvell.
+ *
+ */
+#include "rvu.h"
+
+int rvu_mbox_handler_fdb_notify(struct rvu *rvu,
+ struct fdb_notify_req *req,
+ struct msg_rsp *rsp)
+{
+ return 0;
+}
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l2.h b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l2.h
new file mode 100644
index 000000000000..ff28612150c9
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l2.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Marvell RVU Admin Function driver
+ *
+ * Copyright (C) 2026 Marvell.
+ *
+ */
+
+#ifndef RVU_SW_L2_H
+#define RVU_SW_L2_H
+
+#endif
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l3.c b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l3.c
new file mode 100644
index 000000000000..2b798d5f0644
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l3.c
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Marvell RVU Admin Function driver
+ *
+ * Copyright (C) 2026 Marvell.
+ *
+ */
+#include "rvu.h"
+
+int rvu_mbox_handler_fib_notify(struct rvu *rvu,
+ struct fib_notify_req *req,
+ struct msg_rsp *rsp)
+{
+ return 0;
+}
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l3.h b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l3.h
new file mode 100644
index 000000000000..ac8c4f9ba5ac
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l3.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Marvell RVU Admin Function driver
+ *
+ * Copyright (C) 2026 Marvell.
+ *
+ */
+
+#ifndef RVU_SW_L3_H
+#define RVU_SW_L3_H
+
+#endif
--
2.43.0
^ permalink raw reply related
* [PATCH v2 net-next 2/9] octeontx2-af: switch: Add switch dev to AF mboxes
From: Ratheesh Kannoth @ 2026-07-02 4:50 UTC (permalink / raw)
To: linux-kernel, netdev
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, sgoutham,
Ratheesh Kannoth
In-Reply-To: <20260702045026.2914748-1-rkannoth@marvell.com>
The Marvell switch hardware runs on a Linux OS. Switch
needs various information from AF driver. These mboxes are defined
to query those from AF driver.
Signed-off-by: Ratheesh Kannoth <rkannoth@marvell.com>
---
.../ethernet/marvell/octeontx2/af/Makefile | 2 +-
.../net/ethernet/marvell/octeontx2/af/mbox.h | 121 ++++++++++++++++++
.../net/ethernet/marvell/octeontx2/af/rvu.c | 116 +++++++++++++++++
.../net/ethernet/marvell/octeontx2/af/rvu.h | 1 +
.../ethernet/marvell/octeontx2/af/rvu_nix.c | 3 +-
.../ethernet/marvell/octeontx2/af/rvu_npc.c | 88 +++++++++++++
.../marvell/octeontx2/af/rvu_npc_fs.c | 11 ++
.../marvell/octeontx2/af/switch/rvu_sw.c | 15 +++
.../marvell/octeontx2/af/switch/rvu_sw.h | 11 ++
9 files changed, 365 insertions(+), 3 deletions(-)
create mode 100644 drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.c
create mode 100644 drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.h
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/Makefile b/drivers/net/ethernet/marvell/octeontx2/af/Makefile
index 82dd387308c9..73f20a44f1a0 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/Makefile
+++ b/drivers/net/ethernet/marvell/octeontx2/af/Makefile
@@ -12,6 +12,6 @@ rvu_af-y := cgx.o rvu.o rvu_cgx.o rvu_npa.o rvu_nix.o \
rvu_reg.o rvu_npc.o rvu_debugfs.o ptp.o rvu_npc_fs.o \
rvu_cpt.o rvu_devlink.o rpm.o rvu_cn10k.o rvu_switch.o \
rvu_sdp.o rvu_npc_hash.o mcs.o mcs_rvu_if.o mcs_cnf10kb.o \
- switch/rvu_sw_l2.o switch/rvu_sw_l3.o switch/rvu_sw_fl.o\
+ switch/rvu_sw.o switch/rvu_sw_l2.o switch/rvu_sw_l3.o switch/rvu_sw_fl.o \
rvu_rep.o cn20k/mbox_init.o cn20k/nix.o cn20k/debugfs.o \
cn20k/npa.o cn20k/npc.o
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/mbox.h b/drivers/net/ethernet/marvell/octeontx2/af/mbox.h
index 821895a7014e..43ff97441273 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/mbox.h
+++ b/drivers/net/ethernet/marvell/octeontx2/af/mbox.h
@@ -172,6 +172,10 @@ M(FL_NOTIFY, 0x012, fl_notify, \
fl_notify_req, msg_rsp) \
M(FL_GET_STATS, 0x013, fl_get_stats, \
fl_get_stats_req, fl_get_stats_rsp) \
+M(GET_IFACE_GET_INFO, 0x014, iface_get_info, msg_req, \
+ iface_get_info_rsp) \
+M(SWDEV2AF_NOTIFY, 0x015, swdev2af_notify, \
+ swdev2af_notify_req, msg_rsp) \
/* CGX mbox IDs (range 0x200 - 0x3FF) */ \
M(CGX_START_RXTX, 0x200, cgx_start_rxtx, msg_req, msg_rsp) \
M(CGX_STOP_RXTX, 0x201, cgx_stop_rxtx, msg_req, msg_rsp) \
@@ -317,6 +321,14 @@ M(NPC_MCAM_GET_DFT_RL_IDXS, 0x601e, npc_get_dft_rl_idxs, \
M(NPC_MCAM_GET_NPC_PFL_INFO, 0x601f, npc_get_pfl_info, \
msg_req, \
npc_get_pfl_info_rsp) \
+M(NPC_MCAM_FLOW_DEL_N_FREE, 0x6020, npc_flow_del_n_free, \
+ npc_flow_del_n_free_req, msg_rsp) \
+M(NPC_MCAM_GET_MUL_STATS, 0x6021, npc_mcam_mul_stats, \
+ npc_mcam_get_mul_stats_req, \
+ npc_mcam_get_mul_stats_rsp) \
+M(NPC_MCAM_GET_FEATURES, 0x6022, npc_mcam_get_features, \
+ msg_req, \
+ npc_mcam_get_features_rsp) \
/* NIX mbox IDs (range 0x8000 - 0xFFFF) */ \
M(NIX_LF_ALLOC, 0x8000, nix_lf_alloc, \
nix_lf_alloc_req, nix_lf_alloc_rsp) \
@@ -446,6 +458,12 @@ M(MCS_INTR_NOTIFY, 0xE00, mcs_intr_notify, mcs_intr_info, msg_rsp)
#define MBOX_UP_REP_MESSAGES \
M(REP_EVENT_UP_NOTIFY, 0xEF0, rep_event_up_notify, rep_event, msg_rsp) \
+#define MBOX_UP_AF2SWDEV_MESSAGES \
+M(AF2SWDEV, 0xEF1, af2swdev_notify, af2swdev_notify_req, msg_rsp)
+
+#define MBOX_UP_AF2PF_FDB_REFRESH_MESSAGES \
+M(AF2PF_FDB_REFRESH, 0xEF2, af2pf_fdb_refresh, af2pf_fdb_refresh_req, msg_rsp)
+
enum {
#define M(_name, _id, _1, _2, _3) MBOX_MSG_ ## _name = _id,
MBOX_MESSAGES
@@ -453,6 +471,8 @@ MBOX_UP_CGX_MESSAGES
MBOX_UP_CPT_MESSAGES
MBOX_UP_MCS_MESSAGES
MBOX_UP_REP_MESSAGES
+MBOX_UP_AF2SWDEV_MESSAGES
+MBOX_UP_AF2PF_FDB_REFRESH_MESSAGES
#undef M
};
@@ -1587,6 +1607,30 @@ struct npc_mcam_alloc_entry_rsp {
u16 entry_list[NPC_MAX_NONCONTIG_ENTRIES];
};
+struct npc_flow_del_n_free_req {
+ struct mbox_msghdr hdr;
+ u16 cnt;
+ u16 entry[256]; /* Entry index to be freed */
+};
+
+struct npc_mcam_get_features_rsp {
+ struct mbox_msghdr hdr;
+ u64 rx_features;
+ u64 tx_features;
+};
+
+struct npc_mcam_get_mul_stats_req {
+ struct mbox_msghdr hdr;
+ int cnt;
+ u16 entry[256]; /* mcam entry */
+};
+
+struct npc_mcam_get_mul_stats_rsp {
+ struct mbox_msghdr hdr;
+ int cnt;
+ u64 stat[256]; /* counter stats */
+};
+
struct npc_mcam_free_entry_req {
struct mbox_msghdr hdr;
u16 entry; /* Entry index to be freed */
@@ -1909,6 +1953,83 @@ struct fl_get_stats_rsp {
u64 pkts_diff;
};
+struct af2swdev_notify_req {
+ struct mbox_msghdr hdr;
+ u64 flags;
+ u32 port_id;
+ u32 switch_id;
+ union {
+ struct {
+ u8 mac[6];
+ };
+ struct {
+ u8 cnt;
+ struct fib_entry entry[16];
+ };
+
+ struct {
+ unsigned long cookie;
+ u64 features;
+ struct fl_tuple tuple;
+ };
+ };
+};
+
+struct af2pf_fdb_refresh_req {
+ struct mbox_msghdr hdr;
+ u16 pcifunc;
+ u8 mac[6];
+};
+
+struct iface_info {
+ u64 is_vf :1;
+ u64 is_sdp :1;
+ u16 pcifunc;
+ u16 rx_chan_base;
+ u16 tx_chan_base;
+ u16 sq_cnt;
+ u16 cq_cnt;
+ u16 rq_cnt;
+ u8 rx_chan_cnt;
+ u8 tx_chan_cnt;
+ u8 tx_link;
+ u8 nix;
+};
+
+#define IFACE_MAX (256 + 32) /* 32 PFs + 256 Vs */
+
+struct iface_get_info_rsp {
+ struct mbox_msghdr hdr;
+ int cnt;
+ struct iface_info info[IFACE_MAX];
+};
+
+struct fl_info {
+ unsigned long cookie;
+ u16 mcam_idx[2];
+ u8 dis : 1;
+ u8 uni_di : 1;
+};
+
+struct swdev2af_notify_req {
+ struct mbox_msghdr hdr;
+ u64 msg_type;
+#define SWDEV2AF_MSG_TYPE_FW_STATUS BIT_ULL(0)
+#define SWDEV2AF_MSG_TYPE_REFRESH_FDB BIT_ULL(1)
+#define SWDEV2AF_MSG_TYPE_REFRESH_FL BIT_ULL(2)
+ u16 pcifunc;
+ union {
+ bool fw_up; // FW_STATUS message
+
+ u8 mac[ETH_ALEN]; // fdb refresh message
+
+ struct { // fl refresh message
+ int cnt;
+ struct fl_info fl[64];
+ };
+ };
+};
+
struct flow_msg {
unsigned char dmac[6];
unsigned char smac[6];
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu.c
index ffba56ee8a60..6e10a58a0421 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu.c
+++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu.c
@@ -1990,6 +1990,122 @@ int rvu_mbox_handler_msix_offset(struct rvu *rvu, struct msg_req *req,
return 0;
}
+int rvu_mbox_handler_iface_get_info(struct rvu *rvu, struct msg_req *req,
+ struct iface_get_info_rsp *rsp)
+{
+ struct iface_info *info;
+ struct rvu_pfvf *pfvf;
+ int pf, vf, numvfs;
+ u16 pcifunc;
+ int tot = 0;
+ u64 cfg;
+
+ info = rsp->info;
+ for (pf = 0; pf < rvu->hw->total_pfs; pf++) {
+ if (tot >= IFACE_MAX)
+ goto done;
+
+ cfg = rvu_read64(rvu, BLKADDR_RVUM, RVU_PRIV_PFX_CFG(pf));
+ numvfs = (cfg >> 12) & 0xFF;
+
+ /* Skip not enabled PFs */
+ if (!(cfg & BIT_ULL(20)))
+ goto chk_vfs;
+
+ /* If Admin function, check on VFs */
+ if (cfg & BIT_ULL(21))
+ goto chk_vfs;
+
+ pcifunc = rvu_make_pcifunc(rvu->pdev, pf, 0);
+ pfvf = rvu_get_pfvf(rvu, pcifunc);
+
+ /* Populate iff at least one Tx channel */
+ if (!pfvf->tx_chan_cnt)
+ goto chk_vfs;
+
+ info->is_vf = 0;
+ info->pcifunc = pcifunc;
+ info->rx_chan_base = pfvf->rx_chan_base;
+ info->rx_chan_cnt = pfvf->rx_chan_cnt;
+ info->tx_chan_base = pfvf->tx_chan_base;
+ info->tx_chan_cnt = pfvf->tx_chan_cnt;
+ info->tx_link = nix_get_tx_link(rvu, pcifunc);
+ if (is_sdp_pfvf(rvu, pcifunc))
+ info->is_sdp = 1;
+
+ /* If interfaces are not UP, there are no queues */
+ info->sq_cnt = 0;
+ info->cq_cnt = 0;
+ info->rq_cnt = 0;
+
+ if (pfvf->sq_bmap)
+ info->sq_cnt = bitmap_weight(pfvf->sq_bmap, BITS_PER_LONG * 16);
+
+ if (pfvf->cq_bmap)
+ info->cq_cnt = bitmap_weight(pfvf->cq_bmap, BITS_PER_LONG);
+
+ if (pfvf->rq_bmap)
+ info->rq_cnt = bitmap_weight(pfvf->rq_bmap, BITS_PER_LONG);
+
+ if (pfvf->nix_blkaddr == BLKADDR_NIX0)
+ info->nix = 0;
+ else
+ info->nix = 1;
+
+ info++;
+ tot++;
+
+chk_vfs:
+ for (vf = 0; vf < numvfs; vf++) {
+ if (tot >= IFACE_MAX)
+ goto done;
+
+ pcifunc = rvu_make_pcifunc(rvu->pdev, pf, vf + 1);
+ pfvf = rvu_get_pfvf(rvu, pcifunc);
+
+ if (!pfvf->tx_chan_cnt)
+ continue;
+
+ info->is_vf = 1;
+ info->pcifunc = pcifunc;
+ info->rx_chan_base = pfvf->rx_chan_base;
+ info->rx_chan_cnt = pfvf->rx_chan_cnt;
+ info->tx_chan_base = pfvf->tx_chan_base;
+ info->tx_chan_cnt = pfvf->tx_chan_cnt;
+ info->tx_link = nix_get_tx_link(rvu, pcifunc);
+ if (is_sdp_pfvf(rvu, pcifunc))
+ info->is_sdp = 1;
+
+ /* If interfaces are not UP, there are no queues */
+ info->sq_cnt = 0;
+ info->cq_cnt = 0;
+ info->rq_cnt = 0;
+
+ if (pfvf->sq_bmap)
+ info->sq_cnt = bitmap_weight(pfvf->sq_bmap, BITS_PER_LONG * 16);
+
+ if (pfvf->cq_bmap)
+ info->cq_cnt = bitmap_weight(pfvf->cq_bmap, BITS_PER_LONG);
+
+ if (pfvf->rq_bmap)
+ info->rq_cnt = bitmap_weight(pfvf->rq_bmap, BITS_PER_LONG);
+
+ if (pfvf->nix_blkaddr == BLKADDR_NIX0)
+ info->nix = 0;
+ else
+ info->nix = 1;
+
+ info++;
+
+ tot++;
+ }
+ }
+done:
+ rsp->cnt = tot;
+
+ return 0;
+}
+
int rvu_mbox_handler_free_rsrc_cnt(struct rvu *rvu, struct msg_req *req,
struct free_rsrcs_rsp *rsp)
{
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu.h b/drivers/net/ethernet/marvell/octeontx2/af/rvu.h
index 7f3505ae6860..21803e257cae 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu.h
+++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu.h
@@ -1158,6 +1158,7 @@ void rvu_program_channels(struct rvu *rvu);
/* CN10K NIX */
void rvu_nix_block_cn10k_init(struct rvu *rvu, struct nix_hw *nix_hw);
+int nix_get_tx_link(struct rvu *rvu, u16 pcifunc);
/* CN10K RVU - LMT*/
void rvu_reset_lmt_map_tbl(struct rvu *rvu, u16 pcifunc);
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c
index 0297c7ab0614..d2734f6d6d92 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c
+++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c
@@ -32,7 +32,6 @@ static int nix_free_all_bandprof(struct rvu *rvu, u16 pcifunc);
static void nix_clear_ratelimit_aggr(struct rvu *rvu, struct nix_hw *nix_hw,
u32 leaf_prof);
static const char *nix_get_ctx_name(int ctype);
-static int nix_get_tx_link(struct rvu *rvu, u16 pcifunc);
enum mc_tbl_sz {
MC_TBL_SZ_256,
@@ -2087,7 +2086,7 @@ static void nix_clear_tx_xoff(struct rvu *rvu, int blkaddr,
rvu_write64(rvu, blkaddr, reg, 0x0);
}
-static int nix_get_tx_link(struct rvu *rvu, u16 pcifunc)
+int nix_get_tx_link(struct rvu *rvu, u16 pcifunc)
{
struct rvu_hwinfo *hw = rvu->hw;
int pf = rvu_get_pf(rvu->pdev, pcifunc);
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c
index c7bc0b3a29b9..d797cd05776e 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c
+++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c
@@ -3544,6 +3544,45 @@ int rvu_mbox_handler_npc_mcam_free_entry(struct rvu *rvu,
return rc;
}
+int rvu_mbox_handler_npc_flow_del_n_free(struct rvu *rvu,
+ struct npc_flow_del_n_free_req *mreq,
+ struct msg_rsp *rsp)
+{
+ struct npc_mcam_free_entry_req sreq = { 0 };
+ struct npc_delete_flow_req dreq = { 0 };
+ struct npc_delete_flow_rsp drsp = { 0 };
+ bool err = false;
+ int ret = 0, i;
+
+ sreq.hdr.pcifunc = mreq->hdr.pcifunc;
+ dreq.hdr.pcifunc = mreq->hdr.pcifunc;
+
+ if (!mreq->cnt || mreq->cnt > 256) {
+ dev_err(rvu->dev, "Invalid cnt=%d\n", mreq->cnt);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < mreq->cnt; i++) {
+ dreq.entry = mreq->entry[i];
+ ret = rvu_mbox_handler_npc_delete_flow(rvu, &dreq, &drsp);
+ if (ret) {
+ dev_err(rvu->dev, "delete flow error for i=%d entry=%d\n",
+ i, mreq->entry[i]);
+ err = true;
+ }
+
+ sreq.entry = mreq->entry[i];
+ ret = rvu_mbox_handler_npc_mcam_free_entry(rvu, &sreq, rsp);
+ if (ret) {
+ dev_err(rvu->dev, "free entry error for i=%d entry=%d\n",
+ i, mreq->entry[i]);
+ err = true;
+ }
+ }
+
+ return err ? -EINVAL : 0;
+}
+
int rvu_mbox_handler_npc_mcam_read_entry(struct rvu *rvu,
struct npc_mcam_read_entry_req *req,
struct npc_mcam_read_entry_rsp *rsp)
@@ -4361,6 +4400,55 @@ int rvu_mbox_handler_npc_mcam_entry_stats(struct rvu *rvu,
return 0;
}
+int rvu_mbox_handler_npc_mcam_mul_stats(struct rvu *rvu,
+ struct npc_mcam_get_mul_stats_req *req,
+ struct npc_mcam_get_mul_stats_rsp *rsp)
+{
+ struct npc_mcam *mcam = &rvu->hw->mcam;
+ u16 pcifunc = req->hdr.pcifunc;
+ u16 index, cntr, entry;
+ int blkaddr;
+ u64 regval;
+ u32 bank;
+
+ if (!req->cnt || req->cnt > 256) {
+ dev_err(rvu->dev, "%s invalid request cnt=%d\n",
+ __func__, req->cnt);
+ return -EINVAL;
+ }
+
+ blkaddr = rvu_get_blkaddr(rvu, BLKTYPE_NPC, 0);
+ if (blkaddr < 0)
+ return NPC_MCAM_INVALID_REQ;
+
+ mutex_lock(&mcam->lock);
+
+ for (int i = 0; i < req->cnt; i++) {
+ entry = npc_cn20k_vidx2idx(req->entry[i]);
+
+ if (npc_mcam_verify_entry(mcam, pcifunc, entry)) {
+ dev_err(rvu->dev, "%s invalid mcam index=%d\n",
+ __func__, req->entry[i]);
+ return -EINVAL;
+ }
+
+ index = entry & (mcam->banksize - 1);
+ bank = npc_get_bank(mcam, entry);
+
+ /* read MCAM entry STAT_ACT register */
+ regval = rvu_read64(rvu, blkaddr, NPC_AF_MCAMEX_BANKX_STAT_ACT(index, bank));
+ cntr = regval & 0x1FF;
+
+ rsp->stat[i] = rvu_read64(rvu, blkaddr, NPC_AF_MATCH_STATX(cntr));
+ rsp->stat[i] &= BIT_ULL(48) - 1;
+ }
+
+ rsp->cnt = req->cnt;
+
+ mutex_unlock(&mcam->lock);
+ return 0;
+}
+
void rvu_npc_clear_ucast_entry(struct rvu *rvu, int pcifunc, int nixlf)
{
struct npc_mcam *mcam = &rvu->hw->mcam;
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc_fs.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc_fs.c
index 91b5947dae06..09c7ee8571df 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc_fs.c
+++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc_fs.c
@@ -1926,6 +1926,17 @@ static int npc_delete_flow(struct rvu *rvu, struct rvu_npc_mcam_rule *rule,
return rvu_mbox_handler_npc_mcam_dis_entry(rvu, &dis_req, &dis_rsp);
}
+int rvu_mbox_handler_npc_mcam_get_features(struct rvu *rvu,
+ struct msg_req *req,
+ struct npc_mcam_get_features_rsp *rsp)
+{
+ struct npc_mcam *mcam = &rvu->hw->mcam;
+
+ rsp->rx_features = mcam->rx_features;
+ rsp->tx_features = mcam->tx_features;
+ return 0;
+}
+
int rvu_mbox_handler_npc_delete_flow(struct rvu *rvu,
struct npc_delete_flow_req *req,
struct npc_delete_flow_rsp *rsp)
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.c b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.c
new file mode 100644
index 000000000000..fe143ad3f944
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.c
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Marvell RVU Admin Function driver
+ *
+ * Copyright (C) 2026 Marvell.
+ *
+ */
+
+#include "rvu.h"
+
+int rvu_mbox_handler_swdev2af_notify(struct rvu *rvu,
+ struct swdev2af_notify_req *req,
+ struct msg_rsp *rsp)
+{
+ return 0;
+}
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.h b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.h
new file mode 100644
index 000000000000..f28dba556d80
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Marvell RVU Admin Function driver
+ *
+ * Copyright (C) 2026 Marvell.
+ *
+ */
+
+#ifndef RVU_SWITCH_H
+#define RVU_SWITCH_H
+
+#endif
--
2.43.0
^ permalink raw reply related
* [PATCH v2 net-next 3/9] octeontx2-pf: switch: Add pf files hierarchy
From: Ratheesh Kannoth @ 2026-07-02 4:50 UTC (permalink / raw)
To: linux-kernel, netdev
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, sgoutham,
Ratheesh Kannoth
In-Reply-To: <20260702045026.2914748-1-rkannoth@marvell.com>
Adds CONFIG_OCTEONTX_SWITCH, links stub switch objects into the PF
module, and introduces empty sw_* init/deinit and notifier hooks for
later patches.
Signed-off-by: Ratheesh Kannoth <rkannoth@marvell.com>
---
drivers/net/ethernet/marvell/octeontx2/Kconfig | 10 ++++++++++
.../net/ethernet/marvell/octeontx2/nic/Makefile | 8 +++++++-
.../marvell/octeontx2/nic/switch/sw_fdb.c | 16 ++++++++++++++++
.../marvell/octeontx2/nic/switch/sw_fdb.h | 13 +++++++++++++
.../marvell/octeontx2/nic/switch/sw_fib.c | 16 ++++++++++++++++
.../marvell/octeontx2/nic/switch/sw_fib.h | 13 +++++++++++++
.../marvell/octeontx2/nic/switch/sw_fl.c | 16 ++++++++++++++++
.../marvell/octeontx2/nic/switch/sw_fl.h | 13 +++++++++++++
.../marvell/octeontx2/nic/switch/sw_nb.c | 17 +++++++++++++++++
.../marvell/octeontx2/nic/switch/sw_nb.h | 13 +++++++++++++
10 files changed, 134 insertions(+), 1 deletion(-)
create mode 100644 drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.c
create mode 100644 drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.h
create mode 100644 drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fib.c
create mode 100644 drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fib.h
create mode 100644 drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.c
create mode 100644 drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.h
create mode 100644 drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c
create mode 100644 drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.h
diff --git a/drivers/net/ethernet/marvell/octeontx2/Kconfig b/drivers/net/ethernet/marvell/octeontx2/Kconfig
index 47e549c581f0..e2fb6dd71078 100644
--- a/drivers/net/ethernet/marvell/octeontx2/Kconfig
+++ b/drivers/net/ethernet/marvell/octeontx2/Kconfig
@@ -28,6 +28,16 @@ config NDC_DIS_DYNAMIC_CACHING
, NPA stack pages etc in NDC. Also locks down NIX SQ/CQ/RQ/RSS and
NPA Aura/Pool contexts.
+config OCTEONTX_SWITCH
+ bool "Marvell OcteonTX2 switch driver"
+ depends on (64BIT && COMPILE_TEST) || ARM64
+ depends on OCTEONTX2_PF
+ default n
+ help
+ This driver supports Marvell's OcteonTX2 switch.
+ Marvell SWITCH HW can offload L2, L3 flow. ARM core interacts
+ with Marvell SW HW thru mbox.
+
config OCTEONTX2_PF
tristate "Marvell OcteonTX2 NIC Physical Function driver"
select OCTEONTX2_MBOX
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/Makefile b/drivers/net/ethernet/marvell/octeontx2/nic/Makefile
index 883e9f4d601c..da87e952c187 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/Makefile
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/Makefile
@@ -9,7 +9,13 @@ obj-$(CONFIG_RVU_ESWITCH) += rvu_rep.o
rvu_nicpf-y := otx2_pf.o otx2_common.o otx2_txrx.o otx2_ethtool.o \
otx2_flows.o otx2_tc.o cn10k.o cn20k.o otx2_dmac_flt.o \
- otx2_devlink.o qos_sq.o qos.o otx2_xsk.o
+ otx2_devlink.o qos_sq.o qos.o otx2_xsk.o \
+ switch/sw_fdb.o switch/sw_fl.o
+
+ifdef CONFIG_OCTEONTX_SWITCH
+rvu_nicpf-y += switch/sw_nb.o switch/sw_fib.o
+endif
+
rvu_nicvf-y := otx2_vf.o
rvu_rep-y := rep.o
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.c b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.c
new file mode 100644
index 000000000000..6842c8d91ffc
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.c
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Marvell RVU switch driver
+ *
+ * Copyright (C) 2026 Marvell.
+ *
+ */
+#include "sw_fdb.h"
+
+int sw_fdb_init(void)
+{
+ return 0;
+}
+
+void sw_fdb_deinit(void)
+{
+}
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.h b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.h
new file mode 100644
index 000000000000..d4314d6d3ee4
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Marvell switch driver
+ *
+ * Copyright (C) 2026 Marvell.
+ *
+ */
+#ifndef SW_FDB_H_
+#define SW_FDB_H_
+
+void sw_fdb_deinit(void);
+int sw_fdb_init(void);
+
+#endif // SW_FDB_H
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fib.c b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fib.c
new file mode 100644
index 000000000000..12ddf8119372
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fib.c
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Marvell RVU switch driver
+ *
+ * Copyright (C) 2026 Marvell.
+ *
+ */
+#include "sw_fib.h"
+
+int sw_fib_init(void)
+{
+ return 0;
+}
+
+void sw_fib_deinit(void)
+{
+}
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fib.h b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fib.h
new file mode 100644
index 000000000000..a51d15c2b80e
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fib.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Marvell switch driver
+ *
+ * Copyright (C) 2026 Marvell.
+ *
+ */
+#ifndef SW_FIB_H_
+#define SW_FIB_H_
+
+void sw_fib_deinit(void);
+int sw_fib_init(void);
+
+#endif // SW_FIB_H
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.c b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.c
new file mode 100644
index 000000000000..36a2359a0a48
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.c
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Marvell RVU switch driver
+ *
+ * Copyright (C) 2026 Marvell.
+ *
+ */
+#include "sw_fl.h"
+
+int sw_fl_init(void)
+{
+ return 0;
+}
+
+void sw_fl_deinit(void)
+{
+}
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.h b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.h
new file mode 100644
index 000000000000..cd018d770a8a
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Marvell switch driver
+ *
+ * Copyright (C) 2026 Marvell.
+ *
+ */
+#ifndef SW_FL_H_
+#define SW_FL_H_
+
+void sw_fl_deinit(void);
+int sw_fl_init(void);
+
+#endif // SW_FL_H
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c
new file mode 100644
index 000000000000..2d14a0590c5d
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Marvell RVU switch driver
+ *
+ * Copyright (C) 2026 Marvell.
+ *
+ */
+#include "sw_nb.h"
+
+int sw_nb_unregister(void)
+{
+ return 0;
+}
+
+int sw_nb_register(void)
+{
+ return 0;
+}
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.h b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.h
new file mode 100644
index 000000000000..5f744cc3ecbb
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Marvell switch driver
+ *
+ * Copyright (C) 2026 Marvell.
+ *
+ */
+#ifndef SW_NB_H_
+#define SW_NB_H_
+
+int sw_nb_register(void);
+int sw_nb_unregister(void);
+
+#endif // SW_NB_H_
--
2.43.0
^ permalink raw reply related
* [PATCH v2 net-next 4/9] octeontx2-af: switch: Representor for switch port
From: Ratheesh Kannoth @ 2026-07-02 4:50 UTC (permalink / raw)
To: linux-kernel, netdev
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, sgoutham,
Ratheesh Kannoth
In-Reply-To: <20260702045026.2914748-1-rkannoth@marvell.com>
Extends esw_cfg with a devlink-derived switch id, copies it into
rvu->rswitch on the AF, adds rvu_sw_port_id(), exports
rvu_rep_get_vlan_id().
Signed-off-by: Ratheesh Kannoth <rkannoth@marvell.com>
---
drivers/net/ethernet/marvell/octeontx2/af/mbox.h | 1 +
drivers/net/ethernet/marvell/octeontx2/af/rvu.h | 5 +++++
.../net/ethernet/marvell/octeontx2/af/rvu_rep.c | 3 ++-
.../ethernet/marvell/octeontx2/af/switch/rvu_sw.c | 15 +++++++++++++++
.../ethernet/marvell/octeontx2/af/switch/rvu_sw.h | 3 +++
drivers/net/ethernet/marvell/octeontx2/nic/rep.c | 4 ++++
6 files changed, 30 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/mbox.h b/drivers/net/ethernet/marvell/octeontx2/af/mbox.h
index 43ff97441273..7ac26629d9af 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/mbox.h
+++ b/drivers/net/ethernet/marvell/octeontx2/af/mbox.h
@@ -1833,6 +1833,7 @@ struct esw_cfg_req {
struct mbox_msghdr hdr;
u8 ena;
u64 rsvd;
+ unsigned char switch_id[MAX_PHYS_ITEM_ID_LEN];
};
struct rep_evt_data {
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu.h b/drivers/net/ethernet/marvell/octeontx2/af/rvu.h
index 21803e257cae..3e34d56a5bd3 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu.h
+++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu.h
@@ -576,6 +576,10 @@ struct rvu_switch {
u16 *entry2pcifunc;
u16 mode;
u16 start_entry;
+ unsigned char switch_id[MAX_PHYS_ITEM_ID_LEN];
+#define RVU_SWITCH_FLAG_FW_READY BIT_ULL(0)
+ u64 flags;
+ u16 pcifunc;
};
struct rep_evtq_ent {
@@ -1197,4 +1201,5 @@ int rvu_rep_install_mcam_rules(struct rvu *rvu);
void rvu_rep_update_rules(struct rvu *rvu, u16 pcifunc, bool ena);
int rvu_rep_notify_pfvf_state(struct rvu *rvu, u16 pcifunc, bool enable);
int npc_mcam_verify_entry(struct npc_mcam *mcam, u16 pcifunc, int entry);
+u16 rvu_rep_get_vlan_id(struct rvu *rvu, u16 pcifunc);
#endif /* RVU_H */
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_rep.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_rep.c
index a2781e0f504e..6bb6064b2391 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_rep.c
+++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_rep.c
@@ -189,7 +189,7 @@ int rvu_mbox_handler_nix_lf_stats(struct rvu *rvu,
return 0;
}
-static u16 rvu_rep_get_vlan_id(struct rvu *rvu, u16 pcifunc)
+u16 rvu_rep_get_vlan_id(struct rvu *rvu, u16 pcifunc)
{
int id;
@@ -436,6 +436,7 @@ int rvu_mbox_handler_esw_cfg(struct rvu *rvu, struct esw_cfg_req *req,
return 0;
rvu->rep_mode = req->ena;
+ memcpy(rvu->rswitch.switch_id, req->switch_id, MAX_PHYS_ITEM_ID_LEN);
if (!rvu->rep_mode)
rvu_npc_free_mcam_entries(rvu, req->hdr.pcifunc, -1);
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.c b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.c
index fe143ad3f944..28f8281e9219 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.c
+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.c
@@ -5,7 +5,22 @@
*
*/
+#include <linux/bitfield.h>
#include "rvu.h"
+#include "rvu_sw.h"
+
+u32 rvu_sw_port_id(struct rvu *rvu, u16 pcifunc)
+{
+ u32 port_id;
+ u16 rep_id;
+
+ rep_id = rvu_rep_get_vlan_id(rvu, pcifunc);
+
+ port_id = FIELD_PREP(GENMASK_ULL(31, 16), rep_id) |
+ FIELD_PREP(GENMASK_ULL(15, 0), pcifunc);
+
+ return port_id;
+}
int rvu_mbox_handler_swdev2af_notify(struct rvu *rvu,
struct swdev2af_notify_req *req,
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.h b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.h
index f28dba556d80..847a8da60d0a 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.h
+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.h
@@ -8,4 +8,7 @@
#ifndef RVU_SWITCH_H
#define RVU_SWITCH_H
+/* RVU Switch */
+u32 rvu_sw_port_id(struct rvu *rvu, u16 pcifunc);
+
#endif
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/rep.c b/drivers/net/ethernet/marvell/octeontx2/nic/rep.c
index 0f5d5642d3f7..257a2ae6a53e 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/rep.c
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/rep.c
@@ -399,8 +399,11 @@ static void rvu_rep_get_stats64(struct net_device *dev,
static int rvu_eswitch_config(struct otx2_nic *priv, u8 ena)
{
+ struct devlink_port_attrs attrs = {};
struct esw_cfg_req *req;
+ rvu_rep_devlink_set_switch_id(priv, &attrs.switch_id);
+
mutex_lock(&priv->mbox.lock);
req = otx2_mbox_alloc_msg_esw_cfg(&priv->mbox);
if (!req) {
@@ -408,6 +411,7 @@ static int rvu_eswitch_config(struct otx2_nic *priv, u8 ena)
return -ENOMEM;
}
req->ena = ena;
+ memcpy(req->switch_id, attrs.switch_id.id, attrs.switch_id.id_len);
otx2_sync_mbox_msg(&priv->mbox);
mutex_unlock(&priv->mbox.lock);
return 0;
--
2.43.0
^ permalink raw reply related
* [PATCH v2 net-next 5/9] octeontx2-af: PAN switch TL1 scheduling and NPC channel control
From: Ratheesh Kannoth @ 2026-07-02 4:50 UTC (permalink / raw)
To: linux-kernel, netdev
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, sgoutham,
Ratheesh Kannoth
In-Reply-To: <20260702045026.2914748-1-rkannoth@marvell.com>
Switch (PAN) mode needs more than one TL1 scheduler queue index so the
hardware can steer traffic to different links according to NPC flow
rules, not only the PF/VF default Tx link.
Add NIX_TXSCH_ALLOC_FLAG_PAN to nix_txsch_alloc requests: use the PAN
link index for scheduler range calculation, allow multiple TL1 queues
when the aggregate level spans start..end, and allocate indices in
that range. Add TXSCHQ_FREE_PAN_TL1 so TL1 entries in that path can be
freed via nix_txsch_free where they were previously skipped.
For NPC install flow, add set_chanmask so callers can keep a non-default
chan_mask when the requester is not the AF; without it, chan_mask was
always forced to 0xFFF for non-AF functions.
Allocate the NIX LF SQ bitmap with the same span used by
bitmap_weight(..., BITS_PER_LONG * 16) in rvu_get_hwinfo().
Extend struct sg_list with cq_idx and len for transmit-side metadata.
Signed-off-by: Ratheesh Kannoth <rkannoth@marvell.com>
---
.../net/ethernet/marvell/octeontx2/af/mbox.h | 4 ++
.../ethernet/marvell/octeontx2/af/rvu_nix.c | 50 ++++++++++++++++---
.../marvell/octeontx2/nic/otx2_txrx.h | 2 +
3 files changed, 50 insertions(+), 6 deletions(-)
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/mbox.h b/drivers/net/ethernet/marvell/octeontx2/af/mbox.h
index 7ac26629d9af..28ebf0a8d3d6 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/mbox.h
+++ b/drivers/net/ethernet/marvell/octeontx2/af/mbox.h
@@ -1157,6 +1157,8 @@ struct nix_txsch_alloc_req {
/* Scheduler queue count request at each level */
u16 schq_contig[NIX_TXSCH_LVL_CNT]; /* No of contiguous queues */
u16 schq[NIX_TXSCH_LVL_CNT]; /* No of non-contiguous queues */
+#define NIX_TXSCH_ALLOC_FLAG_PAN BIT_ULL(0)
+ u64 flags;
};
struct nix_txsch_alloc_rsp {
@@ -1175,6 +1177,7 @@ struct nix_txsch_alloc_rsp {
struct nix_txsch_free_req {
struct mbox_msghdr hdr;
#define TXSCHQ_FREE_ALL BIT_ULL(0)
+#define TXSCHQ_FREE_PAN_TL1 BIT_ULL(1)
u16 flags;
/* Scheduler queue level to be freed */
u16 schq_lvl;
@@ -2105,6 +2108,7 @@ struct npc_install_flow_req {
u8 hw_prio;
u8 req_kw_type; /* Key type to be written */
u8 alloc_entry; /* only for cn20k */
+ u8 set_chanmask;
/* For now use any priority, once AF driver is changed to
* allocate least priority entry instead of mid zone then make
* NPC_MCAM_LEAST_PRIO as 3
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c
index d2734f6d6d92..35c91cdc50b9 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c
+++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c
@@ -1596,7 +1596,7 @@ int rvu_mbox_handler_nix_lf_alloc(struct rvu *rvu,
if (rc)
goto free_mem;
- pfvf->sq_bmap = kcalloc(req->sq_cnt, sizeof(long), GFP_KERNEL);
+ pfvf->sq_bmap = kcalloc(req->sq_cnt, sizeof(long) * 16, GFP_KERNEL);
if (!pfvf->sq_bmap) {
rc = -ENOMEM;
goto free_mem;
@@ -2138,11 +2138,14 @@ static int nix_check_txschq_alloc_req(struct rvu *rvu, int lvl, u16 pcifunc,
if (!req_schq)
return 0;
- link = nix_get_tx_link(rvu, pcifunc);
+ if (req->flags & NIX_TXSCH_ALLOC_FLAG_PAN)
+ link = hw->cgx_links + hw->lbk_links + 1;
+ else
+ link = nix_get_tx_link(rvu, pcifunc);
/* For traffic aggregating scheduler level, one queue is enough */
if (lvl >= hw->cap.nix_tx_aggr_lvl) {
- if (req_schq != 1)
+ if (req_schq != 1 && !(req->flags & NIX_TXSCH_ALLOC_FLAG_PAN))
return NIX_AF_ERR_TLX_ALLOC_FAIL;
return 0;
}
@@ -2179,11 +2182,41 @@ static void nix_txsch_alloc(struct rvu *rvu, struct nix_txsch *txsch,
struct rvu_hwinfo *hw = rvu->hw;
u16 pcifunc = rsp->hdr.pcifunc;
int idx, schq;
+ bool alloc;
/* For traffic aggregating levels, queue alloc is based
* on transmit link to which PF_FUNC is mapped to.
*/
if (lvl >= hw->cap.nix_tx_aggr_lvl) {
+ if (start != end) {
+ idx = 0;
+ alloc = false;
+ for (schq = start; schq <= end; schq++, idx++) {
+ if (test_bit(schq, txsch->schq.bmap))
+ continue;
+
+ set_bit(schq, txsch->schq.bmap);
+
+ /* A single TL queue is allocated each time */
+ if (rsp->schq_contig[lvl]) {
+ alloc = true;
+ rsp->schq_contig_list[lvl][idx] = schq;
+ continue;
+ }
+
+ if (rsp->schq[lvl]) {
+ alloc = true;
+ rsp->schq_list[lvl][idx] = schq;
+ continue;
+ }
+ }
+
+ if (!alloc)
+ dev_err(rvu->dev,
+ "Could not allocate schq at lvl=%u start=%u end=%u\n",
+ lvl, start, end);
+ return;
+ }
/* A single TL queue is allocated */
if (rsp->schq_contig[lvl]) {
rsp->schq_contig[lvl] = 1;
@@ -2300,11 +2333,14 @@ int rvu_mbox_handler_nix_txsch_alloc(struct rvu *rvu,
rsp->schq[lvl] = req->schq[lvl];
rsp->schq_contig[lvl] = req->schq_contig[lvl];
- link = nix_get_tx_link(rvu, pcifunc);
+ if (req->flags & NIX_TXSCH_ALLOC_FLAG_PAN)
+ link = hw->cgx_links + hw->lbk_links + 1;
+ else
+ link = nix_get_tx_link(rvu, pcifunc);
if (lvl >= hw->cap.nix_tx_aggr_lvl) {
start = link;
- end = link;
+ end = link + !!(req->flags & NIX_TXSCH_ALLOC_FLAG_PAN);
} else if (hw->cap.nix_fixed_txschq_mapping) {
nix_get_txschq_range(rvu, pcifunc, link, &start, &end);
} else {
@@ -2669,7 +2705,9 @@ static int nix_txschq_free_one(struct rvu *rvu,
schq = req->schq;
txsch = &nix_hw->txsch[lvl];
- if (lvl >= hw->cap.nix_tx_aggr_lvl || schq >= txsch->schq.max)
+ if ((lvl >= hw->cap.nix_tx_aggr_lvl &&
+ !(req->flags & TXSCHQ_FREE_PAN_TL1)) ||
+ schq >= txsch->schq.max)
return 0;
pfvf_map = txsch->pfvf_map;
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_txrx.h b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_txrx.h
index acf259d72008..73a98b94426b 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_txrx.h
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_txrx.h
@@ -78,6 +78,8 @@ struct otx2_rcv_queue {
struct sg_list {
u16 num_segs;
u16 flags;
+ u16 cq_idx;
+ u16 len;
u64 skb;
u64 size[OTX2_MAX_FRAGS_IN_SQE];
u64 dma_addr[OTX2_MAX_FRAGS_IN_SQE];
--
2.43.0
^ permalink raw reply related
* [PATCH v2 net-next 6/9] octeontx2-pf: register switch notifiers for eswitch offload
From: Ratheesh Kannoth @ 2026-07-02 4:50 UTC (permalink / raw)
To: linux-kernel, netdev
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, sgoutham,
Ratheesh Kannoth
In-Reply-To: <20260702045026.2914748-1-rkannoth@marvell.com>
The representor enables switch mode via devlink; register and unregister
the switch notifier blocks when that mode is turned on or off so the PF
can observe FIB routes, neighbour updates, IPv4/IPv6 address changes,
netdev state, and switchdev FDB notifications.
Add sw_nb_v4.c and sw_nb_v6.c for IPv4 and IPv6-specific handling, build
sw_nb_v6.o only when CONFIG_IPV6 is set, and extend sw_nb.c with device
filtering for Cavium ports behind bridges and VLANs.
Initialize and tear down the existing sw_fdb, sw_fib, and sw_fl helpers
together with notifier registration.
Signed-off-by: Ratheesh Kannoth <rkannoth@marvell.com>
---
.../ethernet/marvell/octeontx2/nic/Makefile | 7 +-
.../net/ethernet/marvell/octeontx2/nic/rep.c | 9 +
.../marvell/octeontx2/nic/switch/sw_nb.c | 418 +++++++++++++++++-
.../marvell/octeontx2/nic/switch/sw_nb.h | 28 +-
.../marvell/octeontx2/nic/switch/sw_nb_v4.c | 333 ++++++++++++++
.../marvell/octeontx2/nic/switch/sw_nb_v4.h | 21 +
.../marvell/octeontx2/nic/switch/sw_nb_v6.c | 246 +++++++++++
.../marvell/octeontx2/nic/switch/sw_nb_v6.h | 21 +
8 files changed, 1077 insertions(+), 6 deletions(-)
create mode 100644 drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.c
create mode 100644 drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.h
create mode 100644 drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v6.c
create mode 100644 drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v6.h
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/Makefile b/drivers/net/ethernet/marvell/octeontx2/nic/Makefile
index da87e952c187..0e12659876e0 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/Makefile
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/Makefile
@@ -13,7 +13,12 @@ rvu_nicpf-y := otx2_pf.o otx2_common.o otx2_txrx.o otx2_ethtool.o \
switch/sw_fdb.o switch/sw_fl.o
ifdef CONFIG_OCTEONTX_SWITCH
-rvu_nicpf-y += switch/sw_nb.o switch/sw_fib.o
+rvu_nicpf-y += switch/sw_nb.o switch/sw_fib.o \
+ switch/sw_nb_v4.o
+
+ifdef CONFIG_IPV6
+rvu_nicpf-y += switch/sw_nb_v6.o
+endif
endif
rvu_nicvf-y := otx2_vf.o
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/rep.c b/drivers/net/ethernet/marvell/octeontx2/nic/rep.c
index 257a2ae6a53e..e4c01ac87477 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/rep.c
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/rep.c
@@ -15,6 +15,7 @@
#include "cn10k.h"
#include "otx2_reg.h"
#include "rep.h"
+#include "switch/sw_nb.h"
#define DRV_NAME "rvu_rep"
#define DRV_STRING "Marvell RVU Representor Driver"
@@ -399,6 +400,9 @@ static void rvu_rep_get_stats64(struct net_device *dev,
static int rvu_eswitch_config(struct otx2_nic *priv, u8 ena)
{
+#if IS_ENABLED(CONFIG_OCTEONTX_SWITCH)
+ struct net_device *netdev = priv->netdev;
+#endif
struct devlink_port_attrs attrs = {};
struct esw_cfg_req *req;
@@ -414,6 +418,11 @@ static int rvu_eswitch_config(struct otx2_nic *priv, u8 ena)
memcpy(req->switch_id, attrs.switch_id.id, attrs.switch_id.id_len);
otx2_sync_mbox_msg(&priv->mbox);
mutex_unlock(&priv->mbox.lock);
+
+#if IS_ENABLED(CONFIG_OCTEONTX_SWITCH)
+ ena ? sw_nb_register(netdev) : sw_nb_unregister(netdev);
+#endif
+
return 0;
}
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c
index 2d14a0590c5d..fb183265a9aa 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c
@@ -4,14 +4,428 @@
* Copyright (C) 2026 Marvell.
*
*/
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <net/switchdev.h>
+#include <net/netevent.h>
+#include <net/arp.h>
+#include <net/route.h>
+#include <linux/inetdevice.h>
+#include <net/addrconf.h>
+
+#include "../otx2_reg.h"
+#include "../otx2_common.h"
+#include "../otx2_struct.h"
+#include "../cn10k.h"
#include "sw_nb.h"
+#include "sw_fdb.h"
+#include "sw_fib.h"
+#include "sw_fl.h"
+#include "sw_nb_v4.h"
+#include "sw_nb_v6.h"
+
+/* PF netdev for netdev_* logging when notifier info has no device */
+static struct net_device *sw_nb_pf_netdev;
-int sw_nb_unregister(void)
+static const char *sw_nb_cmd2str[OTX2_CMD_MAX] = {
+ [OTX2_DEV_UP] = "OTX2_DEV_UP",
+ [OTX2_DEV_DOWN] = "OTX2_DEV_DOWN",
+ [OTX2_DEV_CHANGE] = "OTX2_DEV_CHANGE",
+ [OTX2_NEIGH_UPDATE] = "OTX2_NEIGH_UPDATE",
+ [OTX2_FIB_ENTRY_REPLACE] = "OTX2_FIB_ENTRY_REPLACE",
+ [OTX2_FIB_ENTRY_ADD] = "OTX2_FIB_ENTRY_ADD",
+ [OTX2_FIB_ENTRY_DEL] = "OTX2_FIB_ENTRY_DEL",
+ [OTX2_FIB_ENTRY_APPEND] = "OTX2_FIB_ENTRY_APPEND",
+};
+
+const char *sw_nb_get_cmd2str(int cmd)
{
+ return sw_nb_cmd2str[cmd];
+}
+EXPORT_SYMBOL(sw_nb_get_cmd2str);
+
+bool sw_nb_is_cavium_dev(struct net_device *netdev)
+{
+ struct pci_dev *pdev;
+ struct device *dev;
+
+ dev = netdev->dev.parent;
+ if (!dev || dev->bus != &pci_bus_type)
+ return false;
+
+ pdev = to_pci_dev(dev);
+ if (pdev->vendor != PCI_VENDOR_ID_CAVIUM)
+ return false;
+
+ return true;
+}
+
+static int sw_nb_check_slaves(struct net_device *dev,
+ struct netdev_nested_priv *priv)
+{
+ int *cnt;
+
+ if (!priv->flags)
+ return 0;
+
+ priv->flags &= sw_nb_is_cavium_dev(dev);
+ if (priv->flags) {
+ cnt = priv->data;
+ (*cnt)++;
+ }
+
return 0;
}
-int sw_nb_register(void)
+bool sw_nb_is_valid_dev(struct net_device *netdev)
{
+ struct netdev_nested_priv priv;
+ struct net_device *br;
+ int cnt = 0;
+
+ priv.flags = true;
+ priv.data = &cnt;
+
+ if (netif_is_bridge_master(netdev) || is_vlan_dev(netdev)) {
+ /* sw_nb_is_valid_dev() is invoked in notifier callback context, so
+ * rtnl_lcok is already acquired
+ */
+ netdev_walk_all_lower_dev(netdev, sw_nb_check_slaves, &priv);
+ return priv.flags && !!*(int *)priv.data;
+ }
+
+ if (netif_is_bridge_port(netdev)) {
+ br = netdev_master_upper_dev_get_rcu(netdev);
+ if (!br)
+ return false;
+
+ netdev_walk_all_lower_dev(br, sw_nb_check_slaves, &priv);
+ return priv.flags && !!*(int *)priv.data;
+ }
+
+ return sw_nb_is_cavium_dev(netdev);
+}
+
+static int sw_nb_fdb_event(struct notifier_block *unused,
+ unsigned long event, void *ptr)
+{
+ struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
+ struct switchdev_notifier_fdb_info *fdb_info = ptr;
+
+ if (!sw_nb_is_valid_dev(dev))
+ return NOTIFY_DONE;
+
+ switch (event) {
+ case SWITCHDEV_FDB_ADD_TO_DEVICE:
+ if (fdb_info->is_local)
+ break;
+ break;
+
+ case SWITCHDEV_FDB_DEL_TO_DEVICE:
+ if (fdb_info->is_local)
+ break;
+ break;
+
+ default:
+ return NOTIFY_DONE;
+ }
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block sw_nb_fdb = {
+ .notifier_call = sw_nb_fdb_event,
+};
+
+static void __maybe_unused
+sw_nb_fib_event_dump(unsigned long event, void *ptr)
+{
+ struct fib_entry_notifier_info *fen_info = ptr;
+ struct net_device *log_dev;
+ struct fib_nh *fib_nh;
+ struct fib_info *fi;
+ int i;
+
+ fi = fen_info->fi;
+ log_dev = (fi && fi->fib_nhs) ? fi->fib_nh->fib_nh_dev : sw_nb_pf_netdev;
+ if (log_dev)
+ netdev_info(log_dev, "%s: FIB event=%lu dst=%#x dstlen=%u type=%u\n",
+ __func__, event, fen_info->dst, fen_info->dst_len,
+ fen_info->type);
+
+ if (!fi)
+ return;
+
+ fib_nh = fi->fib_nh;
+ for (i = 0; i < fi->fib_nhs; i++, fib_nh++) {
+ if (!fib_nh->fib_nh_dev)
+ continue;
+ netdev_info(fib_nh->fib_nh_dev,
+ "%s: dev=%s saddr=%#x gw=%#x\n",
+ __func__, fib_nh->fib_nh_dev->name,
+ fib_nh->nh_saddr, fib_nh->fib_nh_gw4);
+ }
+}
+
+#define SWITCH_NB_FIB_EVENT_DUMP(...) \
+ sw_nb_fib_event_dump(__VA_ARGS__)
+
+int sw_nb_fib_event_to_otx2_event(int event, struct net_device *netdev)
+{
+ switch (event) {
+ case FIB_EVENT_ENTRY_REPLACE:
+ return OTX2_FIB_ENTRY_REPLACE;
+ case FIB_EVENT_ENTRY_ADD:
+ return OTX2_FIB_ENTRY_ADD;
+ case FIB_EVENT_ENTRY_DEL:
+ return OTX2_FIB_ENTRY_DEL;
+ default:
+ break;
+ }
+
+ netdev_err(netdev, "Wrong FIB event %d\n", event);
+ return -1;
+}
+
+static int sw_nb_fib_event(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct fib_notifier_info *info = ptr;
+
+ switch (event) {
+ case FIB_EVENT_ENTRY_REPLACE:
+ case FIB_EVENT_ENTRY_ADD:
+ case FIB_EVENT_ENTRY_DEL:
+ break;
+ default:
+ if (sw_nb_pf_netdev)
+ netdev_dbg(sw_nb_pf_netdev,
+ "%s: Won't process FIB event %lu\n",
+ __func__, event);
+ return NOTIFY_DONE;
+ }
+
+ switch (info->family) {
+ case AF_INET:
+ return sw_nb_v4_fib_event(nb, event, ptr);
+#if IS_ENABLED(CONFIG_IPV6)
+ case AF_INET6:
+ return sw_nb_v6_fib_event(nb, event, ptr);
+#endif
+ default:
+ break;
+ }
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block sw_nb_fib = {
+ .notifier_call = sw_nb_fib_event,
+};
+
+static int sw_nb_net_event(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct neighbour *n = ptr;
+
+ if (!sw_nb_is_valid_dev(n->dev))
+ return NOTIFY_DONE;
+
+ if (event != NETEVENT_NEIGH_UPDATE)
+ return NOTIFY_DONE;
+
+ switch (n->tbl->family) {
+ case AF_INET:
+ return sw_nb_net_v4_neigh_update(nb, event, ptr);
+#if IS_ENABLED(CONFIG_IPV6)
+ case AF_INET6:
+ return sw_nb_net_v6_neigh_update(nb, event, ptr);
+#endif
+ default:
+ break;
+ }
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block sw_nb_netevent = {
+ .notifier_call = sw_nb_net_event,
+
+};
+
+int sw_nb_inetaddr_event_to_otx2_event(int event, struct net_device *netdev)
+{
+ switch (event) {
+ case NETDEV_CHANGE:
+ return OTX2_DEV_CHANGE;
+ case NETDEV_UP:
+ return OTX2_DEV_UP;
+ case NETDEV_DOWN:
+ return OTX2_DEV_DOWN;
+ default:
+ break;
+ }
+ netdev_dbg(netdev, "%s: Wrong interaddr event %d\n",
+ __func__, event);
+ return -1;
+}
+
+static struct notifier_block sw_nb_v4_inetaddr = {
+ .notifier_call = sw_nb_v4_inetaddr_event,
+};
+
+#if IS_ENABLED(CONFIG_IPV6)
+static struct notifier_block sw_nb_v6_inetaddr = {
+ .notifier_call = sw_nb_v6_inetaddr_event,
+};
+#endif
+
+static int sw_nb_netdev_event(struct notifier_block *unused,
+ unsigned long event, void *ptr)
+{
+ struct net_device *dev = netdev_notifier_info_to_dev(ptr);
+ struct in_device *idev;
+ struct inet6_dev *i6dev;
+
+ if (event != NETDEV_CHANGE &&
+ event != NETDEV_UP &&
+ event != NETDEV_DOWN) {
+ return NOTIFY_DONE;
+ }
+
+ if (!sw_nb_is_valid_dev(dev))
+ return NOTIFY_DONE;
+
+ idev = __in_dev_get_rtnl(dev);
+ if (idev)
+ sw_nb_v4_netdev_event(unused, event, ptr);
+
+#if IS_ENABLED(CONFIG_IPV6)
+ i6dev = __in6_dev_get(dev);
+ if (i6dev)
+ sw_nb_v6_netdev_event(unused, event, ptr);
+#endif
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block sw_nb_netdev = {
+ .notifier_call = sw_nb_netdev_event,
+};
+
+int sw_nb_unregister(struct net_device *netdev)
+{
+ int err;
+
+ err = unregister_switchdev_notifier(&sw_nb_fdb);
+
+ if (err)
+ netdev_err(netdev, "Failed to unregister switchdev nb\n");
+
+ err = unregister_fib_notifier(&init_net, &sw_nb_fib);
+ if (err)
+ netdev_err(netdev, "Failed to unregister fib nb\n");
+
+ err = unregister_netevent_notifier(&sw_nb_netevent);
+ if (err)
+ netdev_err(netdev, "Failed to unregister netevent\n");
+
+ err = unregister_inetaddr_notifier(&sw_nb_v4_inetaddr);
+ if (err)
+ netdev_err(netdev, "Failed to unregister addr event\n");
+
+#if IS_ENABLED(CONFIG_IPV6)
+ err = unregister_inet6addr_notifier(&sw_nb_v6_inetaddr);
+ if (err)
+ netdev_err(netdev, "Failed to unregister addr event\n");
+#endif
+
+ err = unregister_netdevice_notifier(&sw_nb_netdev);
+ if (err)
+ netdev_err(netdev, "Failed to unregister netdev notifier\n");
+
+ sw_fl_deinit();
+ sw_fib_deinit();
+ sw_fdb_deinit();
+
+ sw_nb_pf_netdev = NULL;
+
return 0;
}
+EXPORT_SYMBOL(sw_nb_unregister);
+
+int sw_nb_register(struct net_device *netdev)
+{
+ int err;
+
+ sw_nb_pf_netdev = netdev;
+
+ sw_fdb_init();
+ sw_fib_init();
+ sw_fl_init();
+
+ err = register_switchdev_notifier(&sw_nb_fdb);
+ if (err) {
+ netdev_err(netdev, "Failed to register switchdev nb\n");
+ sw_nb_pf_netdev = NULL;
+ return err;
+ }
+
+ err = register_fib_notifier(&init_net, &sw_nb_fib, NULL, NULL);
+ if (err) {
+ netdev_err(netdev, "Failed to register fb notifier block\n");
+ goto err1;
+ }
+
+ err = register_netevent_notifier(&sw_nb_netevent);
+ if (err) {
+ netdev_err(netdev, "Failed to register netevent\n");
+ goto err2;
+ }
+
+#if IS_ENABLED(CONFIG_IPV6)
+ err = register_inet6addr_notifier(&sw_nb_v6_inetaddr);
+ if (err) {
+ netdev_err(netdev, "Failed to register addr event\n");
+ goto err3;
+ }
+#endif
+
+ err = register_inetaddr_notifier(&sw_nb_v4_inetaddr);
+ if (err) {
+ netdev_err(netdev, "Failed to register addr event\n");
+ goto err4;
+ }
+
+ err = register_netdevice_notifier(&sw_nb_netdev);
+ if (err) {
+ netdev_err(netdev, "Failed to register netdevice nb\n");
+ goto err5;
+ }
+
+ return 0;
+
+err5:
+ unregister_inetaddr_notifier(&sw_nb_v4_inetaddr);
+
+err4:
+#if IS_ENABLED(CONFIG_IPV6)
+ unregister_inet6addr_notifier(&sw_nb_v6_inetaddr);
+
+err3:
+#endif
+ unregister_netevent_notifier(&sw_nb_netevent);
+
+err2:
+ unregister_fib_notifier(&init_net, &sw_nb_fib);
+
+err1:
+ unregister_switchdev_notifier(&sw_nb_fdb);
+
+ sw_fl_deinit();
+ sw_fib_deinit();
+ sw_fdb_deinit();
+
+ sw_nb_pf_netdev = NULL;
+ return err;
+}
+EXPORT_SYMBOL(sw_nb_register);
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.h b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.h
index 5f744cc3ecbb..b0ce10ed25d4 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.h
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.h
@@ -7,7 +7,29 @@
#ifndef SW_NB_H_
#define SW_NB_H_
-int sw_nb_register(void);
-int sw_nb_unregister(void);
+enum {
+ OTX2_DEV_UP = 1,
+ OTX2_DEV_DOWN,
+ OTX2_DEV_CHANGE,
+ OTX2_NEIGH_UPDATE,
+ OTX2_FIB_ENTRY_REPLACE,
+ OTX2_FIB_ENTRY_ADD,
+ OTX2_FIB_ENTRY_DEL,
+ OTX2_FIB_ENTRY_APPEND,
+ OTX2_CMD_MAX,
+};
-#endif // SW_NB_H_
+int sw_nb_register(struct net_device *netdev);
+int sw_nb_unregister(struct net_device *netdev);
+bool sw_nb_is_valid_dev(struct net_device *netdev);
+
+int otx2_mbox_up_handler_af2pf_fdb_refresh(struct otx2_nic *pf,
+ struct af2pf_fdb_refresh_req *req,
+ struct msg_rsp *rsp);
+
+bool sw_nb_is_cavium_dev(struct net_device *netdev);
+int sw_nb_fib_event_to_otx2_event(int event, struct net_device *netdev);
+int sw_nb_inetaddr_event_to_otx2_event(int event, struct net_device *netdev);
+
+const char *sw_nb_get_cmd2str(int cmd);
+#endif // SW_NB_H__
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.c b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.c
new file mode 100644
index 000000000000..947dafe586a0
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.c
@@ -0,0 +1,333 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Marvell RVU switch driver
+ *
+ * Copyright (C) 2026 Marvell.
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <net/switchdev.h>
+#include <net/netevent.h>
+#include <net/arp.h>
+#include <net/route.h>
+#include <linux/inetdevice.h>
+
+#include "../otx2_reg.h"
+#include "../otx2_common.h"
+#include "../otx2_struct.h"
+#include "../cn10k.h"
+#include "sw_nb.h"
+#include "sw_fdb.h"
+#include "sw_fib.h"
+#include "sw_fl.h"
+#include "sw_nb.h"
+#include "sw_nb_v4.h"
+
+int sw_nb_v4_netdev_event(struct notifier_block *unused,
+ unsigned long event, void *ptr)
+{
+ struct net_device *dev = netdev_notifier_info_to_dev(ptr);
+ struct netdev_hw_addr *dev_addr;
+ struct net_device *pf_dev;
+ struct in_ifaddr *ifa;
+ struct fib_entry *entry;
+ struct in_device *idev;
+ struct otx2_nic *pf;
+ struct list_head *iter;
+ struct net_device *lower;
+
+ idev = __in_dev_get_rtnl(dev);
+ if (!idev || !idev->ifa_list)
+ return NOTIFY_DONE;
+
+ ifa = rtnl_dereference(idev->ifa_list);
+
+ entry = kcalloc(1, sizeof(*entry), GFP_KERNEL);
+ if (!entry)
+ return NOTIFY_DONE;
+
+ entry->cmd = sw_nb_inetaddr_event_to_otx2_event(event, dev);
+ entry->dst = (__force u32)htonl((__force u32)ifa->ifa_address);
+ entry->dst_len = 32;
+ entry->mac_valid = 1;
+ entry->host = 1;
+
+ pf_dev = dev;
+ if (netif_is_bridge_master(dev)) {
+ entry->bridge = 1;
+ netdev_for_each_lower_dev(dev, lower, iter) {
+ pf_dev = lower;
+ break;
+ }
+ } else if (is_vlan_dev(dev)) {
+ entry->vlan_valid = 1;
+ pf_dev = vlan_dev_real_dev(dev);
+ entry->vlan_tag = vlan_dev_vlan_id(dev);
+ }
+
+ pf = netdev_priv(pf_dev);
+ entry->port_id = pf->pcifunc;
+
+ for_each_dev_addr(dev, dev_addr) {
+ ether_addr_copy(entry->mac, dev_addr->addr);
+ break;
+ }
+
+ netdev_dbg(dev, "%s: pushing netdev event from HOST interface address %#x, %pM, dev=%s\n",
+ __func__, entry->dst, entry->mac, dev->name);
+ kfree(entry);
+
+ return NOTIFY_DONE;
+}
+
+int sw_nb_v4_inetaddr_event(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct in_ifaddr *ifa = (struct in_ifaddr *)ptr;
+ struct net_device *dev = ifa->ifa_dev->dev;
+ struct net_device *lower, *pf_dev;
+ struct netdev_hw_addr *dev_addr;
+ struct fib_entry *entry;
+ struct in_device *idev;
+ struct list_head *iter;
+ struct otx2_nic *pf;
+
+ if (event != NETDEV_CHANGE &&
+ event != NETDEV_UP &&
+ event != NETDEV_DOWN) {
+ return NOTIFY_DONE;
+ }
+
+ idev = __in_dev_get_rtnl(dev);
+ if (!idev || !idev->ifa_list)
+ return NOTIFY_DONE;
+
+ entry = kcalloc(1, sizeof(*entry), GFP_ATOMIC);
+ entry->cmd = sw_nb_inetaddr_event_to_otx2_event(event, dev);
+ entry->dst = (__force u32)htonl((__force u32)ifa->ifa_address);
+ entry->dst_len = 32;
+ entry->mac_valid = 1;
+ entry->host = 1;
+
+ pf_dev = dev;
+ if (netif_is_bridge_master(dev)) {
+ entry->bridge = 1;
+ netdev_for_each_lower_dev(dev, lower, iter) {
+ pf_dev = lower;
+ break;
+ }
+ } else if (is_vlan_dev(dev)) {
+ entry->vlan_valid = 1;
+ pf_dev = vlan_dev_real_dev(dev);
+ entry->vlan_tag = vlan_dev_vlan_id(dev);
+ }
+
+ pf = netdev_priv(pf_dev);
+ entry->port_id = pf->pcifunc;
+
+ for_each_dev_addr(dev, dev_addr) {
+ ether_addr_copy(entry->mac, dev_addr->addr);
+ break;
+ }
+
+ netdev_dbg(dev, "%s: pushing inetaddr event from HOST interface address %#x, %pM, %s\n",
+ __func__, entry->dst, entry->mac, dev->name);
+
+ kfree(entry);
+ return NOTIFY_DONE;
+}
+
+int sw_nb_v4_fib_event(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct fib_entry_notifier_info *fen_info = ptr;
+ struct fib_entry *entries, *iter;
+ struct net_device *dev, *pf_dev = NULL;
+ struct netdev_hw_addr *dev_addr;
+ struct net_device *lower;
+ struct list_head *lh;
+ struct neighbour *neigh;
+ struct fib_nh *fib_nh;
+ struct fib_info *fi;
+ struct otx2_nic *pf;
+ u32 *haddr;
+ int hcnt = 0;
+ int cnt, i;
+
+ /* Process only UNICAST routes add or del */
+ if (fen_info->type != RTN_UNICAST)
+ return NOTIFY_DONE;
+
+ fi = fen_info->fi;
+ if (!fi)
+ return NOTIFY_DONE;
+
+ if (fi->fib_nh_is_v6) {
+ struct net_device *log_dev = (fi->fib_nhs > 0) ?
+ fi->fib_nh->fib_nh_dev : NULL;
+
+ if (log_dev)
+ netdev_dbg(log_dev, "%s: Received v6 notification\n",
+ __func__);
+ return NOTIFY_DONE;
+ }
+
+ entries = kcalloc(fi->fib_nhs, sizeof(*entries), GFP_ATOMIC);
+ if (!entries)
+ return NOTIFY_DONE;
+
+ haddr = kcalloc(fi->fib_nhs, sizeof(u32), GFP_ATOMIC);
+ if (!haddr) {
+ kfree(entries);
+ return NOTIFY_DONE;
+ }
+
+ iter = entries;
+ fib_nh = fi->fib_nh;
+ for (i = 0; i < fi->fib_nhs; i++, fib_nh++) {
+ dev = fib_nh->fib_nh_dev;
+
+ if (!dev)
+ continue;
+
+ if (dev->type != ARPHRD_ETHER)
+ continue;
+
+ if (!sw_nb_is_valid_dev(dev))
+ continue;
+
+ iter->cmd = sw_nb_fib_event_to_otx2_event(event, dev);
+ iter->dst = fen_info->dst;
+ iter->dst_len = fen_info->dst_len;
+ iter->gw = (__force u32)htonl((__force u32)fib_nh->fib_nh_gw4);
+
+ netdev_dbg(dev, "%s: FIB route Rule cmd=%lld dst=%#x dst_len=%d gw=%#x\n",
+ __func__, iter->cmd, iter->dst, iter->dst_len, iter->gw);
+
+ pf_dev = dev;
+ if (netif_is_bridge_master(dev)) {
+ iter->bridge = 1;
+ netdev_for_each_lower_dev(dev, lower, lh) {
+ pf_dev = lower;
+ break;
+ }
+ } else if (is_vlan_dev(dev)) {
+ iter->vlan_valid = 1;
+ pf_dev = vlan_dev_real_dev(dev);
+ iter->vlan_tag = vlan_dev_vlan_id(dev);
+ }
+
+ pf = netdev_priv(pf_dev);
+ iter->port_id = pf->pcifunc;
+
+ if (!fib_nh->fib_nh_gw4) {
+ if (iter->dst || iter->dst_len)
+ iter++;
+
+ continue;
+ }
+ iter->gw_valid = 1;
+
+ if (fib_nh->nh_saddr)
+ haddr[hcnt++] = (__force u32)fib_nh->nh_saddr;
+
+ rcu_read_lock();
+ neigh = ip_neigh_gw4(fib_nh->fib_nh_dev, fib_nh->fib_nh_gw4);
+ if (!neigh) {
+ rcu_read_unlock();
+ iter++;
+ continue;
+ }
+
+ if (is_valid_ether_addr(neigh->ha)) {
+ iter->mac_valid = 1;
+ ether_addr_copy(iter->mac, neigh->ha);
+ }
+
+ iter++;
+ rcu_read_unlock();
+ }
+
+ cnt = iter - entries;
+ if (!cnt)
+ return NOTIFY_DONE;
+
+ netdev_dbg(pf_dev, "pf_dev is %s cnt=%d\n", pf_dev->name, cnt);
+ kfree(entries);
+
+ if (!hcnt)
+ return NOTIFY_DONE;
+
+ entries = kcalloc(hcnt, sizeof(*entries), GFP_ATOMIC);
+ if (!entries)
+ return NOTIFY_DONE;
+
+ iter = entries;
+
+ for (i = 0; i < hcnt; i++, iter++) {
+ iter->cmd = sw_nb_fib_event_to_otx2_event(event, pf_dev);
+ iter->dst = (__force u32)htonl(haddr[i]);
+ iter->dst_len = 32;
+ iter->mac_valid = 1;
+ iter->host = 1;
+ iter->port_id = pf->pcifunc;
+
+ for_each_dev_addr(pf_dev, dev_addr) {
+ ether_addr_copy(iter->mac, dev_addr->addr);
+ break;
+ }
+
+ netdev_dbg(pf_dev, "%s: FIB host Rule cmd=%lld dst=%#x dst_len=%d gw=%#x %s\n",
+ __func__, iter->cmd, iter->dst, iter->dst_len, iter->gw, pf_dev->name);
+ }
+ kfree(entries);
+ kfree(haddr);
+ return NOTIFY_DONE;
+}
+
+int sw_nb_net_v4_neigh_update(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct net_device *lower, *pf_dev;
+ struct neighbour *n = ptr;
+ struct fib_entry *entry;
+ struct list_head *iter;
+ struct otx2_nic *pf;
+
+ if (n->tbl != &arp_tbl)
+ return NOTIFY_DONE;
+
+ entry = kcalloc(1, sizeof(*entry), GFP_ATOMIC);
+ entry->cmd = OTX2_NEIGH_UPDATE;
+ entry->dst = (__force u32)htonl(*(u32 *)n->primary_key);
+ entry->dst_len = n->tbl->key_len * 8;
+ entry->mac_valid = 1;
+ entry->nud_state = n->nud_state;
+ neigh_ha_snapshot(entry->mac, n, n->dev);
+ ether_addr_copy(entry->mac, n->ha);
+
+ pf_dev = n->dev;
+
+ rcu_read_lock();
+ if (netif_is_bridge_master(n->dev)) {
+ entry->bridge = 1;
+ /* Get first lower device thru which we can send MBOX to AF */
+ netdev_for_each_lower_dev(n->dev, lower, iter) {
+ pf_dev = lower;
+ break;
+ }
+ } else if (is_vlan_dev(n->dev)) {
+ entry->vlan_valid = 1;
+ pf_dev = vlan_dev_real_dev(n->dev);
+ entry->vlan_tag = vlan_dev_vlan_id(n->dev);
+ }
+
+ pf = netdev_priv(pf_dev);
+ entry->port_id = pf->pcifunc;
+
+ rcu_read_unlock();
+
+ kfree(entry);
+ return NOTIFY_DONE;
+}
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.h b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.h
new file mode 100644
index 000000000000..c6dbf4b93a9a
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Marvell switch driver
+ *
+ * Copyright (C) 2026 Marvell.
+ *
+ */
+#ifndef SW_NB_V4_H_
+#define SW_NB_V4_H_
+
+int sw_nb_v4_fib_event(struct notifier_block *nb,
+ unsigned long event, void *ptr);
+
+int sw_nb_net_v4_neigh_update(struct notifier_block *nb,
+ unsigned long event, void *ptr);
+
+int sw_nb_v4_inetaddr_event(struct notifier_block *nb,
+ unsigned long event, void *ptr);
+
+int sw_nb_v4_netdev_event(struct notifier_block *unused,
+ unsigned long event, void *ptr);
+#endif // SW_NB_V4_H__
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v6.c b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v6.c
new file mode 100644
index 000000000000..cc908f565d24
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v6.c
@@ -0,0 +1,246 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Marvell RVU switch driver
+ *
+ * Copyright (C) 2026 Marvell.
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <net/switchdev.h>
+#include <net/netevent.h>
+#include <net/arp.h>
+#include <net/route.h>
+#include <linux/inetdevice.h>
+#include <net/addrconf.h>
+#include <net/ip6_fib.h>
+#include <net/nexthop.h>
+
+#include "../otx2_reg.h"
+#include "../otx2_common.h"
+#include "../otx2_struct.h"
+#include "../cn10k.h"
+#include "sw_nb.h"
+#include "sw_fdb.h"
+#include "sw_fib.h"
+#include "sw_fl.h"
+#include "sw_nb.h"
+#include "sw_nb_v6.h"
+
+#if IS_ENABLED(CONFIG_IPV6)
+
+int sw_nb_v6_netdev_event(struct notifier_block *unused,
+ unsigned long event, void *ptr)
+{
+ struct net_device *dev = netdev_notifier_info_to_dev(ptr);
+ struct netdev_hw_addr *dev_addr;
+ struct inet6_ifaddr *ifp;
+ struct fib_entry *entry;
+ struct inet6_dev *i6dev;
+ struct otx2_nic *pf;
+
+ i6dev = __in6_dev_get(dev);
+
+ rcu_read_lock();
+ ifp = list_first_entry_or_null(&i6dev->addr_list,
+ struct inet6_ifaddr, if_list);
+ if (!ifp) {
+ rcu_read_unlock();
+ return NOTIFY_DONE;
+ }
+
+ if (ipv6_addr_type(&ifp->addr) & IPV6_ADDR_LINKLOCAL) {
+ rcu_read_unlock();
+ return NOTIFY_DONE;
+ }
+
+ pf = netdev_priv(dev);
+
+ entry = kcalloc(1, sizeof(*entry), GFP_KERNEL);
+ entry->cmd = sw_nb_inetaddr_event_to_otx2_event(event, dev);
+ memcpy(entry->dst6, &ifp->addr, sizeof(entry->dst6));
+ entry->dst6_plen = ifp->prefix_len;
+ entry->host = 1;
+ entry->ipv6 = 1;
+ entry->port_id = pf->pcifunc;
+
+ for_each_dev_addr(dev, dev_addr) {
+ entry->mac_valid = 1;
+ ether_addr_copy(entry->mac, dev_addr->addr);
+ break;
+ }
+
+ netdev_dbg(dev, "netdev event %pM plen=%u mac=%pM\n",
+ &ifp->addr, ifp->prefix_len, entry->mac);
+ rcu_read_unlock();
+ kfree(entry);
+ return NOTIFY_DONE;
+}
+
+int sw_nb_v6_fib_event(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct fib6_entry_notifier_info *f6_eni;
+ struct fib_notifier_info *info = ptr;
+ struct net_device *fib_dev;
+ struct fib_entry *entry;
+ struct fib6_info *f6i;
+ struct neighbour *neigh;
+ struct fib6_nh *nh6;
+ struct otx2_nic *pf;
+ struct rt6key *key;
+
+ f6_eni = container_of(info, struct fib6_entry_notifier_info, info);
+ f6i = f6_eni->rt;
+
+ fib_dev = fib6_info_nh_dev(f6i);
+
+ if (!fib_dev)
+ return NOTIFY_DONE;
+
+ if (fib_dev->type != ARPHRD_ETHER)
+ return NOTIFY_DONE;
+
+ if (!sw_nb_is_cavium_dev(fib_dev))
+ return NOTIFY_DONE;
+
+ if (f6i->fib6_type != RTN_UNICAST)
+ return NOTIFY_DONE;
+
+ key = &f6i->fib6_dst;
+ /* TODO: vlan and bridge support */
+ if (ipv6_addr_type(&key->addr) & IPV6_ADDR_LINKLOCAL)
+ return NOTIFY_DONE;
+
+ netdev_dbg(fib_dev, "fib6dst rt6key.addr=%pI6c len=%u\n", &key->addr,
+ key->plen);
+
+ netdev_dbg(fib_dev, "fib6flags=%#x proto=%u type=%u\n",
+ f6i->fib6_flags, f6i->fib6_protocol, f6i->fib6_type);
+
+ nh6 = f6i->nh ? nexthop_fib6_nh(f6i->nh) : f6i->fib6_nh;
+ netdev_dbg(nh6->fib_nh_dev ? nh6->fib_nh_dev : fib_dev,
+ "nh family=%u dev=%s gw=%pI6c gwfamily=%u\n",
+ nh6->fib_nh_family,
+ nh6->fib_nh_dev ? nh6->fib_nh_dev->name : "No dev",
+ &nh6->fib_nh_gw6, nh6->fib_nh_gw_family);
+
+ pf = netdev_priv(fib_dev);
+
+ entry = kcalloc(1, sizeof(*entry), GFP_ATOMIC);
+ if (!entry)
+ return NOTIFY_DONE;
+
+ entry->cmd = sw_nb_fib_event_to_otx2_event(event, fib_dev);
+ entry->ipv6 = 1;
+ entry->port_id = pf->pcifunc;
+ memcpy(entry->dst6, &key->addr, sizeof(entry->dst6));
+ entry->dst6_plen = key->plen;
+
+ memcpy(entry->gw6, &nh6->fib_nh_gw6, sizeof(nh6->fib_nh_gw6));
+ entry->gw_valid = !!(ipv6_addr_type(&nh6->fib_nh_gw6) & IPV6_ADDR_UNICAST);
+
+ rcu_read_lock();
+ neigh = ip_neigh_gw6(fib_dev, &nh6->fib_nh_gw6);
+ if (!neigh) {
+ rcu_read_unlock();
+ kfree(entry);
+ return NOTIFY_DONE;
+ }
+
+ if (is_valid_ether_addr(neigh->ha)) {
+ entry->mac_valid = 1;
+ ether_addr_copy(entry->mac, neigh->ha);
+ netdev_dbg(fib_dev, "fib found MAC=%pM\n", entry->mac);
+ }
+
+ rcu_read_unlock();
+ kfree(entry);
+
+ return NOTIFY_DONE;
+}
+
+int sw_nb_net_v6_neigh_update(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct neighbour *n = ptr;
+ struct fib_entry *entry;
+ struct net_device *pf_dev;
+ struct otx2_nic *pf;
+
+ if (n->tbl != &nd_tbl)
+ return NOTIFY_DONE;
+
+ if (ipv6_addr_type((struct in6_addr *)n->primary_key) & IPV6_ADDR_LINKLOCAL)
+ return NOTIFY_DONE;
+
+ pf_dev = n->dev;
+ pf = netdev_priv(pf_dev);
+
+ entry = kcalloc(1, sizeof(*entry), GFP_ATOMIC);
+ entry->cmd = OTX2_NEIGH_UPDATE;
+
+ entry->dst6_plen = n->tbl->key_len * 8;
+ memcpy(entry->dst6, (struct in6_addr *)n->primary_key,
+ sizeof(entry->dst6));
+ entry->ipv6 = 1;
+ entry->nud_state = n->nud_state;
+ ether_addr_copy(entry->mac, n->ha);
+ entry->mac_valid = 1;
+ entry->port_id = pf->pcifunc;
+
+ netdev_dbg(n->dev, "v6 neigh update %pI6 mac=%pM plen=%u\n",
+ n->primary_key, n->ha, n->tbl->key_len * 8);
+ kfree(entry);
+
+ return NOTIFY_DONE;
+}
+
+int sw_nb_v6_inetaddr_event(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct inet6_ifaddr *ifa6 = (struct inet6_ifaddr *)ptr;
+ struct net_device *dev = ifa6->idev->dev;
+ struct netdev_hw_addr *dev_addr;
+ struct fib_entry *entry;
+ struct otx2_nic *pf;
+
+ if (event != NETDEV_CHANGE &&
+ event != NETDEV_UP &&
+ event != NETDEV_DOWN) {
+ return NOTIFY_DONE;
+ }
+
+ if (dev->type != ARPHRD_ETHER)
+ return NOTIFY_DONE;
+
+ if (!sw_nb_is_cavium_dev(dev))
+ return NOTIFY_DONE;
+
+ if (ipv6_addr_type(&ifa6->addr) & IPV6_ADDR_LINKLOCAL)
+ return NOTIFY_DONE;
+
+ pf = netdev_priv(dev);
+
+ entry = kcalloc(1, sizeof(*entry), GFP_ATOMIC);
+ entry->cmd = sw_nb_inetaddr_event_to_otx2_event(event, dev);
+ memcpy(entry->dst6, &ifa6->addr, sizeof(entry->dst6));
+ entry->dst6_plen = ifa6->prefix_len;
+ entry->mac_valid = 1;
+ entry->host = 1;
+ entry->ipv6 = 1;
+ entry->port_id = pf->pcifunc;
+
+ for_each_dev_addr(dev, dev_addr) {
+ ether_addr_copy(entry->mac, dev_addr->addr);
+ entry->mac_valid = 1;
+ break;
+ }
+
+ netdev_dbg(dev, "inetaddr addr=%pI6c len=%u %pM\n",
+ &ifa6->addr, ifa6->prefix_len, entry->mac);
+ kfree(entry);
+
+ return NOTIFY_DONE;
+}
+#endif
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v6.h b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v6.h
new file mode 100644
index 000000000000..f73efc98c311
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v6.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Marvell switch driver
+ *
+ * Copyright (C) 2026 Marvell.
+ *
+ */
+#ifndef SW_NB_V6_H_
+#define SW_NB_V6_H_
+
+int sw_nb_v6_fib_event(struct notifier_block *nb,
+ unsigned long event, void *ptr);
+
+int sw_nb_net_v6_neigh_update(struct notifier_block *nb,
+ unsigned long event, void *ptr);
+
+int sw_nb_v6_inetaddr_event(struct notifier_block *nb,
+ unsigned long event, void *ptr);
+
+int sw_nb_v6_netdev_event(struct notifier_block *unused,
+ unsigned long event, void *ptr);
+#endif // SW_NB_V6_H__
--
2.43.0
^ permalink raw reply related
* [PATCH v2 net-next 7/9] octeontx2: plumb bridge FDB updates through AF and switchdev
From: Ratheesh Kannoth @ 2026-07-02 4:50 UTC (permalink / raw)
To: linux-kernel, netdev
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, sgoutham,
Ratheesh Kannoth
In-Reply-To: <20260702045026.2914748-1-rkannoth@marvell.com>
Handle switchdev FDB add and delete notifications on the PF by queuing
work that sends fdb_notify mailbox messages to the AF. The AF queues
those updates and pushes L2 rules toward the switchdev image with
af2swdev notify messages when firmware is ready.
Teach the AF swdev2af path to initialize L2 offload workqueues on
firmware up/down and to accept refresh requests that enqueue FDB
entries for AF to PF mailbox delivery. Add an AF to PF (and VF) upstream
message for FDB refresh, handle it in the VF driver, and treat it like
the CGX link event when acknowledging mailbox completion in the AF.
On refresh, invoke the switchdev notifier so the host bridge can learn
the updated FDB entry.
Signed-off-by: Ratheesh Kannoth <rkannoth@marvell.com>
---
.../net/ethernet/marvell/octeontx2/af/rvu.c | 4 +
.../marvell/octeontx2/af/switch/rvu_sw.c | 21 +-
.../marvell/octeontx2/af/switch/rvu_sw.h | 1 +
.../marvell/octeontx2/af/switch/rvu_sw_l2.c | 324 ++++++++++++++++++
.../marvell/octeontx2/af/switch/rvu_sw_l2.h | 3 +
.../ethernet/marvell/octeontx2/nic/otx2_vf.c | 17 +
.../marvell/octeontx2/nic/switch/sw_fdb.c | 149 ++++++++
.../marvell/octeontx2/nic/switch/sw_fdb.h | 1 +
.../marvell/octeontx2/nic/switch/sw_nb.c | 2 +
9 files changed, 521 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu.c
index 6e10a58a0421..0f751555338e 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu.c
+++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu.c
@@ -23,6 +23,7 @@
#include "cn20k/reg.h"
#include "cn20k/api.h"
#include "cn20k/npc.h"
+#include "switch/rvu_sw.h"
#define DRV_NAME "rvu_af"
#define DRV_STRING "Marvell OcteonTX2 RVU Admin Function Driver"
@@ -2579,6 +2580,7 @@ static void __rvu_mbox_up_handler(struct rvu_work *mwork, int type)
switch (msg->id) {
case MBOX_MSG_CGX_LINK_EVENT:
+ case MBOX_MSG_AF2PF_FDB_REFRESH:
break;
default:
if (msg->rc)
@@ -3821,6 +3823,8 @@ static void rvu_remove(struct pci_dev *pdev)
{
struct rvu *rvu = pci_get_drvdata(pdev);
+ rvu_sw_shutdown();
+
rvu_dbg_exit(rvu);
rvu_unregister_dl(rvu);
rvu_unregister_interrupts(rvu);
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.c b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.c
index 28f8281e9219..6fc13aeff45f 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.c
+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.c
@@ -8,6 +8,8 @@
#include <linux/bitfield.h>
#include "rvu.h"
#include "rvu_sw.h"
+#include "rvu_sw_l2.h"
+#include "rvu_sw_fl.h"
u32 rvu_sw_port_id(struct rvu *rvu, u16 pcifunc)
{
@@ -26,5 +28,22 @@ int rvu_mbox_handler_swdev2af_notify(struct rvu *rvu,
struct swdev2af_notify_req *req,
struct msg_rsp *rsp)
{
- return 0;
+ int rc = 0;
+
+ switch (req->msg_type) {
+ case SWDEV2AF_MSG_TYPE_FW_STATUS:
+ rc = rvu_sw_l2_init_offl_wq(rvu, req->pcifunc, req->fw_up);
+ break;
+
+ case SWDEV2AF_MSG_TYPE_REFRESH_FDB:
+ rc = rvu_sw_l2_fdb_list_entry_add(rvu, req->pcifunc, req->mac);
+ break;
+ }
+
+ return rc;
+}
+
+void rvu_sw_shutdown(void)
+{
+ rvu_sw_l2_shutdown();
}
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.h b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.h
index 847a8da60d0a..fb24e0a79ff9 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.h
+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.h
@@ -10,5 +10,6 @@
/* RVU Switch */
u32 rvu_sw_port_id(struct rvu *rvu, u16 pcifunc);
+void rvu_sw_shutdown(void);
#endif
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l2.c b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l2.c
index 5f805bfa81ed..bd0fe13b1fc9 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l2.c
+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l2.c
@@ -4,11 +4,335 @@
* Copyright (C) 2026 Marvell.
*
*/
+
+#include <linux/bitfield.h>
#include "rvu.h"
+#include "rvu_sw.h"
+#include "rvu_sw_l2.h"
+
+#define M(_name, _id, _fn_name, _req_type, _rsp_type) \
+static struct _req_type __maybe_unused \
+*otx2_mbox_alloc_msg_ ## _fn_name(struct rvu *rvu, int devid) \
+{ \
+ struct _req_type *req; \
+ \
+ req = (struct _req_type *)otx2_mbox_alloc_msg_rsp( \
+ &rvu->afpf_wq_info.mbox_up, devid, sizeof(struct _req_type), \
+ sizeof(struct _rsp_type)); \
+ if (!req) \
+ return NULL; \
+ req->hdr.sig = OTX2_MBOX_REQ_SIG; \
+ req->hdr.id = _id; \
+ return req; \
+}
+MBOX_UP_AF2SWDEV_MESSAGES
+MBOX_UP_AF2PF_FDB_REFRESH_MESSAGES
+#undef M
+
+struct l2_entry {
+ struct list_head list;
+ u64 flags;
+ u32 port_id;
+ u8 mac[ETH_ALEN];
+};
+
+static DEFINE_MUTEX(l2_offl_list_lock);
+static LIST_HEAD(l2_offl_lh);
+
+static DEFINE_MUTEX(fdb_refresh_list_lock);
+static LIST_HEAD(fdb_refresh_lh);
+
+struct rvu_sw_l2_work {
+ struct rvu *rvu;
+ struct work_struct work;
+};
+
+/* Work queue for switchdev message handling. There is only
+ * on switch HW per SoC, so one instance of each type of
+ * workqueue is enough.
+ */
+static struct rvu_sw_l2_work l2_offl_work;
+static struct workqueue_struct *rvu_sw_l2_offl_wq;
+
+static struct rvu_sw_l2_work fdb_refresh_work;
+static struct workqueue_struct *fdb_refresh_wq;
+
+/* High-frequency link state transitions or aggressive FDB
+ * aging intervals can induce rapid fdb churn. To prevent
+ * thrashing, inhibit hardware offloading of these transient
+ * forwarding states to the switching ASIC.
+ */
+static void rvu_sw_l2_offl_cancel_add_if_del_reqs_exist(u8 *mac)
+{
+ struct l2_entry *entry, *tmp;
+
+ mutex_lock(&l2_offl_list_lock);
+ list_for_each_entry_safe(entry, tmp, &l2_offl_lh, list) {
+ if (!ether_addr_equal(mac, entry->mac))
+ continue;
+
+ if (!(entry->flags & FDB_DEL))
+ continue;
+
+ list_del_init(&entry->list);
+ kfree(entry);
+ break;
+ }
+ mutex_unlock(&l2_offl_list_lock);
+}
+
+static int rvu_sw_l2_offl_rule_push(struct rvu *rvu, struct l2_entry *l2_entry)
+{
+ struct af2swdev_notify_req *req;
+ int swdev_pf;
+
+ swdev_pf = rvu_get_pf(rvu->pdev, rvu->rswitch.pcifunc);
+
+ mutex_lock(&rvu->mbox_lock);
+ req = otx2_mbox_alloc_msg_af2swdev_notify(rvu, swdev_pf);
+ if (!req) {
+ mutex_unlock(&rvu->mbox_lock);
+ return -ENOMEM;
+ }
+
+ ether_addr_copy(req->mac, l2_entry->mac);
+ req->flags = l2_entry->flags;
+ req->port_id = l2_entry->port_id;
+
+ otx2_mbox_wait_for_zero(&rvu->afpf_wq_info.mbox_up, swdev_pf);
+ otx2_mbox_msg_send_up(&rvu->afpf_wq_info.mbox_up, swdev_pf);
+
+ mutex_unlock(&rvu->mbox_lock);
+ return 0;
+}
+
+static int rvu_sw_l2_fdb_refresh(struct rvu *rvu, u16 pcifunc, u8 *mac)
+{
+ struct af2pf_fdb_refresh_req *req;
+ int pf, vidx;
+
+ pf = rvu_get_pf(rvu->pdev, pcifunc);
+
+ mutex_lock(&rvu->mbox_lock);
+
+ if (pf) {
+ req = otx2_mbox_alloc_msg_af2pf_fdb_refresh(rvu, pf);
+ if (!req) {
+ mutex_unlock(&rvu->mbox_lock);
+ return -ENOMEM;
+ }
+
+ req->hdr.pcifunc = pcifunc;
+ ether_addr_copy(req->mac, mac);
+ req->pcifunc = pcifunc;
+
+ otx2_mbox_wait_for_zero(&rvu->afpf_wq_info.mbox_up, pf);
+ otx2_mbox_msg_send_up(&rvu->afpf_wq_info.mbox_up, pf);
+ } else {
+ vidx = pcifunc - 1;
+
+ req = (struct af2pf_fdb_refresh_req *)
+ otx2_mbox_alloc_msg_rsp(&rvu->afvf_wq_info.mbox_up, vidx,
+ sizeof(*req), sizeof(struct msg_rsp));
+ if (!req) {
+ mutex_unlock(&rvu->mbox_lock);
+ return -ENOMEM;
+ }
+ req->hdr.sig = OTX2_MBOX_REQ_SIG;
+ req->hdr.id = MBOX_MSG_AF2PF_FDB_REFRESH;
+
+ req->hdr.pcifunc = pcifunc;
+ ether_addr_copy(req->mac, mac);
+ req->pcifunc = pcifunc;
+
+ otx2_mbox_wait_for_zero(&rvu->afvf_wq_info.mbox_up, vidx);
+ otx2_mbox_msg_send_up(&rvu->afvf_wq_info.mbox_up, vidx);
+ }
+
+ mutex_unlock(&rvu->mbox_lock);
+
+ return 0;
+}
+
+static void rvu_sw_l2_fdb_refresh_wq_handler(struct work_struct *work)
+{
+ struct rvu_sw_l2_work *fdb_work;
+ struct l2_entry *l2_entry;
+
+ fdb_work = container_of(work, struct rvu_sw_l2_work, work);
+
+ while (1) {
+ mutex_lock(&fdb_refresh_list_lock);
+ l2_entry = list_first_entry_or_null(&fdb_refresh_lh,
+ struct l2_entry, list);
+ if (!l2_entry) {
+ mutex_unlock(&fdb_refresh_list_lock);
+ return;
+ }
+
+ list_del_init(&l2_entry->list);
+ mutex_unlock(&fdb_refresh_list_lock);
+
+ rvu_sw_l2_fdb_refresh(fdb_work->rvu, l2_entry->port_id, l2_entry->mac);
+ kfree(l2_entry);
+ }
+}
+
+static void rvu_sw_l2_offl_rule_wq_handler(struct work_struct *work)
+{
+ struct rvu_sw_l2_work *offl_work;
+ struct l2_entry *l2_entry;
+ int budget = 16;
+ bool add_fdb;
+
+ offl_work = container_of(work, struct rvu_sw_l2_work, work);
+
+ while (budget--) {
+ mutex_lock(&l2_offl_list_lock);
+ l2_entry = list_first_entry_or_null(&l2_offl_lh, struct l2_entry, list);
+ if (!l2_entry) {
+ mutex_unlock(&l2_offl_list_lock);
+ return;
+ }
+
+ list_del_init(&l2_entry->list);
+ mutex_unlock(&l2_offl_list_lock);
+
+ add_fdb = !!(l2_entry->flags & FDB_ADD);
+
+ if (add_fdb)
+ rvu_sw_l2_offl_cancel_add_if_del_reqs_exist(l2_entry->mac);
+
+ if (rvu_sw_l2_offl_rule_push(offl_work->rvu, l2_entry))
+ dev_err(offl_work->rvu->dev,
+ "%s: Error to push l2 rule\n",
+ __func__);
+ kfree(l2_entry);
+ }
+
+ if (!list_empty(&l2_offl_lh))
+ queue_work(rvu_sw_l2_offl_wq, &l2_offl_work.work);
+}
+
+static bool fw_is_up;
+
+int rvu_sw_l2_init_offl_wq(struct rvu *rvu, u16 pcifunc, bool fw_up)
+{
+ struct rvu_switch *rswitch;
+
+ rswitch = &rvu->rswitch;
+
+ if (fw_up && !fw_is_up) {
+ /* Switch HW sends an MBOX message to Host once it is ready */
+ rswitch->pcifunc = pcifunc;
+ fw_is_up = true;
+
+ l2_offl_work.rvu = rvu;
+ INIT_WORK(&l2_offl_work.work, rvu_sw_l2_offl_rule_wq_handler);
+ rvu_sw_l2_offl_wq = alloc_workqueue("swdev_rvu_sw_l2_offl_wq", 0, 0);
+ if (!rvu_sw_l2_offl_wq) {
+ dev_err(rvu->dev, "L2 offl workqueue allocation failed\n");
+ return -ENOMEM;
+ }
+
+ fdb_refresh_work.rvu = rvu;
+ INIT_WORK(&fdb_refresh_work.work, rvu_sw_l2_fdb_refresh_wq_handler);
+ fdb_refresh_wq = alloc_workqueue("swdev_fdb_refresg_wq", 0, 0);
+ if (!fdb_refresh_wq) {
+ dev_err(rvu->dev, "fdb refresh workqueue allocation failed\n");
+ return -ENOMEM;
+ }
+
+ rswitch->flags |= RVU_SWITCH_FLAG_FW_READY;
+ return 0;
+ }
+
+ rswitch->flags &= ~RVU_SWITCH_FLAG_FW_READY;
+ flush_work(&l2_offl_work.work);
+ rswitch->pcifunc = -1;
+ return 0;
+}
+
+int rvu_sw_l2_fdb_list_entry_add(struct rvu *rvu, u16 pcifunc, u8 *mac)
+{
+ struct l2_entry *l2_entry;
+
+ l2_entry = kcalloc(1, sizeof(*l2_entry), GFP_KERNEL);
+ if (!l2_entry)
+ return -ENOMEM;
+
+ l2_entry->port_id = pcifunc;
+ ether_addr_copy(l2_entry->mac, mac);
+
+ mutex_lock(&fdb_refresh_list_lock);
+ list_add_tail(&l2_entry->list, &fdb_refresh_lh);
+ mutex_unlock(&fdb_refresh_list_lock);
+
+ queue_work(fdb_refresh_wq, &fdb_refresh_work.work);
+ return 0;
+}
int rvu_mbox_handler_fdb_notify(struct rvu *rvu,
struct fdb_notify_req *req,
struct msg_rsp *rsp)
{
+ struct l2_entry *l2_entry;
+
+ if (!(rvu->rswitch.flags & RVU_SWITCH_FLAG_FW_READY))
+ return 0;
+
+ l2_entry = kcalloc(1, sizeof(*l2_entry), GFP_KERNEL);
+ if (!l2_entry)
+ return -ENOMEM;
+
+ l2_entry->port_id = rvu_sw_port_id(rvu, req->hdr.pcifunc);
+ ether_addr_copy(l2_entry->mac, req->mac);
+ l2_entry->flags = req->flags;
+
+ mutex_lock(&l2_offl_list_lock);
+ list_add_tail(&l2_entry->list, &l2_offl_lh);
+ mutex_unlock(&l2_offl_list_lock);
+
+ queue_work(rvu_sw_l2_offl_wq, &l2_offl_work.work);
+
return 0;
}
+
+void rvu_sw_l2_shutdown(void)
+{
+ struct l2_entry *entry;
+ LIST_HEAD(tlist);
+
+ if (!fdb_refresh_wq)
+ return;
+
+ cancel_work_sync(&fdb_refresh_work.work);
+ destroy_workqueue(fdb_refresh_wq);
+
+ mutex_lock(&fdb_refresh_list_lock);
+ while (1) {
+ entry = list_first_entry_or_null(&fdb_refresh_lh,
+ struct l2_entry, list);
+ if (!entry)
+ break;
+
+ list_del_init(&entry->list);
+ kfree(entry);
+ }
+ mutex_unlock(&fdb_refresh_list_lock);
+
+ cancel_work_sync(&l2_offl_work.work);
+ destroy_workqueue(rvu_sw_l2_offl_wq);
+
+ mutex_lock(&l2_offl_list_lock);
+ while (1) {
+ entry = list_first_entry_or_null(&l2_offl_lh,
+ struct l2_entry, list);
+ if (!entry)
+ break;
+
+ list_del_init(&entry->list);
+ kfree(entry);
+ }
+ mutex_unlock(&l2_offl_list_lock);
+}
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l2.h b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l2.h
index ff28612150c9..6685431d60a2 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l2.h
+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l2.h
@@ -8,4 +8,7 @@
#ifndef RVU_SW_L2_H
#define RVU_SW_L2_H
+int rvu_sw_l2_init_offl_wq(struct rvu *rvu, u16 pcifunc, bool fw_up);
+int rvu_sw_l2_fdb_list_entry_add(struct rvu *rvu, u16 pcifunc, u8 *mac);
+void rvu_sw_l2_shutdown(void);
#endif
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c
index b022f52c6845..4540ffacdddc 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c
@@ -15,6 +15,7 @@
#include "otx2_ptp.h"
#include "cn10k.h"
#include "cn10k_ipsec.h"
+#include "switch/sw_nb.h"
#define DRV_NAME "rvu_nicvf"
#define DRV_STRING "Marvell RVU NIC Virtual Function Driver"
@@ -141,6 +142,22 @@ static int otx2vf_process_mbox_msg_up(struct otx2_nic *vf,
err = otx2_mbox_up_handler_cgx_link_event(
vf, (struct cgx_link_info_msg *)req, rsp);
return err;
+
+ case MBOX_MSG_AF2PF_FDB_REFRESH:
+ rsp = (struct msg_rsp *)otx2_mbox_alloc_msg(&vf->mbox.mbox_up, 0,
+ sizeof(struct msg_rsp));
+ if (!rsp)
+ return -ENOMEM;
+
+ rsp->hdr.id = MBOX_MSG_AF2PF_FDB_REFRESH;
+ rsp->hdr.sig = OTX2_MBOX_RSP_SIG;
+ rsp->hdr.pcifunc = req->pcifunc;
+ rsp->hdr.rc = 0;
+ err = otx2_mbox_up_handler_af2pf_fdb_refresh(vf,
+ (struct af2pf_fdb_refresh_req *)req,
+ rsp);
+ return err;
+
default:
otx2_reply_invalid_msg(&vf->mbox.mbox_up, 0, 0, req->id);
return -ENODEV;
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.c b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.c
index 6842c8d91ffc..327a6efeb526 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.c
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.c
@@ -4,13 +4,162 @@
* Copyright (C) 2026 Marvell.
*
*/
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <net/switchdev.h>
+#include <net/netevent.h>
+#include <net/arp.h>
+
+#include "../otx2_reg.h"
+#include "../otx2_common.h"
+#include "../otx2_struct.h"
+#include "../cn10k.h"
+#include "sw_nb.h"
#include "sw_fdb.h"
+#if !IS_ENABLED(CONFIG_OCTEONTX_SWITCH)
+
+int otx2_mbox_up_handler_af2pf_fdb_refresh(struct otx2_nic *pf,
+ struct af2pf_fdb_refresh_req *req,
+ struct msg_rsp *rsp)
+{
+ return 0;
+}
+
+#else
+
+static DEFINE_SPINLOCK(sw_fdb_llock);
+static LIST_HEAD(sw_fdb_lh);
+
+struct sw_fdb_list_entry {
+ struct list_head list;
+ u64 flags;
+ struct otx2_nic *pf;
+ u8 mac[ETH_ALEN];
+ bool add_fdb;
+};
+
+static struct workqueue_struct *sw_fdb_wq;
+static struct work_struct sw_fdb_work;
+
+static int sw_fdb_add_or_del(struct otx2_nic *pf,
+ const unsigned char *addr,
+ bool add_fdb)
+{
+ struct fdb_notify_req *req;
+ int rc;
+
+ mutex_lock(&pf->mbox.lock);
+ req = otx2_mbox_alloc_msg_fdb_notify(&pf->mbox);
+ if (!req) {
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ ether_addr_copy(req->mac, addr);
+ req->flags = add_fdb ? FDB_ADD : FDB_DEL;
+
+ rc = otx2_sync_mbox_msg(&pf->mbox);
+out:
+ mutex_unlock(&pf->mbox.lock);
+ return rc;
+}
+
+static void sw_fdb_wq_handler(struct work_struct *work)
+{
+ struct sw_fdb_list_entry *entry;
+ LIST_HEAD(tlist);
+
+ spin_lock(&sw_fdb_llock);
+ list_splice_init(&sw_fdb_lh, &tlist);
+ spin_unlock(&sw_fdb_llock);
+
+ while ((entry =
+ list_first_entry_or_null(&tlist,
+ struct sw_fdb_list_entry,
+ list)) != NULL) {
+ list_del_init(&entry->list);
+ if (sw_fdb_add_or_del(entry->pf, entry->mac, entry->add_fdb))
+ netdev_err(entry->pf->netdev,
+ "Error to add/del fdb %pM entry\n",
+ entry->mac);
+ dev_put(entry->pf->netdev);
+ kfree(entry);
+ }
+
+ spin_lock(&sw_fdb_llock);
+ if (!list_empty(&sw_fdb_lh))
+ queue_work(sw_fdb_wq, &sw_fdb_work);
+ spin_unlock(&sw_fdb_llock);
+}
+
+int sw_fdb_add_to_list(struct net_device *dev, u8 *mac, bool add_fdb)
+{
+ struct otx2_nic *pf = netdev_priv(dev);
+ struct sw_fdb_list_entry *entry;
+
+ entry = kcalloc(1, sizeof(*entry), GFP_ATOMIC);
+ if (!entry)
+ return -ENOMEM;
+
+ ether_addr_copy(entry->mac, mac);
+ entry->add_fdb = add_fdb;
+ entry->pf = pf;
+ dev_hold(dev);
+
+ spin_lock(&sw_fdb_llock);
+ list_add_tail(&entry->list, &sw_fdb_lh);
+ queue_work(sw_fdb_wq, &sw_fdb_work);
+ spin_unlock(&sw_fdb_llock);
+
+ return 0;
+}
+
int sw_fdb_init(void)
{
+ INIT_WORK(&sw_fdb_work, sw_fdb_wq_handler);
+ sw_fdb_wq = alloc_workqueue("sw_fdb_wq", 0, 0);
+ if (!sw_fdb_wq)
+ return -ENOMEM;
+
return 0;
}
void sw_fdb_deinit(void)
{
+ struct sw_fdb_list_entry *entry;
+ LIST_HEAD(tlist);
+
+ cancel_work_sync(&sw_fdb_work);
+ destroy_workqueue(sw_fdb_wq);
+
+ spin_lock(&sw_fdb_llock);
+ list_splice_init(&sw_fdb_lh, &tlist);
+ spin_unlock(&sw_fdb_llock);
+
+ while ((entry =
+ list_first_entry_or_null(&tlist,
+ struct sw_fdb_list_entry,
+ list)) != NULL) {
+ list_del_init(&entry->list);
+ dev_put(entry->pf->netdev);
+ kfree(entry);
+ }
+}
+
+int otx2_mbox_up_handler_af2pf_fdb_refresh(struct otx2_nic *pf,
+ struct af2pf_fdb_refresh_req *req,
+ struct msg_rsp *rsp)
+{
+ struct switchdev_notifier_fdb_info item = {0};
+
+ item.addr = req->mac;
+ item.info.dev = pf->netdev;
+ call_switchdev_notifiers(SWITCHDEV_FDB_ADD_TO_BRIDGE,
+ item.info.dev, &item.info, NULL);
+
+ return 0;
}
+#endif
+EXPORT_SYMBOL(otx2_mbox_up_handler_af2pf_fdb_refresh);
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.h b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.h
index d4314d6d3ee4..3b06a77e6b56 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.h
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.h
@@ -7,6 +7,7 @@
#ifndef SW_FDB_H_
#define SW_FDB_H_
+int sw_fdb_add_to_list(struct net_device *dev, u8 *mac, bool add_fdb);
void sw_fdb_deinit(void);
int sw_fdb_init(void);
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c
index fb183265a9aa..99b8b9fdfe8a 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c
@@ -120,11 +120,13 @@ static int sw_nb_fdb_event(struct notifier_block *unused,
case SWITCHDEV_FDB_ADD_TO_DEVICE:
if (fdb_info->is_local)
break;
+ sw_fdb_add_to_list(dev, (u8 *)fdb_info->addr, true);
break;
case SWITCHDEV_FDB_DEL_TO_DEVICE:
if (fdb_info->is_local)
break;
+ sw_fdb_add_to_list(dev, (u8 *)fdb_info->addr, false);
break;
default:
--
2.43.0
^ permalink raw reply related
* [PATCH v2 net-next 8/9] octeontx2: offload host FIB updates to switch via AF mailbox
From: Ratheesh Kannoth @ 2026-07-02 4:50 UTC (permalink / raw)
To: linux-kernel, netdev
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, sgoutham,
Ratheesh Kannoth
In-Reply-To: <20260702045026.2914748-1-rkannoth@marvell.com>
Queue IPv4/IPv6 FIB-derived updates from the switch notifier path
and handle fib_notify in the RVU AF by batching fib_entry
structures and sending them to the switch PF through the
AF-to-switchdev FIB_CMD). Require the switch firmware to
be ready before accepting offload work.
Signed-off-by: Ratheesh Kannoth <rkannoth@marvell.com>
---
.../net/ethernet/marvell/octeontx2/af/mbox.h | 2 +-
.../marvell/octeontx2/af/switch/rvu_sw.c | 2 +
.../marvell/octeontx2/af/switch/rvu_sw_l3.c | 226 ++++++++++++++++++
.../marvell/octeontx2/af/switch/rvu_sw_l3.h | 1 +
.../marvell/octeontx2/nic/switch/sw_fib.c | 123 ++++++++++
.../marvell/octeontx2/nic/switch/sw_fib.h | 3 +
.../marvell/octeontx2/nic/switch/sw_nb.c | 10 +-
.../marvell/octeontx2/nic/switch/sw_nb_v4.c | 24 +-
.../marvell/octeontx2/nic/switch/sw_nb_v6.c | 10 +-
9 files changed, 384 insertions(+), 17 deletions(-)
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/mbox.h b/drivers/net/ethernet/marvell/octeontx2/af/mbox.h
index 28ebf0a8d3d6..ae6f145a0db0 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/mbox.h
+++ b/drivers/net/ethernet/marvell/octeontx2/af/mbox.h
@@ -1968,7 +1968,7 @@ struct af2swdev_notify_req {
};
struct {
u8 cnt;
- struct fib_entry entry[16];
+ struct fib_entry entry[12];
};
struct {
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.c b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.c
index 6fc13aeff45f..6a2a3a03523d 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.c
+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.c
@@ -9,6 +9,7 @@
#include "rvu.h"
#include "rvu_sw.h"
#include "rvu_sw_l2.h"
+#include "rvu_sw_l3.h"
#include "rvu_sw_fl.h"
u32 rvu_sw_port_id(struct rvu *rvu, u16 pcifunc)
@@ -46,4 +47,5 @@ int rvu_mbox_handler_swdev2af_notify(struct rvu *rvu,
void rvu_sw_shutdown(void)
{
rvu_sw_l2_shutdown();
+ rvu_sw_l3_shutdown();
}
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l3.c b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l3.c
index 2b798d5f0644..bd4ec3b20d4a 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l3.c
+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l3.c
@@ -4,11 +4,237 @@
* Copyright (C) 2026 Marvell.
*
*/
+
+#include <linux/bitfield.h>
#include "rvu.h"
+#include "rvu_sw.h"
+#include "rvu_sw_l3.h"
+
+#define M(_name, _id, _fn_name, _req_type, _rsp_type) \
+static struct _req_type __maybe_unused \
+*otx2_mbox_alloc_msg_ ## _fn_name(struct rvu *rvu, int devid) \
+{ \
+ struct _req_type *req; \
+ \
+ req = (struct _req_type *)otx2_mbox_alloc_msg_rsp( \
+ &rvu->afpf_wq_info.mbox_up, devid, sizeof(struct _req_type), \
+ sizeof(struct _rsp_type)); \
+ if (!req) \
+ return NULL; \
+ req->hdr.sig = OTX2_MBOX_REQ_SIG; \
+ req->hdr.id = _id; \
+ return req; \
+}
+MBOX_UP_AF2SWDEV_MESSAGES
+#undef M
+
+static struct workqueue_struct *sw_l3_offl_wq;
+
+struct l3_entry {
+ struct list_head list;
+ struct rvu *rvu;
+ u32 port_id;
+ int cnt;
+ struct fib_entry entry[];
+};
+
+static DEFINE_MUTEX(l3_offl_llock);
+static LIST_HEAD(l3_offl_lh);
+static bool l3_offl_work_running;
+
+static struct workqueue_struct *sw_l3_offl_wq;
+static void sw_l3_offl_work_handler(struct work_struct *work);
+static DECLARE_DELAYED_WORK(l3_offl_work, sw_l3_offl_work_handler);
+
+static int rvu_sw_l3_offl_rule_push(struct list_head *lh)
+{
+ struct af2swdev_notify_req *req;
+ struct fib_entry *entry, *dst;
+ struct l3_entry *l3_entry;
+ struct rvu *rvu;
+ int tot_cnt = 0;
+ int swdev_pf;
+ int sz, cnt;
+ bool rc;
+
+ BUILD_BUG_ON(sizeof(*req) > 1024);
+
+ l3_entry = list_first_entry_or_null(lh, struct l3_entry, list);
+ if (!l3_entry)
+ return 0;
+
+ rvu = l3_entry->rvu;
+ swdev_pf = rvu_get_pf(rvu->pdev, rvu->rswitch.pcifunc);
+
+ mutex_lock(&rvu->mbox_lock);
+ req = otx2_mbox_alloc_msg_af2swdev_notify(rvu, swdev_pf);
+ if (!req) {
+ mutex_unlock(&rvu->mbox_lock);
+
+ while ((l3_entry =
+ list_first_entry_or_null(lh,
+ struct l3_entry, list)) != NULL) {
+ list_del_init(&l3_entry->list);
+ kfree(l3_entry);
+ }
+
+ return -ENOMEM;
+ }
+
+ dst = &req->entry[0];
+ while ((l3_entry =
+ list_first_entry_or_null(lh,
+ struct l3_entry, list)) != NULL) {
+ entry = l3_entry->entry;
+ cnt = l3_entry->cnt;
+ sz = sizeof(*entry) * cnt;
+
+ memcpy(dst, entry, sz);
+ tot_cnt += cnt;
+ dst += cnt;
+
+ list_del_init(&l3_entry->list);
+ kfree(l3_entry);
+ }
+ req->flags = FIB_CMD;
+ req->cnt = tot_cnt;
+
+ rc = otx2_mbox_wait_for_zero(&rvu->afpf_wq_info.mbox_up, swdev_pf);
+ if (rc)
+ otx2_mbox_msg_send_up(&rvu->afpf_wq_info.mbox_up, swdev_pf);
+
+ mutex_unlock(&rvu->mbox_lock);
+ return rc ? 0 : -EFAULT;
+}
+
+static atomic64_t req_cnt;
+static atomic64_t ack_cnt;
+static atomic64_t req_processed;
+static LIST_HEAD(l3_local_lh);
+static int lcnt;
+
+static void sw_l3_offl_work_handler(struct work_struct *work)
+{
+ struct l3_entry *l3_entry;
+ struct list_head l3lh;
+ u64 req, ack, proc;
+
+ INIT_LIST_HEAD(&l3lh);
+
+ mutex_lock(&l3_offl_llock);
+ while (1) {
+ l3_entry = list_first_entry_or_null(&l3_offl_lh, struct l3_entry, list);
+
+ if (!l3_entry)
+ break;
+
+ if (lcnt + l3_entry->cnt > 8 && !list_empty(&l3_local_lh)) {
+ req = atomic64_read(&req_cnt);
+ atomic64_set(&ack_cnt, req);
+ atomic64_set(&req_processed, req);
+ mutex_unlock(&l3_offl_llock);
+ goto process;
+ }
+
+ lcnt += l3_entry->cnt;
+
+ atomic64_inc(&req_cnt);
+ list_del_init(&l3_entry->list);
+ list_add_tail(&l3_entry->list, &l3_local_lh);
+ }
+ mutex_unlock(&l3_offl_llock);
+
+ req = atomic64_read(&req_cnt);
+ ack = atomic64_read(&ack_cnt);
+
+ if (req > ack) {
+ atomic64_set(&ack_cnt, req);
+ queue_delayed_work(sw_l3_offl_wq, &l3_offl_work,
+ msecs_to_jiffies(100));
+ return;
+ }
+
+ proc = atomic64_read(&req_processed);
+ if (req == proc) {
+ queue_delayed_work(sw_l3_offl_wq, &l3_offl_work,
+ msecs_to_jiffies(1000));
+ return;
+ }
+
+ atomic64_set(&req_processed, req);
+
+process:
+ lcnt = 0;
+
+ mutex_lock(&l3_offl_llock);
+ list_splice_init(&l3_local_lh, &l3lh);
+ mutex_unlock(&l3_offl_llock);
+
+ if (rvu_sw_l3_offl_rule_push(&l3lh))
+ pr_err("%s: Error to push rules\n", __func__);
+
+ queue_delayed_work(sw_l3_offl_wq, &l3_offl_work, msecs_to_jiffies(100));
+}
int rvu_mbox_handler_fib_notify(struct rvu *rvu,
struct fib_notify_req *req,
struct msg_rsp *rsp)
{
+ struct l3_entry *l3_entry;
+ int sz;
+
+ if (!(rvu->rswitch.flags & RVU_SWITCH_FLAG_FW_READY))
+ return 0;
+
+ if (req->cnt > 16)
+ return -EINVAL;
+
+ sz = req->cnt * sizeof(struct fib_entry);
+
+ l3_entry = kcalloc(1, sizeof(*l3_entry) + sz, GFP_KERNEL);
+ if (!l3_entry)
+ return -ENOMEM;
+
+ l3_entry->port_id = rvu_sw_port_id(rvu, req->hdr.pcifunc);
+ l3_entry->rvu = rvu;
+ l3_entry->cnt = req->cnt;
+ INIT_LIST_HEAD(&l3_entry->list);
+ memcpy(l3_entry->entry, req->entry, sz);
+
+ mutex_lock(&l3_offl_llock);
+ list_add_tail(&l3_entry->list, &l3_offl_lh);
+ mutex_unlock(&l3_offl_llock);
+
+ if (!l3_offl_work_running) {
+ sw_l3_offl_wq = alloc_workqueue("sw_af_fib_wq", 0, 0);
+ l3_offl_work_running = true;
+ queue_delayed_work(sw_l3_offl_wq, &l3_offl_work,
+ msecs_to_jiffies(1000));
+ }
+
return 0;
}
+
+void rvu_sw_l3_shutdown(void)
+{
+ struct l3_entry *entry;
+ LIST_HEAD(tlist);
+
+ if (!sw_l3_offl_wq)
+ return;
+
+ cancel_delayed_work_sync(&l3_offl_work);
+ destroy_workqueue(sw_l3_offl_wq);
+
+ mutex_lock(&l3_offl_llock);
+ while (1) {
+ entry = list_first_entry_or_null(&l3_offl_lh,
+ struct l3_entry, list);
+ if (!entry)
+ break;
+
+ list_del_init(&entry->list);
+ kfree(entry);
+ }
+ mutex_unlock(&l3_offl_llock);
+}
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l3.h b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l3.h
index ac8c4f9ba5ac..153f1415466d 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l3.h
+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l3.h
@@ -8,4 +8,5 @@
#ifndef RVU_SW_L3_H
#define RVU_SW_L3_H
+void rvu_sw_l3_shutdown(void);
#endif
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fib.c b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fib.c
index 12ddf8119372..54b854aa7ffa 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fib.c
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fib.c
@@ -4,13 +4,136 @@
* Copyright (C) 2026 Marvell.
*
*/
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <net/switchdev.h>
+#include <net/netevent.h>
+#include <net/arp.h>
+#include <net/route.h>
+
+#include "../otx2_reg.h"
+#include "../otx2_common.h"
+#include "../otx2_struct.h"
+#include "../cn10k.h"
+#include "sw_nb.h"
#include "sw_fib.h"
+static DEFINE_SPINLOCK(sw_fib_llock);
+static LIST_HEAD(sw_fib_lh);
+
+static struct workqueue_struct *sw_fib_wq;
+static void sw_fib_work_handler(struct work_struct *work);
+static DECLARE_DELAYED_WORK(sw_fib_work, sw_fib_work_handler);
+
+struct sw_fib_list_entry {
+ struct list_head lh;
+ struct otx2_nic *pf;
+ int cnt;
+ struct fib_entry *entry;
+};
+
+static int sw_fib_notify(struct otx2_nic *pf,
+ int cnt,
+ struct fib_entry *entry)
+{
+ struct fib_notify_req *req;
+ int rc;
+
+ if (cnt > 16)
+ return -EINVAL;
+
+ mutex_lock(&pf->mbox.lock);
+ req = otx2_mbox_alloc_msg_fib_notify(&pf->mbox);
+ if (!req) {
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ req->cnt = cnt;
+ memcpy(req->entry, entry, sizeof(*entry) * cnt);
+
+ rc = otx2_sync_mbox_msg(&pf->mbox);
+out:
+ mutex_unlock(&pf->mbox.lock);
+ return rc;
+}
+
+static void sw_fib_work_handler(struct work_struct *work)
+{
+ struct sw_fib_list_entry *lentry;
+ LIST_HEAD(tlist);
+
+ spin_lock_bh(&sw_fib_llock);
+ list_splice_init(&sw_fib_lh, &tlist);
+ spin_unlock_bh(&sw_fib_llock);
+
+ while ((lentry =
+ list_first_entry_or_null(&tlist,
+ struct sw_fib_list_entry, lh)) != NULL) {
+ list_del_init(&lentry->lh);
+ sw_fib_notify(lentry->pf, lentry->cnt, lentry->entry);
+ kfree(lentry->entry);
+ kfree(lentry);
+ }
+
+ spin_lock_bh(&sw_fib_llock);
+ if (!list_empty(&sw_fib_lh))
+ queue_delayed_work(sw_fib_wq, &sw_fib_work,
+ msecs_to_jiffies(10));
+ spin_unlock_bh(&sw_fib_llock);
+}
+
+int sw_fib_add_to_list(struct net_device *dev,
+ struct fib_entry *entry, int cnt)
+{
+ struct otx2_nic *pf = netdev_priv(dev);
+ struct sw_fib_list_entry *lentry;
+
+ lentry = kcalloc(1, sizeof(*lentry), GFP_ATOMIC);
+ if (!lentry)
+ return -ENOMEM;
+
+ lentry->pf = pf;
+ lentry->cnt = cnt;
+ lentry->entry = entry;
+ INIT_LIST_HEAD(&lentry->lh);
+
+ spin_lock(&sw_fib_llock);
+ list_add_tail(&lentry->lh, &sw_fib_lh);
+ queue_delayed_work(sw_fib_wq, &sw_fib_work,
+ msecs_to_jiffies(10));
+ spin_unlock(&sw_fib_llock);
+
+ return 0;
+}
+
int sw_fib_init(void)
{
+ sw_fib_wq = alloc_workqueue("sw_pf_fib_wq", 0, 0);
+ if (!sw_fib_wq)
+ return -ENOMEM;
+
return 0;
}
void sw_fib_deinit(void)
{
+ struct sw_fib_list_entry *lentry;
+ LIST_HEAD(tlist);
+
+ cancel_delayed_work_sync(&sw_fib_work);
+ destroy_workqueue(sw_fib_wq);
+
+ spin_lock_bh(&sw_fib_llock);
+ list_splice_init(&sw_fib_lh, &tlist);
+ spin_unlock_bh(&sw_fib_llock);
+
+ while ((lentry =
+ list_first_entry_or_null(&tlist,
+ struct sw_fib_list_entry, lh)) != NULL) {
+ list_del_init(&lentry->lh);
+ kfree(lentry->entry);
+ kfree(lentry);
+ }
}
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fib.h b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fib.h
index a51d15c2b80e..50c4fbca81e8 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fib.h
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fib.h
@@ -7,6 +7,9 @@
#ifndef SW_FIB_H_
#define SW_FIB_H_
+int sw_fib_add_to_list(struct net_device *dev,
+ struct fib_entry *entry, int cnt);
+
void sw_fib_deinit(void);
int sw_fib_init(void);
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c
index 99b8b9fdfe8a..61f0ed26adfd 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c
@@ -112,6 +112,7 @@ static int sw_nb_fdb_event(struct notifier_block *unused,
{
struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
struct switchdev_notifier_fdb_info *fdb_info = ptr;
+ int rc = 0;
if (!sw_nb_is_valid_dev(dev))
return NOTIFY_DONE;
@@ -120,19 +121,22 @@ static int sw_nb_fdb_event(struct notifier_block *unused,
case SWITCHDEV_FDB_ADD_TO_DEVICE:
if (fdb_info->is_local)
break;
- sw_fdb_add_to_list(dev, (u8 *)fdb_info->addr, true);
+ rc = sw_fdb_add_to_list(dev, (u8 *)fdb_info->addr, true);
break;
case SWITCHDEV_FDB_DEL_TO_DEVICE:
if (fdb_info->is_local)
break;
- sw_fdb_add_to_list(dev, (u8 *)fdb_info->addr, false);
+ rc = sw_fdb_add_to_list(dev, (u8 *)fdb_info->addr, false);
break;
default:
return NOTIFY_DONE;
}
+ if (rc)
+ netdev_err(dev, "%s: Error to add to list\n", __func__);
+
return NOTIFY_DONE;
}
@@ -301,11 +305,9 @@ static int sw_nb_netdev_event(struct notifier_block *unused,
if (idev)
sw_nb_v4_netdev_event(unused, event, ptr);
-#if IS_ENABLED(CONFIG_IPV6)
i6dev = __in6_dev_get(dev);
if (i6dev)
sw_nb_v6_netdev_event(unused, event, ptr);
-#endif
return NOTIFY_DONE;
}
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.c b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.c
index 947dafe586a0..8fd02edaa90e 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.c
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.c
@@ -76,7 +76,7 @@ int sw_nb_v4_netdev_event(struct notifier_block *unused,
netdev_dbg(dev, "%s: pushing netdev event from HOST interface address %#x, %pM, dev=%s\n",
__func__, entry->dst, entry->mac, dev->name);
- kfree(entry);
+ sw_fib_add_to_list(pf_dev, entry, 1);
return NOTIFY_DONE;
}
@@ -134,7 +134,7 @@ int sw_nb_v4_inetaddr_event(struct notifier_block *nb,
netdev_dbg(dev, "%s: pushing inetaddr event from HOST interface address %#x, %pM, %s\n",
__func__, entry->dst, entry->mac, dev->name);
- kfree(entry);
+ sw_fib_add_to_list(pf_dev, entry, 1);
return NOTIFY_DONE;
}
@@ -250,18 +250,26 @@ int sw_nb_v4_fib_event(struct notifier_block *nb,
}
cnt = iter - entries;
- if (!cnt)
+ if (!cnt) {
+ kfree(entries);
+ kfree(haddr);
return NOTIFY_DONE;
+ }
netdev_dbg(pf_dev, "pf_dev is %s cnt=%d\n", pf_dev->name, cnt);
- kfree(entries);
- if (!hcnt)
+ sw_fib_add_to_list(pf_dev, entries, cnt);
+
+ if (!hcnt) {
+ kfree(haddr);
return NOTIFY_DONE;
+ }
entries = kcalloc(hcnt, sizeof(*entries), GFP_ATOMIC);
- if (!entries)
+ if (!entries) {
+ kfree(haddr);
return NOTIFY_DONE;
+ }
iter = entries;
@@ -281,7 +289,7 @@ int sw_nb_v4_fib_event(struct notifier_block *nb,
netdev_dbg(pf_dev, "%s: FIB host Rule cmd=%lld dst=%#x dst_len=%d gw=%#x %s\n",
__func__, iter->cmd, iter->dst, iter->dst_len, iter->gw, pf_dev->name);
}
- kfree(entries);
+ sw_fib_add_to_list(pf_dev, entries, hcnt);
kfree(haddr);
return NOTIFY_DONE;
}
@@ -326,8 +334,8 @@ int sw_nb_net_v4_neigh_update(struct notifier_block *nb,
pf = netdev_priv(pf_dev);
entry->port_id = pf->pcifunc;
+ sw_fib_add_to_list(pf_dev, entry, 1);
rcu_read_unlock();
- kfree(entry);
return NOTIFY_DONE;
}
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v6.c b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v6.c
index cc908f565d24..49fd6bdf3df4 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v6.c
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v6.c
@@ -72,8 +72,8 @@ int sw_nb_v6_netdev_event(struct notifier_block *unused,
netdev_dbg(dev, "netdev event %pM plen=%u mac=%pM\n",
&ifp->addr, ifp->prefix_len, entry->mac);
+ sw_fib_add_to_list(dev, entry, 1);
rcu_read_unlock();
- kfree(entry);
return NOTIFY_DONE;
}
@@ -154,8 +154,8 @@ int sw_nb_v6_fib_event(struct notifier_block *nb,
netdev_dbg(fib_dev, "fib found MAC=%pM\n", entry->mac);
}
+ sw_fib_add_to_list(fib_dev, entry, 1);
rcu_read_unlock();
- kfree(entry);
return NOTIFY_DONE;
}
@@ -189,9 +189,10 @@ int sw_nb_net_v6_neigh_update(struct notifier_block *nb,
entry->mac_valid = 1;
entry->port_id = pf->pcifunc;
+ sw_fib_add_to_list(pf_dev, entry, 1);
+
netdev_dbg(n->dev, "v6 neigh update %pI6 mac=%pM plen=%u\n",
n->primary_key, n->ha, n->tbl->key_len * 8);
- kfree(entry);
return NOTIFY_DONE;
}
@@ -237,9 +238,10 @@ int sw_nb_v6_inetaddr_event(struct notifier_block *nb,
break;
}
+ sw_fib_add_to_list(dev, entry, 1);
+
netdev_dbg(dev, "inetaddr addr=%pI6c len=%u %pM\n",
&ifa6->addr, ifa6->prefix_len, entry->mac);
- kfree(entry);
return NOTIFY_DONE;
}
--
2.43.0
^ permalink raw reply related
* [PATCH v2 net-next 9/9] octeontx2: add TC flow offload path for switch flows
From: Ratheesh Kannoth @ 2026-07-02 4:50 UTC (permalink / raw)
To: linux-kernel, netdev
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, sgoutham,
Ratheesh Kannoth
In-Reply-To: <20260702045026.2914748-1-rkannoth@marvell.com>
Register an ingress flow-table offload callback that translates TC
flower rules into fl_tuple state, resolves ingress and egress
pcifunc via FIB for accelerated ports, and notifies the RVU AF over
the PF mailbox. The AF forwards flow updates to switchdev and
keeps per-cookie packet counters in sync using NPC MCAM multi-stats
when the switch requests SWDEV2AF refresh.
Signed-off-by: Ratheesh Kannoth <rkannoth@marvell.com>
---
.../marvell/octeontx2/af/switch/rvu_sw.c | 9 +-
.../marvell/octeontx2/af/switch/rvu_sw_fl.c | 305 ++++++++++
.../marvell/octeontx2/af/switch/rvu_sw_fl.h | 2 +
.../ethernet/marvell/octeontx2/nic/Makefile | 2 +-
.../marvell/octeontx2/nic/switch/sw_fl.c | 565 ++++++++++++++++++
.../marvell/octeontx2/nic/switch/sw_fl.h | 2 +
.../marvell/octeontx2/nic/switch/sw_trace.c | 13 +
.../marvell/octeontx2/nic/switch/sw_trace.h | 82 +++
8 files changed, 977 insertions(+), 3 deletions(-)
create mode 100644 drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_trace.c
create mode 100644 drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_trace.h
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.c b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.c
index 6a2a3a03523d..f075da1507e7 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.c
+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.c
@@ -7,14 +7,14 @@
#include <linux/bitfield.h>
#include "rvu.h"
-#include "rvu_sw.h"
#include "rvu_sw_l2.h"
#include "rvu_sw_l3.h"
#include "rvu_sw_fl.h"
+#include "rvu_sw.h"
u32 rvu_sw_port_id(struct rvu *rvu, u16 pcifunc)
{
- u32 port_id;
+ u16 port_id;
u16 rep_id;
rep_id = rvu_rep_get_vlan_id(rvu, pcifunc);
@@ -39,6 +39,10 @@ int rvu_mbox_handler_swdev2af_notify(struct rvu *rvu,
case SWDEV2AF_MSG_TYPE_REFRESH_FDB:
rc = rvu_sw_l2_fdb_list_entry_add(rvu, req->pcifunc, req->mac);
break;
+
+ case SWDEV2AF_MSG_TYPE_REFRESH_FL:
+ rc = rvu_sw_fl_stats_sync2db(rvu, req->fl, req->cnt);
+ break;
}
return rc;
@@ -48,4 +52,5 @@ void rvu_sw_shutdown(void)
{
rvu_sw_l2_shutdown();
rvu_sw_l3_shutdown();
+ rvu_sw_fl_shutdown();
}
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_fl.c b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_fl.c
index 1f8b82a84a5d..6788cfc640c9 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_fl.c
+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_fl.c
@@ -4,12 +4,257 @@
* Copyright (C) 2026 Marvell.
*
*/
+
+#include <linux/bitfield.h>
#include "rvu.h"
+#include "rvu_sw.h"
+#include "rvu_sw_fl.h"
+
+#define M(_name, _id, _fn_name, _req_type, _rsp_type) \
+static struct _req_type __maybe_unused \
+*otx2_mbox_alloc_msg_ ## _fn_name(struct rvu *rvu, int devid) \
+{ \
+ struct _req_type *req; \
+ \
+ req = (struct _req_type *)otx2_mbox_alloc_msg_rsp( \
+ &rvu->afpf_wq_info.mbox_up, devid, sizeof(struct _req_type), \
+ sizeof(struct _rsp_type)); \
+ if (!req) \
+ return NULL; \
+ req->hdr.sig = OTX2_MBOX_REQ_SIG; \
+ req->hdr.id = _id; \
+ return req; \
+}
+
+MBOX_UP_AF2SWDEV_MESSAGES
+#undef M
+
+static struct workqueue_struct *sw_fl_offl_wq;
+
+struct fl_entry {
+ struct list_head list;
+ struct rvu *rvu;
+ u32 port_id;
+ unsigned long cookie;
+ struct fl_tuple tuple;
+ u64 flags;
+ u64 features;
+};
+
+static DEFINE_MUTEX(fl_offl_llock);
+static LIST_HEAD(fl_offl_lh);
+static bool fl_offl_work_running;
+
+static struct workqueue_struct *sw_fl_offl_wq;
+static void sw_fl_offl_work_handler(struct work_struct *work);
+static DECLARE_DELAYED_WORK(fl_offl_work, sw_fl_offl_work_handler);
+
+struct sw_fl_stats_node {
+ struct list_head list;
+ unsigned long cookie;
+ u16 mcam_idx[2];
+ u64 opkts, npkts;
+ bool uni_di;
+};
+
+static LIST_HEAD(sw_fl_stats_lh);
+static DEFINE_MUTEX(sw_fl_stats_lock);
+
+static int
+rvu_sw_fl_stats_sync2db_one_entry(unsigned long cookie, u8 disabled,
+ u16 mcam_idx[2], bool uni_di, u64 pkts)
+{
+ struct sw_fl_stats_node *snode, *tmp;
+
+ mutex_lock(&sw_fl_stats_lock);
+ list_for_each_entry_safe(snode, tmp, &sw_fl_stats_lh, list) {
+ if (snode->cookie != cookie)
+ continue;
+
+ if (disabled) {
+ list_del_init(&snode->list);
+ mutex_unlock(&sw_fl_stats_lock);
+ kfree(snode);
+ return 0;
+ }
+
+ if (snode->uni_di != uni_di) {
+ snode->uni_di = uni_di;
+ snode->mcam_idx[1] = mcam_idx[1];
+ }
+
+ if (snode->opkts == pkts) {
+ mutex_unlock(&sw_fl_stats_lock);
+ return 0;
+ }
+
+ snode->npkts = pkts;
+ mutex_unlock(&sw_fl_stats_lock);
+ return 0;
+ }
+
+ if (disabled) {
+ mutex_unlock(&sw_fl_stats_lock);
+ return 0;
+ }
+
+ snode = kcalloc(1, sizeof(*snode), GFP_KERNEL);
+ if (!snode) {
+ mutex_unlock(&sw_fl_stats_lock);
+ return -ENOMEM;
+ }
+
+ snode->cookie = cookie;
+ snode->mcam_idx[0] = mcam_idx[0];
+ if (!uni_di)
+ snode->mcam_idx[1] = mcam_idx[1];
+
+ snode->npkts = pkts;
+ snode->uni_di = uni_di;
+ INIT_LIST_HEAD(&snode->list);
+
+ list_add_tail(&snode->list, &sw_fl_stats_lh);
+ mutex_unlock(&sw_fl_stats_lock);
+
+ return 0;
+}
+
+int rvu_sw_fl_stats_sync2db(struct rvu *rvu, struct fl_info *fl, int cnt)
+{
+ struct npc_mcam_get_mul_stats_req *req = NULL;
+ struct npc_mcam_get_mul_stats_rsp *rsp = NULL;
+ int tot = 0;
+ u16 i2idx_map[256];
+ int rc = 0;
+ u64 pkts;
+ int idx;
+
+ if (cnt > 256)
+ return -ENOMEM;
+
+ for (int i = 0; i < cnt; i++) {
+ tot++;
+ if (fl[i].uni_di)
+ continue;
+
+ tot++;
+ }
+
+ req = kcalloc(1, sizeof(*req), GFP_KERNEL);
+ if (!req) {
+ rc = -ENOMEM;
+ goto fail;
+ }
+
+ rsp = kcalloc(1, sizeof(*rsp), GFP_KERNEL);
+ if (!rsp) {
+ rc = -ENOMEM;
+ goto fail;
+ }
+
+ req->cnt = tot;
+ idx = 0;
+ for (int i = 0; i < tot; idx++) {
+ i2idx_map[i] = idx;
+ req->entry[i++] = fl[idx].mcam_idx[0];
+ if (fl[idx].uni_di)
+ continue;
+
+ i2idx_map[i] = idx;
+ req->entry[i++] = fl[idx].mcam_idx[1];
+ }
+
+ if (rvu_mbox_handler_npc_mcam_mul_stats(rvu, req, rsp)) {
+ dev_err(rvu->dev, "Error to get multiple stats\n");
+ rc = -EFAULT;
+ goto fail;
+ }
+
+ for (int i = 0; i < tot;) {
+ idx = i2idx_map[i];
+ pkts = rsp->stat[i++];
+
+ if (!fl[idx].uni_di)
+ pkts += rsp->stat[i++];
+
+ rc |= rvu_sw_fl_stats_sync2db_one_entry(fl[idx].cookie, fl[idx].dis,
+ fl[idx].mcam_idx,
+ fl[idx].uni_di, pkts);
+ }
+
+fail:
+ kfree(req);
+ kfree(rsp);
+ return rc;
+}
+
+static int rvu_sw_fl_offl_rule_push(struct fl_entry *fl_entry)
+{
+ struct af2swdev_notify_req *req;
+ struct rvu *rvu;
+ int swdev_pf;
+
+ rvu = fl_entry->rvu;
+ swdev_pf = rvu_get_pf(rvu->pdev, rvu->rswitch.pcifunc);
+
+ mutex_lock(&rvu->mbox_lock);
+ req = otx2_mbox_alloc_msg_af2swdev_notify(rvu, swdev_pf);
+ if (!req) {
+ mutex_unlock(&rvu->mbox_lock);
+ return -ENOMEM;
+ }
+
+ req->tuple = fl_entry->tuple;
+ req->flags = fl_entry->flags;
+ req->cookie = fl_entry->cookie;
+ req->features = fl_entry->features;
+
+ otx2_mbox_wait_for_zero(&rvu->afpf_wq_info.mbox_up, swdev_pf);
+ otx2_mbox_msg_send_up(&rvu->afpf_wq_info.mbox_up, swdev_pf);
+
+ mutex_unlock(&rvu->mbox_lock);
+ return 0;
+}
+
+static void sw_fl_offl_work_handler(struct work_struct *work)
+{
+ struct fl_entry *fl_entry;
+
+ mutex_lock(&fl_offl_llock);
+ fl_entry = list_first_entry_or_null(&fl_offl_lh, struct fl_entry, list);
+ if (!fl_entry) {
+ mutex_unlock(&fl_offl_llock);
+ return;
+ }
+
+ list_del_init(&fl_entry->list);
+ mutex_unlock(&fl_offl_llock);
+
+ rvu_sw_fl_offl_rule_push(fl_entry);
+ kfree(fl_entry);
+
+ mutex_lock(&fl_offl_llock);
+ if (!list_empty(&fl_offl_lh))
+ queue_delayed_work(sw_fl_offl_wq, &fl_offl_work, msecs_to_jiffies(10));
+ mutex_unlock(&fl_offl_llock);
+}
int rvu_mbox_handler_fl_get_stats(struct rvu *rvu,
struct fl_get_stats_req *req,
struct fl_get_stats_rsp *rsp)
{
+ struct sw_fl_stats_node *snode, *tmp;
+
+ mutex_lock(&sw_fl_stats_lock);
+ list_for_each_entry_safe(snode, tmp, &sw_fl_stats_lh, list) {
+ if (snode->cookie != req->cookie)
+ continue;
+
+ rsp->pkts_diff = snode->npkts - snode->opkts;
+ snode->opkts = snode->npkts;
+ break;
+ }
+ mutex_unlock(&sw_fl_stats_lock);
return 0;
}
@@ -17,5 +262,65 @@ int rvu_mbox_handler_fl_notify(struct rvu *rvu,
struct fl_notify_req *req,
struct msg_rsp *rsp)
{
+ struct fl_entry *fl_entry;
+
+ if (!(rvu->rswitch.flags & RVU_SWITCH_FLAG_FW_READY))
+ return 0;
+
+ fl_entry = kcalloc(1, sizeof(*fl_entry), GFP_KERNEL);
+ if (!fl_entry)
+ return -ENOMEM;
+
+ fl_entry->port_id = rvu_sw_port_id(rvu, req->hdr.pcifunc);
+ fl_entry->rvu = rvu;
+ INIT_LIST_HEAD(&fl_entry->list);
+ fl_entry->tuple = req->tuple;
+ fl_entry->cookie = req->cookie;
+ fl_entry->flags = req->flags;
+ fl_entry->features = req->features;
+
+ mutex_lock(&fl_offl_llock);
+ list_add_tail(&fl_entry->list, &fl_offl_lh);
+
+ if (!fl_offl_work_running) {
+ sw_fl_offl_wq = alloc_workqueue("sw_af_fl_wq", 0, 0);
+ if (sw_fl_offl_wq)
+ fl_offl_work_running = true;
+ }
+ mutex_unlock(&fl_offl_llock);
+ queue_delayed_work(sw_fl_offl_wq, &fl_offl_work, msecs_to_jiffies(10));
+
return 0;
}
+
+void rvu_sw_fl_shutdown(void)
+{
+ struct sw_fl_stats_node *snode, *tmp;
+ struct fl_entry *entry;
+ LIST_HEAD(tlist);
+
+ if (!sw_fl_offl_wq)
+ return;
+
+ cancel_delayed_work_sync(&fl_offl_work);
+ destroy_workqueue(sw_fl_offl_wq);
+
+ mutex_lock(&fl_offl_llock);
+ while (1) {
+ entry = list_first_entry_or_null(&fl_offl_lh,
+ struct fl_entry, list);
+ if (!entry)
+ break;
+
+ list_del_init(&entry->list);
+ kfree(entry);
+ }
+ mutex_unlock(&fl_offl_llock);
+
+ mutex_lock(&sw_fl_stats_lock);
+ list_for_each_entry_safe(snode, tmp, &sw_fl_stats_lh, list) {
+ list_del_init(&snode->list);
+ kfree(snode);
+ }
+ mutex_unlock(&sw_fl_stats_lock);
+}
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_fl.h b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_fl.h
index cf3e5b884f77..f117a96fc33e 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_fl.h
+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_fl.h
@@ -7,5 +7,7 @@
#ifndef RVU_SW_FL_H
#define RVU_SW_FL_H
+int rvu_sw_fl_stats_sync2db(struct rvu *rvu, struct fl_info *fl, int cnt);
+void rvu_sw_fl_shutdown(void);
#endif
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/Makefile b/drivers/net/ethernet/marvell/octeontx2/nic/Makefile
index 0e12659876e0..871a55ee3798 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/Makefile
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/Makefile
@@ -10,7 +10,7 @@ obj-$(CONFIG_RVU_ESWITCH) += rvu_rep.o
rvu_nicpf-y := otx2_pf.o otx2_common.o otx2_txrx.o otx2_ethtool.o \
otx2_flows.o otx2_tc.o cn10k.o cn20k.o otx2_dmac_flt.o \
otx2_devlink.o qos_sq.o qos.o otx2_xsk.o \
- switch/sw_fdb.o switch/sw_fl.o
+ switch/sw_fdb.o switch/sw_fl.o switch/sw_trace.o
ifdef CONFIG_OCTEONTX_SWITCH
rvu_nicpf-y += switch/sw_nb.o switch/sw_fib.o \
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.c b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.c
index 36a2359a0a48..3cf4c40b1059 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.c
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.c
@@ -4,13 +4,578 @@
* Copyright (C) 2026 Marvell.
*
*/
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <net/switchdev.h>
+#include <net/netevent.h>
+#include <net/arp.h>
+#include <net/nexthop.h>
+#include <net/netfilter/nf_flow_table.h>
+
+#include "../otx2_reg.h"
+#include "../otx2_common.h"
+#include "../otx2_struct.h"
+#include "../cn10k.h"
+#include "sw_nb.h"
+#include "sw_trace.h"
#include "sw_fl.h"
+#if !IS_ENABLED(CONFIG_OCTEONTX_SWITCH)
+int sw_fl_setup_ft_block_ingress_cb(enum tc_setup_type type,
+ void *type_data, void *cb_priv)
+{
+ return -EOPNOTSUPP;
+}
+
+#else
+
+static DEFINE_SPINLOCK(sw_fl_lock);
+static LIST_HEAD(sw_fl_lh);
+
+struct sw_fl_list_entry {
+ struct list_head list;
+ u64 flags;
+ unsigned long cookie;
+ struct otx2_nic *pf;
+ struct fl_tuple tuple;
+};
+
+static struct workqueue_struct *sw_fl_wq;
+static struct work_struct sw_fl_work;
+
+static int sw_fl_msg_send(struct otx2_nic *pf,
+ struct fl_tuple *tuple,
+ u64 flags,
+ unsigned long cookie)
+{
+ struct fl_notify_req *req;
+ int rc;
+
+ mutex_lock(&pf->mbox.lock);
+ req = otx2_mbox_alloc_msg_fl_notify(&pf->mbox);
+ if (!req) {
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ req->tuple = *tuple;
+ req->flags = flags;
+ req->cookie = cookie;
+
+ rc = otx2_sync_mbox_msg(&pf->mbox);
+out:
+ mutex_unlock(&pf->mbox.lock);
+ return rc;
+}
+
+static void sw_fl_wq_handler(struct work_struct *work)
+{
+ struct sw_fl_list_entry *entry;
+ LIST_HEAD(tlist);
+
+ spin_lock(&sw_fl_lock);
+ list_splice_init(&sw_fl_lh, &tlist);
+ spin_unlock(&sw_fl_lock);
+
+ while ((entry =
+ list_first_entry_or_null(&tlist,
+ struct sw_fl_list_entry,
+ list)) != NULL) {
+ list_del_init(&entry->list);
+ sw_fl_msg_send(entry->pf, &entry->tuple,
+ entry->flags, entry->cookie);
+ dev_put(entry->pf->netdev);
+ kfree(entry);
+ }
+
+ spin_lock(&sw_fl_lock);
+ if (!list_empty(&sw_fl_lh))
+ queue_work(sw_fl_wq, &sw_fl_work);
+ spin_unlock(&sw_fl_lock);
+}
+
+static int
+sw_fl_add_to_list(struct otx2_nic *pf, struct fl_tuple *tuple,
+ unsigned long cookie, bool add_fl)
+{
+ struct sw_fl_list_entry *entry;
+
+ entry = kcalloc(1, sizeof(*entry), GFP_ATOMIC);
+ if (!entry)
+ return -ENOMEM;
+
+ entry->pf = pf;
+ entry->flags = add_fl ? FL_ADD : FL_DEL;
+ if (add_fl)
+ entry->tuple = *tuple;
+ entry->cookie = cookie;
+ entry->tuple.uni_di = netif_is_ovs_port(pf->netdev);
+
+ spin_lock(&sw_fl_lock);
+ dev_hold(pf->netdev);
+ list_add_tail(&entry->list, &sw_fl_lh);
+ queue_work(sw_fl_wq, &sw_fl_work);
+ spin_unlock(&sw_fl_lock);
+
+ return 0;
+}
+
+static int sw_fl_parse_actions(struct otx2_nic *nic,
+ struct flow_action *flow_action,
+ struct flow_cls_offload *f,
+ struct fl_tuple *tuple, u64 *op)
+{
+ struct flow_action_entry *act;
+ struct otx2_nic *out_nic;
+ int err;
+ int used = 0;
+ int i;
+
+ if (!flow_action_has_entries(flow_action))
+ return -EINVAL;
+
+ flow_action_for_each(i, act, flow_action) {
+ if (used >= MANGLE_ARR_SZ) {
+ netdev_err(nic->netdev,
+ "%s: More entries than supported %u\n",
+ __func__, used);
+ return -ENOMEM;
+ }
+
+ switch (act->id) {
+ case FLOW_ACTION_REDIRECT:
+ trace_sw_act_dump(__func__, __LINE__, act->id);
+ tuple->in_pf = nic->pcifunc;
+ out_nic = netdev_priv(act->dev);
+ tuple->xmit_pf = out_nic->pcifunc;
+ *op |= BIT_ULL(FLOW_ACTION_REDIRECT);
+ break;
+
+ case FLOW_ACTION_CT:
+ trace_sw_act_dump(__func__, __LINE__, act->id);
+ err = nf_flow_table_offload_add_cb(act->ct.flow_table,
+ sw_fl_setup_ft_block_ingress_cb,
+ nic);
+ if (err != -EEXIST && err) {
+ netdev_err(nic->netdev,
+ "%s:%d Error to offload flow, err=%d\n",
+ __func__, __LINE__, err);
+ break;
+ }
+
+ *op |= BIT_ULL(FLOW_ACTION_CT);
+ break;
+
+ case FLOW_ACTION_MANGLE:
+ trace_sw_act_dump(__func__, __LINE__, act->id);
+ tuple->mangle[used].type = act->mangle.htype;
+ tuple->mangle[used].val = act->mangle.val;
+ tuple->mangle[used].mask = act->mangle.mask;
+ tuple->mangle[used].offset = act->mangle.offset;
+ tuple->mangle_map[act->mangle.htype] |= BIT(used);
+ used++;
+ break;
+
+ default:
+ trace_sw_act_dump(__func__, __LINE__, act->id);
+ break;
+ }
+ }
+
+ tuple->mangle_cnt = used;
+
+ if (!*op) {
+ netdev_dbg(nic->netdev, "%s:%d Op is not valid\n", __func__, __LINE__);
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int sw_fl_get_route(struct fib_result *res, __be32 addr)
+{
+ struct flowi4 fl4;
+
+ memset(&fl4, 0, sizeof(fl4));
+ fl4.daddr = addr;
+ return fib_lookup(&init_net, &fl4, res, 0);
+}
+
+static int sw_fl_get_pcifunc(struct otx2_nic *pf, __be32 dst, u16 *pcifunc,
+ struct fl_tuple *ftuple, bool is_in_dev)
+{
+ struct fib_nh_common *fib_nhc;
+ struct net_device *dev, *br;
+ struct fib_result res;
+ struct list_head *lh;
+ struct otx2_nic *nic;
+ int err;
+
+ rcu_read_lock();
+
+ err = sw_fl_get_route(&res, dst);
+ if (err) {
+ netdev_err(pf->netdev,
+ "%s:%d Failed to find route to dst %pI4\n",
+ __func__, __LINE__, &dst);
+ goto done;
+ }
+
+ if (res.fi->fib_type != RTN_UNICAST) {
+ netdev_err(pf->netdev,
+ "%s:%d Not unicast route to dst %pi4\n",
+ __func__, __LINE__, &dst);
+ err = -EFAULT;
+ goto done;
+ }
+
+ fib_nhc = fib_info_nhc(res.fi, 0);
+ if (!fib_nhc) {
+ err = -EINVAL;
+ netdev_err(pf->netdev,
+ "%s:%d Could not get fib_nhc for %pI4\n",
+ __func__, __LINE__, &dst);
+ goto done;
+ }
+
+ if (unlikely(netif_is_bridge_master(fib_nhc->nhc_dev))) {
+ br = fib_nhc->nhc_dev;
+
+ if (is_in_dev)
+ ftuple->is_indev_br = 1;
+ else
+ ftuple->is_xdev_br = 1;
+
+ lh = &br->adj_list.lower;
+ if (list_empty(lh)) {
+ netdev_err(pf->netdev,
+ "%s:%d Unable to find any slave device\n",
+ __func__, __LINE__);
+ err = -EINVAL;
+ goto done;
+ }
+ dev = netdev_next_lower_dev_rcu(br, &lh);
+
+ } else {
+ dev = fib_nhc->nhc_dev;
+ }
+
+ if (!dev || !sw_nb_is_valid_dev(dev)) {
+ netdev_err(pf->netdev,
+ "%s:%d flow acceleration support is only for cavium devices\n",
+ __func__, __LINE__);
+ err = -EOPNOTSUPP;
+ goto done;
+ }
+
+ nic = netdev_priv(dev);
+ *pcifunc = nic->pcifunc;
+
+done:
+ rcu_read_unlock();
+ return err;
+}
+
+static int sw_fl_parse_flow(struct otx2_nic *nic, struct flow_cls_offload *f,
+ struct fl_tuple *tuple, u64 *features)
+{
+ struct flow_rule *rule;
+ u8 ip_proto = 0;
+
+ *features = 0;
+
+ rule = flow_cls_offload_flow_rule(f);
+
+ if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_BASIC)) {
+ struct flow_match_basic match;
+
+ flow_rule_match_basic(rule, &match);
+
+ /* All EtherTypes can be matched, no hw limitation */
+
+ if (match.mask->n_proto) {
+ tuple->eth_type = match.key->n_proto;
+ tuple->m_eth_type = match.mask->n_proto;
+ *features |= BIT_ULL(NPC_ETYPE);
+ }
+
+ if (match.mask->ip_proto &&
+ (match.key->ip_proto != IPPROTO_TCP &&
+ match.key->ip_proto != IPPROTO_UDP)) {
+ netdev_dbg(nic->netdev,
+ "ip_proto=%u not supported\n",
+ match.key->ip_proto);
+ }
+
+ if (match.mask->ip_proto)
+ ip_proto = match.key->ip_proto;
+
+ if (ip_proto == IPPROTO_UDP) {
+ *features |= BIT_ULL(NPC_IPPROTO_UDP);
+ } else if (ip_proto == IPPROTO_TCP) {
+ *features |= BIT_ULL(NPC_IPPROTO_TCP);
+ } else {
+ netdev_dbg(nic->netdev,
+ "ip_proto=%u not supported\n",
+ match.key->ip_proto);
+ }
+
+ tuple->proto = ip_proto;
+ }
+
+ if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_ETH_ADDRS)) {
+ struct flow_match_eth_addrs match;
+
+ flow_rule_match_eth_addrs(rule, &match);
+
+ if (!is_zero_ether_addr(match.key->dst) &&
+ is_unicast_ether_addr(match.key->dst)) {
+ ether_addr_copy(tuple->dmac,
+ match.key->dst);
+
+ ether_addr_copy(tuple->m_dmac,
+ match.mask->dst);
+
+ *features |= BIT_ULL(NPC_DMAC);
+ }
+
+ if (!is_zero_ether_addr(match.key->src) &&
+ is_unicast_ether_addr(match.key->src)) {
+ ether_addr_copy(tuple->smac,
+ match.key->src);
+ ether_addr_copy(tuple->m_smac,
+ match.mask->src);
+ *features |= BIT_ULL(NPC_SMAC);
+ }
+ }
+
+ if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IPV4_ADDRS)) {
+ struct flow_match_ipv4_addrs match;
+
+ flow_rule_match_ipv4_addrs(rule, &match);
+
+ if (match.key->dst) {
+ tuple->ip4dst = match.key->dst;
+ tuple->m_ip4dst = match.mask->dst;
+ *features |= BIT_ULL(NPC_DIP_IPV4);
+ }
+
+ if (match.key->src) {
+ tuple->ip4src = match.key->src;
+ tuple->m_ip4src = match.mask->src;
+ *features |= BIT_ULL(NPC_SIP_IPV4);
+ }
+ }
+
+ if (!(*features & BIT_ULL(NPC_DMAC))) {
+ if (!tuple->ip4src || !tuple->ip4dst) {
+ netdev_err(nic->netdev,
+ "%s:%d Invalid src=%pI4 and dst=%pI4 addresses\n",
+ __func__, __LINE__, &tuple->ip4src, &tuple->ip4dst);
+ return -EINVAL;
+ }
+
+ if ((tuple->ip4src & tuple->m_ip4src) == (tuple->ip4dst & tuple->m_ip4dst)) {
+ netdev_err(nic->netdev,
+ "%s:%d Masked values are same; Invalid src=%pI4 and dst=%pI4 addresses\n",
+ __func__, __LINE__, &tuple->ip4src, &tuple->ip4dst);
+ return -EINVAL;
+ }
+ }
+
+ if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_PORTS)) {
+ struct flow_match_ports match;
+
+ flow_rule_match_ports(rule, &match);
+
+ if (ip_proto == IPPROTO_UDP) {
+ if (match.key->dst)
+ *features |= BIT_ULL(NPC_DPORT_UDP);
+
+ if (match.key->src)
+ *features |= BIT_ULL(NPC_SPORT_UDP);
+ } else if (ip_proto == IPPROTO_TCP) {
+ if (match.key->dst)
+ *features |= BIT_ULL(NPC_DPORT_TCP);
+
+ if (match.key->src)
+ *features |= BIT_ULL(NPC_SPORT_TCP);
+ }
+
+ if (match.mask->src) {
+ tuple->sport = match.key->src;
+ tuple->m_sport = match.mask->src;
+ }
+
+ if (match.mask->dst) {
+ tuple->dport = match.key->dst;
+ tuple->m_dport = match.mask->dst;
+ }
+ }
+
+ if (!(*features & (BIT_ULL(NPC_DMAC) |
+ BIT_ULL(NPC_SMAC) |
+ BIT_ULL(NPC_DIP_IPV4) |
+ BIT_ULL(NPC_SIP_IPV4) |
+ BIT_ULL(NPC_DIP_IPV6) |
+ BIT_ULL(NPC_SIP_IPV6) |
+ BIT_ULL(NPC_DPORT_UDP) |
+ BIT_ULL(NPC_SPORT_UDP) |
+ BIT_ULL(NPC_DPORT_TCP) |
+ BIT_ULL(NPC_SPORT_TCP)))) {
+ return -EINVAL;
+ }
+
+ tuple->features = *features;
+
+ return 0;
+}
+
+static int sw_fl_add(struct otx2_nic *nic, struct flow_cls_offload *f)
+{
+ struct fl_tuple tuple = { 0 };
+ struct flow_rule *rule;
+ u64 features = 0;
+ u64 op = 0;
+ int rc;
+
+ rule = flow_cls_offload_flow_rule(f);
+
+ rc = sw_fl_parse_actions(nic, &rule->action, f, &tuple, &op);
+ if (rc)
+ return rc;
+
+ if (op & BIT_ULL(FLOW_ACTION_CT))
+ return 0;
+
+ rc = sw_fl_parse_flow(nic, f, &tuple, &features);
+ if (rc) {
+ trace_sw_fl_dump(__func__, __LINE__, &tuple);
+ return -EFAULT;
+ }
+
+ if (!netif_is_ovs_port(nic->netdev)) {
+ rc = sw_fl_get_pcifunc(nic, tuple.ip4src, &tuple.in_pf,
+ &tuple, true);
+ if (rc) {
+ trace_sw_fl_dump(__func__, __LINE__, &tuple);
+ return rc;
+ }
+
+ rc = sw_fl_get_pcifunc(nic, tuple.ip4dst,
+ &tuple.xmit_pf, &tuple, false);
+ if (rc) {
+ trace_sw_fl_dump(__func__, __LINE__, &tuple);
+ return rc;
+ }
+ }
+
+ trace_sw_fl_dump(__func__, __LINE__, &tuple);
+ sw_fl_add_to_list(nic, &tuple, f->cookie, true);
+ return 0;
+}
+
+static int sw_fl_del(struct otx2_nic *nic, struct flow_cls_offload *f)
+{
+ sw_fl_add_to_list(nic, NULL, f->cookie, false);
+ return 0;
+}
+
+static int sw_fl_stats(struct otx2_nic *nic, struct flow_cls_offload *f)
+{
+ struct fl_get_stats_req *req;
+ struct fl_get_stats_rsp *rsp;
+ u64 pkts_diff;
+ int rc = 0;
+
+ mutex_lock(&nic->mbox.lock);
+
+ req = otx2_mbox_alloc_msg_fl_get_stats(&nic->mbox);
+ if (!req) {
+ netdev_err(nic->netdev,
+ "%s:%d Error happened while mcam alloc req\n",
+ __func__, __LINE__);
+ rc = -ENOMEM;
+ goto fail;
+ }
+ req->cookie = f->cookie;
+
+ rc = otx2_sync_mbox_msg(&nic->mbox);
+ if (rc)
+ goto fail;
+
+ rsp = (struct fl_get_stats_rsp *)otx2_mbox_get_rsp
+ (&nic->mbox.mbox, 0, &req->hdr);
+ pkts_diff = rsp->pkts_diff;
+ mutex_unlock(&nic->mbox.lock);
+
+ if (pkts_diff) {
+ flow_stats_update(&f->stats, 0x0, pkts_diff,
+ 0x0, jiffies,
+ FLOW_ACTION_HW_STATS_IMMEDIATE);
+ }
+ return 0;
+fail:
+ mutex_unlock(&nic->mbox.lock);
+ return rc;
+}
+
+static bool init_done;
+
+int sw_fl_setup_ft_block_ingress_cb(enum tc_setup_type type,
+ void *type_data, void *cb_priv)
+{
+ struct flow_cls_offload *cls = type_data;
+ struct otx2_nic *nic = cb_priv;
+
+ if (!init_done)
+ return 0;
+
+ switch (cls->command) {
+ case FLOW_CLS_REPLACE:
+ return sw_fl_add(nic, cls);
+ case FLOW_CLS_DESTROY:
+ return sw_fl_del(nic, cls);
+ case FLOW_CLS_STATS:
+ return sw_fl_stats(nic, cls);
+ default:
+ break;
+ }
+
+ return -EOPNOTSUPP;
+}
+
int sw_fl_init(void)
{
+ INIT_WORK(&sw_fl_work, sw_fl_wq_handler);
+ sw_fl_wq = alloc_workqueue("sw_fl_wq", 0, 0);
+ if (!sw_fl_wq)
+ return -ENOMEM;
+
+ init_done = true;
return 0;
}
void sw_fl_deinit(void)
{
+ struct sw_fl_list_entry *entry;
+ LIST_HEAD(tlist);
+
+ cancel_work_sync(&sw_fl_work);
+ destroy_workqueue(sw_fl_wq);
+
+ spin_lock(&sw_fl_lock);
+ list_splice_init(&sw_fl_lh, &tlist);
+ spin_unlock(&sw_fl_lock);
+
+ while ((entry =
+ list_first_entry_or_null(&tlist,
+ struct sw_fl_list_entry,
+ list)) != NULL) {
+ list_del_init(&entry->list);
+ dev_put(entry->pf->netdev);
+ kfree(entry);
+ }
}
+#endif
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.h b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.h
index cd018d770a8a..8dd816eb17d2 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.h
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.h
@@ -9,5 +9,7 @@
void sw_fl_deinit(void);
int sw_fl_init(void);
+int sw_fl_setup_ft_block_ingress_cb(enum tc_setup_type type,
+ void *type_data, void *cb_priv);
#endif // SW_FL_H
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_trace.c b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_trace.c
new file mode 100644
index 000000000000..b01e7780ef12
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_trace.c
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Marvell RVU Admin Function driver
+ *
+ * Copyright (C) 2026 Marvell.
+ *
+ */
+
+#define CREATE_TRACE_POINTS
+#if IS_ENABLED(CONFIG_OCTEONTX_SWITCH)
+#include "sw_trace.h"
+EXPORT_TRACEPOINT_SYMBOL(sw_fl_dump);
+EXPORT_TRACEPOINT_SYMBOL(sw_act_dump);
+#endif
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_trace.h b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_trace.h
new file mode 100644
index 000000000000..5949e3dafaed
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_trace.h
@@ -0,0 +1,82 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Marvell RVU Admin Function driver
+ *
+ * Copyright (C) 2026 Marvell.
+ *
+ */
+
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM rvu
+
+#if !defined(SW_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
+#define SW_TRACE_H
+
+#include <linux/types.h>
+#include <linux/tracepoint.h>
+
+#include "mbox.h"
+
+TRACE_EVENT(sw_fl_dump,
+ TP_PROTO(const char *fname, int line, struct fl_tuple *ftuple),
+ TP_ARGS(fname, line, ftuple),
+ TP_STRUCT__entry(__string(f, fname)
+ __field(int, l)
+ __array(u8, smac, ETH_ALEN)
+ __array(u8, dmac, ETH_ALEN)
+ __field(u16, eth_type)
+ __field(u32, sip)
+ __field(u32, dip)
+ __field(u8, ip_proto)
+ __field(u16, sport)
+ __field(u16, dport)
+ __field(u8, uni_di)
+ __field(u16, in_pf)
+ __field(u16, out_pf)
+ ),
+ TP_fast_assign(__assign_str(f);
+ __entry->l = line;
+ memcpy(__entry->smac, ftuple->smac, ETH_ALEN);
+ memcpy(__entry->dmac, ftuple->dmac, ETH_ALEN);
+ __entry->sip = (__force u32)(ftuple->ip4src);
+ __entry->dip = (__force u32)(ftuple->ip4dst);
+ __entry->eth_type = (__force u16)ftuple->eth_type;
+ __entry->ip_proto = ftuple->proto;
+ __entry->sport = (__force u16)(ftuple->sport);
+ __entry->dport = (__force u16)(ftuple->dport);
+ __entry->uni_di = ftuple->uni_di;
+ __entry->in_pf = ftuple->in_pf;
+ __entry->out_pf = ftuple->xmit_pf;
+ ),
+ TP_printk("[%s:%d] %pM %pI4:%u to %pM %pI4:%u eth_type=%#x proto=%u uni=%u in=%#x out=%#x",
+ __get_str(f), __entry->l, __entry->smac, &__entry->sip, __entry->sport,
+ __entry->dmac, &__entry->dip, __entry->dport,
+ ntohs((__force __be16)__entry->eth_type), __entry->ip_proto, __entry->uni_di,
+ __entry->in_pf, __entry->out_pf)
+);
+
+TRACE_EVENT(sw_act_dump,
+ TP_PROTO(const char *fname, int line, u32 act),
+ TP_ARGS(fname, line, act),
+ TP_STRUCT__entry(__string(fname, fname)
+ __field(int, line)
+ __field(u32, act)
+ ),
+
+ TP_fast_assign(__assign_str(fname);
+ __entry->line = line;
+ __entry->act = act;
+ ),
+
+ TP_printk("[%s:%d] %u",
+ __get_str(fname), __entry->line, __entry->act)
+);
+
+#endif
+
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH ../../drivers/net/ethernet/marvell/octeontx2/nic/switch/
+
+#undef TRACE_INCLUDE_FILE
+#define TRACE_INCLUDE_FILE sw_trace
+
+#include <trace/define_trace.h>
--
2.43.0
^ permalink raw reply related
* [PATCH net v2] cxgb4: flower: fix 802.1ad VLAN TPID matching in tc flower filters
From: Harsha M @ 2026-07-02 4:48 UTC (permalink / raw)
To: netdev; +Cc: davem, kuba, edumazet, pabeni, andrew+netdev, bharat, Harsha M
The cxgb4 driver does not correctly handle tc flower filters that
match on an 802.1ad (Q-in-Q) outer VLAN tag. While the VLAN VID is
processed, the specified 802.1ad TPID is not programmed into the
adapter's outer VLAN matching configuration, resulting in incorrect
filter matches.
Fix this by configuring the port-specific OVLAN register with the
requested TPID value, enabling OVLAN matching through the RX control
register, and populating the filter specification with the outer VLAN
VID and valid-bit match fields when an 802.1ad TPID is requested.This
restores correct matching for tc flower filters that specify an 802.1ad
outer VLAN tag
Signed-off-by: Harsha M <h.mahadeva@chelsio.com>
Signed-off-by: Potnuri Bharat Teja <bharat@chelsio.com>
---
v2:
- Compare vlan_tpid with 'cpu_to_be16(ETH_P_8021AD)'
- Program the OVLAN register using the ETH_P_8021AD constant
and TPID mask as 0xffff instead of deriving the value from the
filter key and mask
v1:
https://lore.kernel.org/all/20260629184417.31b21223@kernel.org/
---
.../ethernet/chelsio/cxgb4/cxgb4_tc_flower.c | 61 +++++++++++++------
drivers/net/ethernet/chelsio/cxgb4/t4_regs.h | 3 +
2 files changed, 46 insertions(+), 18 deletions(-)
diff --git a/drivers/net/ethernet/chelsio/cxgb4/cxgb4_tc_flower.c b/drivers/net/ethernet/chelsio/cxgb4/cxgb4_tc_flower.c
index 3307e5042681..8c5cfa6982e7 100644
--- a/drivers/net/ethernet/chelsio/cxgb4/cxgb4_tc_flower.c
+++ b/drivers/net/ethernet/chelsio/cxgb4/cxgb4_tc_flower.c
@@ -40,6 +40,7 @@
#include "cxgb4.h"
#include "cxgb4_filter.h"
#include "cxgb4_tc_flower.h"
+#include "t4_regs.h"
#define STATS_CHECK_PERIOD (HZ / 2)
@@ -266,24 +267,48 @@ static void cxgb4_process_flow_match(struct net_device *dev,
VLAN_PRIO_SHIFT);
vlan_tci_mask = match.mask->vlan_id | (match.mask->vlan_priority <<
VLAN_PRIO_SHIFT);
- fs->val.ivlan = vlan_tci;
- fs->mask.ivlan = vlan_tci_mask;
-
- fs->val.ivlan_vld = 1;
- fs->mask.ivlan_vld = 1;
-
- /* Chelsio adapters use ivlan_vld bit to match vlan packets
- * as 802.1Q. Also, when vlan tag is present in packets,
- * ethtype match is used then to match on ethtype of inner
- * header ie. the header following the vlan header.
- * So, set the ivlan_vld based on ethtype info supplied by
- * TC for vlan packets if its 802.1Q. And then reset the
- * ethtype value else, hw will try to match the supplied
- * ethtype value with ethtype of inner header.
- */
- if (fs->val.ethtype == ETH_P_8021Q) {
- fs->val.ethtype = 0;
- fs->mask.ethtype = 0;
+
+ if (match.key->vlan_tpid == cpu_to_be16(ETH_P_8021AD)) {
+ struct adapter *adap = netdev2adap(dev);
+ u32 ovlan_reg, ctl_reg, val, port_id;
+
+ if (!adap) {
+ netdev_err(dev, "%s: adap not found\n", __func__);
+ return;
+ }
+
+ val = (0xffff << 16) | ETH_P_8021AD;
+ port_id = netdev2pinfo(dev)->port_id;
+ fs->val.ovlan = vlan_tci;
+ fs->mask.ovlan = vlan_tci_mask;
+ fs->val.ovlan_vld = 1;
+ fs->mask.ovlan_vld = 1;
+ ovlan_reg = PORT_REG(port_id, MPS_PORT_RX_OVLAN0_A);
+ ctl_reg = PORT_REG(port_id, MPS_PORT_RX_CTL_A);
+ t4_write_reg(adap, ovlan_reg, val);
+ val = t4_read_reg(adap, ctl_reg);
+ t4_write_reg(adap, ctl_reg, val | 1);
+ t4_tp_wr_bits_indirect(adap, TP_INGRESS_CONFIG_A, 1U << 9, 0);
+ } else {
+ fs->val.ivlan = vlan_tci;
+ fs->mask.ivlan = vlan_tci_mask;
+ fs->val.ivlan_vld = 1;
+ fs->mask.ivlan_vld = 1;
+
+ /* Chelsio adapters use ivlan_vld bit to match vlan packets
+ * as 802.1Q. Also, when vlan tag is present in packets,
+ * ethtype match is used then to match on ethtype of inner
+ * header ie. the header following the vlan header.
+ * So, set the ivlan_vld based on ethtype info supplied by
+ * TC for vlan packets if its 802.1Q. And then reset the
+ * ethtype value else, hw will try to match the supplied
+ * ethtype value with ethtype of inner header.
+ */
+
+ if (fs->val.ethtype == ETH_P_8021Q) {
+ fs->val.ethtype = 0;
+ fs->mask.ethtype = 0;
+ }
}
}
diff --git a/drivers/net/ethernet/chelsio/cxgb4/t4_regs.h b/drivers/net/ethernet/chelsio/cxgb4/t4_regs.h
index 695916ba0405..38c585f3b1ad 100644
--- a/drivers/net/ethernet/chelsio/cxgb4/t4_regs.h
+++ b/drivers/net/ethernet/chelsio/cxgb4/t4_regs.h
@@ -1921,6 +1921,9 @@
#define MAC_PORT_PTP_SUM_LO_A 0x990
#define MAC_PORT_PTP_SUM_HI_A 0x994
+#define MPS_PORT_RX_OVLAN0_A 0x120
+#define MPS_PORT_RX_CTL_A 0X100
+
#define MPS_CMN_CTL_A 0x9000
#define COUNTPAUSEMCRX_S 5
--
2.43.5
^ permalink raw reply related
* [PATCH net v3 0/1] octeontx2-af: Bug fixes for VF RX mode
From: nshettyj @ 2026-07-02 4:56 UTC (permalink / raw)
To: netdev
Cc: linux-kernel, sgoutham, lcherian, gakula, hkelam, sbhatta,
andrew+netdev, davem, edumazet, kuba, pabeni, naveenm, rkannoth,
Kiran Kumar K
From: Kiran Kumar K <kirankumark@marvell.com>
Hello,
The patch resolves an issue where a VF changing its interface
state could inadvertently delete the RX promiscuous and all-multicast
MCAM rules belonging to the host PF.
Changes in v3:
- Drop patch "octeontx2-af: suppress kpu profile loading warning".
- Drop the change based on the Sashiko review comment that both APIs
already use FW_OPT_NO_WARN, making the switch from
request_firmware_direct() to firmware_request_nowarn() unnecessary.
Changes in v2:
- Rebased patch 2/2 to resolve a merge conflict on the net branch.
- Patch 1/2 remains unchanged.
Harman Kalra (1):
octeontx2-af: fix VF bringup affecting PF promiscuous state
drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
--
2.48.1
^ permalink raw reply
* [PATCH net v3 1/1] octeontx2-af: fix VF bringup affecting PF promiscuous state
From: nshettyj @ 2026-07-02 4:56 UTC (permalink / raw)
To: netdev
Cc: linux-kernel, sgoutham, lcherian, gakula, hkelam, sbhatta,
andrew+netdev, davem, edumazet, kuba, pabeni, naveenm, rkannoth,
Harman Kalra, Nitin Shetty J
In-Reply-To: <20260702045616.3002773-1-nshettyj@marvell.com>
From: Harman Kalra <hkalra@marvell.com>
Mbox handling of nix_set_rx_mode for a VF with promiscuous and
all_multi flags set to false causes deletion of the PF's promiscuous
and allmulti MCAM rules. This occurs because the APIs that
enable/disable these rules operate only on the PF, even when the
mbox request is made via a VF interface.
Guard both rvu_npc_enable_allmulti_entry() and
rvu_npc_enable_promisc_entry() disable paths with an is_vf() check so
that a VF bringing up or tearing down its interface cannot inadvertently
clear the PF's MCAM rules.
Fixes: 967db3529eca ("octeontx2-af: add support for multicast/promisc packet replication feature")
Signed-off-by: Harman Kalra <hkalra@marvell.com>
Signed-off-by: Nitin Shetty J <nshettyj@marvell.com>
---
drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c
index d8989395e875..a7e0e0e05ad2 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c
+++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c
@@ -4575,7 +4575,7 @@ int rvu_mbox_handler_nix_set_rx_mode(struct rvu *rvu, struct nix_rx_mode *req,
rvu_npc_install_allmulti_entry(rvu, pcifunc, nixlf,
pfvf->rx_chan_base);
} else {
- if (!nix_rx_multicast)
+ if (!nix_rx_multicast && !is_vf(pcifunc))
rvu_npc_enable_allmulti_entry(rvu, pcifunc, nixlf, false);
}
@@ -4585,7 +4585,7 @@ int rvu_mbox_handler_nix_set_rx_mode(struct rvu *rvu, struct nix_rx_mode *req,
pfvf->rx_chan_base,
pfvf->rx_chan_cnt);
else
- if (!nix_rx_multicast)
+ if (!nix_rx_multicast && !is_vf(pcifunc))
rvu_npc_enable_promisc_entry(rvu, pcifunc, nixlf, false);
return 0;
--
2.48.1
^ permalink raw reply related
* Re: [PATCH v5] bpf: Fix smp_processor_id() call trace for preemptible kernels
From: Alexei Starovoitov @ 2026-07-02 5:40 UTC (permalink / raw)
To: Edward Adam Davis
Cc: Andrii Nakryiko, Andrii Nakryiko, Alexei Starovoitov, bpf,
Daniel Borkmann, Eduard, Emil Tsalapatis, Jiayuan Chen, Jiri Olsa,
LKML, Martin KaFai Lau, Kumar Kartikeya Dwivedi,
Network Development, sashiko, sashiko-reviews, Song Liu,
syzkaller-bugs, Yonghong Song
In-Reply-To: <tencent_30FB52DFC6440984EEF0EEC70C90A7DAC606@qq.com>
On Wed, Jul 1, 2026 at 9:35 PM Edward Adam Davis <eadavis@qq.com> wrote:
>
> On Wed, 1 Jul 2026 12:57:52 -0700, Andrii Nakryiko wrote:
> > > bpf_mem_cache_free_rcu() maybe called in preemptible context, this
> > > will trigger the below warning message:
> > >
> > > BUG: using smp_processor_id() in preemptible [00000000] code: syz.0.17/5820
> > > caller is bpf_mem_cache_free_rcu+0x48/0xc0 kernel/bpf/memalloc.c:954
> > > Call Trace:
> > > check_preemption_disabled+0xd3/0xe0 lib/smp_processor_id.c:47
> > > bpf_mem_cache_free_rcu+0x48/0xc0 kernel/bpf/memalloc.c:954
> > > rhtab_delete_elem+0x185a/0x1b30 kernel/bpf/hashtab.c:2969
> > > __rhtab_map_lookup_and_delete_batch+0x935/0xcb0 kernel/bpf/hashtab.c:3349
> > > bpf_map_do_batch+0x445/0x630 kernel/bpf/syscall.c:-1
> > > __sys_bpf+0x906/0xd90 kernel/bpf/syscall.c:-1
> > >
> > > this_cpu_ptr() requires the caller to prevent task migration.
> > > These helpers currently do not enforce that requirement and may
> > > be invoked from preemptible contexts, leading to accesses to another
> > > CPU's per-CPU cache after migration. Use get_cpu_ptr()/put_cpu_ptr()
> > > to pin the task while accessing the per-CPU allocator state.
> > >
> > > Fixes: 5af6807bdb10 ("bpf: Introduce bpf_mem_free_rcu() similar to kfree_rcu().")
> > > Fixes: 7c8199e24fa0 ("bpf: Introduce any context BPF specific memory allocator.")
> > > Reported-by: syzbot+fd7e415d891073b83e1f@syzkaller.appspotmail.com
> > > Closes: https://syzkaller.appspot.com/bug?extid=fd7e415d891073b83e1f
> > > Signed-off-by: Edward Adam Davis <eadavis@qq.com>
> > > ---
> >
> > from what I can see, bpf_mem_free() is called through bpf kfuncs only,
> > and all BPF programs run with migration disabled. So this seems like a
> > false positive. For per-cpu checking, it should probably check that
> > either preemption is disabled or migration is disabled. tl;dr, there
> > doesn't seem to be any
> Today, I researched the call sites of bpf_mem_free() in detail; it is
> called directly by bpf kfuns only.
>
> Different BPF program types execute in distinct kernel contexts: for
> instance, XDP and TC typically run in non-sleepable networking contexts,
> whereas `raw_tp`, `tracing`, `LSM`, `syscall` and especially sleepable
> BPF programs may run in contexts where preemption and migration are
> enabled. Consequently, any shared BPF code (such as a helper or kfunc)
> that accesses per-CPU data must not rely on the caller having disabled
> migration; instead, it must internally ensure that CPU migration does
> not occur during the access.
>
> Taking into account the CI reviewer's feedback [1] and my further
> investigation into BPF programs as described above, the adjustments
> related to bpf_mem_free() should be retained.
>
> [1]
> https://lore.kernel.org/all/20260630094823.897BD1F00A3D@smtp.kernel.org
> >
> > pw-bot: cr
> >
> > > v1 -> v2: using guard against preemption
> > > v2 -> v3: replace get/put_cpu() to bpf_disable/enable_instrumentation()
> > > v3 -> v4: disable preempt to make this_cpu_ptr() work
> > > v4 -> v5: in mem free disable preemption
> > >
> >
> > maybe throttle your patch resubmission spree a bit?..
> My earlier actions were driven by a desire to keep pace with the CI
> reviewer's response speed.
> Moving forward, I will slow down the cadence of new patch submissions,
> as you suggested.
Don't bother. You don't sound like a human.
Bots are not welcomed here.
^ permalink raw reply
* Re: [PATCH net v3] ipv4: igmp: remove multicast group from hash table on device destruction
From: Kuniyuki Iwashima @ 2026-07-02 5:49 UTC (permalink / raw)
To: yuyanghuang
Cc: davem, dsahern, edumazet, horms, idosch, kuba, linux-kernel,
netdev, pabeni, stable, xiyou.wangcong, Kuniyuki Iwashima
In-Reply-To: <20260701235014.73505-1-yuyanghuang@google.com>
From: Yuyang Huang <yuyanghuang@google.com>
Date: Thu, 2 Jul 2026 08:50:14 +0900
> When a device is destroyed under RTNL, ip_mc_destroy_dev() iterates through
> the multicast list and calls ip_ma_put() on each membership, scheduling
> them for RCU reclamation. However, they are not unlinked from the device's
> multicast hash table (mc_hash).
>
> Since the device remains published in dev->ip_ptr until after
> ip_mc_destroy_dev() completes, concurrent RCU readers traversing mc_hash
> can still locate and access the multicast group after its refcount is
> decremented. If the RCU callback runs and frees the group while a reader is
> accessing it, a use-after-free occurs.
>
> Fix this by unlinking the multicast group from mc_hash using
> ip_mc_hash_remove() before scheduling it for reclamation.
>
> BUG: KASAN: slab-use-after-free in ip_check_mc_rcu+0x149/0x3f0
> Read of size 4 at addr ffff888009bf1408 by task mausezahn/2276
>
> Call Trace:
> <IRQ>
> dump_stack_lvl+0x67/0x90
> print_report+0x175/0x7c0
> kasan_report+0x147/0x180
> ip_check_mc_rcu+0x149/0x3f0
> udp_v4_early_demux+0x36d/0x12d0
> ip_rcv_finish_core+0xb8b/0x1390
> ip_rcv_finish+0x54/0x120
> NF_HOOK+0x213/0x2b0
> __netif_receive_skb+0x126/0x340
> process_backlog+0x4f2/0xf00
> __napi_poll+0x92/0x2c0
> net_rx_action+0x583/0xc60
> handle_softirqs+0x236/0x7f0
> do_softirq+0x57/0x80
> </IRQ>
>
> Allocated by task 2239:
> kasan_save_track+0x3e/0x80
> __kasan_kmalloc+0x72/0x90
> ____ip_mc_inc_group+0x31a/0xa40
> __ip_mc_join_group+0x334/0x3f0
> do_ip_setsockopt+0x16fa/0x2010
> ip_setsockopt+0x3f/0x90
> do_sock_setsockopt+0x1ad/0x300
>
> Freed by task 0:
> kasan_save_track+0x3e/0x80
> kasan_save_free_info+0x40/0x50
> __kasan_slab_free+0x3a/0x60
> __rcu_free_sheaf_prepare+0xd4/0x220
> rcu_free_sheaf+0x36/0x190
> rcu_core+0x8d9/0x12f0
> handle_softirqs+0x236/0x7f0
>
> Fixes: e9897071350b ("igmp: hash a hash table to speedup ip_check_mc_rcu()")
> Cc: stable@vger.kernel.org
> Signed-off-by: Yuyang Huang <yuyanghuang@google.com>
Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
^ permalink raw reply
* Re: [PATCH rdma-next v8] RDMA: Change capability fields in ib_device_attr from int to u32
From: Erni Sri Satya Vennela @ 2026-07-02 6:03 UTC (permalink / raw)
To: Jason Gunthorpe, Leon Romanovsky, mkalderon, zyjzyj2000, sagi,
mgurtovoy, haris.iqbal, jinpu.wang, bvanassche, kbusch,
Jens Axboe, Christoph Hellwig, kch, smfrench, linkinjeon, metze,
tom, trondmy, anna, chuck.lever, jlayton, neil, okorniev, Dai.Ngo,
achender, davem, edumazet, kuba, pabeni, horms, kees, markzhang,
andriy.shevchenko, ebadger, linux-rdma, linux-kernel,
target-devel, linux-nvme, linux-cifs, samba-technical, linux-nfs,
netdev, rds-devel
Cc: Jason Gunthorpe
In-Reply-To: <20260619203107.606359-1-ernis@linux.microsoft.com>
On Fri, Jun 19, 2026 at 01:30:39PM -0700, Erni Sri Satya Vennela wrote:
> The capability counter fields in struct ib_device_attr are declared
> as signed int, but these values are inherently non-negative. Drivers
> maintain their cached caps as u32 and assign them directly into these
> int fields; if a cap exceeds INT_MAX the implicit narrowing yields a
> negative value visible to the IB core.
>
> Change the signed int capability fields to u32 to match the
> underlying nature of the data. Also update consumers across the IB
> core, ULPs, NVMe-oF target, RDS, and NFS/RDMA so the new u32 values
> are not forced back through signed int or u8 via min()/min_t() or
> narrowing local variables.
>
> Suggested-by: Jason Gunthorpe <jgg@nvidia.com>
> Signed-off-by: Erni Sri Satya Vennela <ernis@linux.microsoft.com>
> Acked-by: Stefan Metzmacher <metze@samba.org> # smbdirect
Hi,
Just a friendly follow-up on this patch. The Sashiko review mentioned a
low-priority item, and I'd appreciate any guidance on whether the change
is needed.
https://sashiko.dev/#/patchset/20260619203107.606359-1-ernis%40linux.microsoft.com
Thanks,
Vennela
^ permalink raw reply
* [PATCH] ppp/ppp_{async,synctty}: drop unused {a,}syncppp::bytes_{sent,rcvd}
From: Jiri Slaby (SUSE) @ 2026-07-02 6:04 UTC (permalink / raw)
To: kuba
Cc: linux-kernel, Jiri Slaby (SUSE), Andrew Lunn, David S. Miller,
Eric Dumazet, Paolo Abeni, linux-ppp, netdev
The bytes_sent and bytes_rcvd members of structs asyncppp and syncppp
are not used. Drop them.
Signed-off-by: Jiri Slaby (SUSE) <jirislaby@kernel.org>
Cc: Andrew Lunn <andrew+netdev@lunn.ch>
Cc: "David S. Miller" <davem@davemloft.net>
Cc: Eric Dumazet <edumazet@google.com>
Cc: Jakub Kicinski <kuba@kernel.org>
Cc: Paolo Abeni <pabeni@redhat.com>
---
Cc: linux-ppp@vger.kernel.org
Cc: netdev@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
---
drivers/net/ppp/ppp_async.c | 2 --
drivers/net/ppp/ppp_synctty.c | 2 --
2 files changed, 4 deletions(-)
diff --git a/drivers/net/ppp/ppp_async.c b/drivers/net/ppp/ppp_async.c
index 93a7b0f6c4e7..583426d06381 100644
--- a/drivers/net/ppp/ppp_async.c
+++ b/drivers/net/ppp/ppp_async.c
@@ -49,8 +49,6 @@ struct asyncppp {
unsigned long xmit_flags;
u32 xaccm[8];
u32 raccm;
- unsigned int bytes_sent;
- unsigned int bytes_rcvd;
struct sk_buff *tpkt;
int tpkt_pos;
diff --git a/drivers/net/ppp/ppp_synctty.c b/drivers/net/ppp/ppp_synctty.c
index b7f243b416f8..0b1bd1635c39 100644
--- a/drivers/net/ppp/ppp_synctty.c
+++ b/drivers/net/ppp/ppp_synctty.c
@@ -59,8 +59,6 @@ struct syncppp {
unsigned long xmit_flags;
u32 xaccm[8];
u32 raccm;
- unsigned int bytes_sent;
- unsigned int bytes_rcvd;
struct sk_buff *tpkt;
unsigned long last_xmit;
--
2.54.0
^ permalink raw reply related
* Re: [PATCH v4 1/7] dt-bindings: mtd: jedec,spi-nor: allow the SFDP to be exposed via NVMEM
From: Manikandan.M @ 2026-07-02 6:13 UTC (permalink / raw)
To: linusw, michael
Cc: pratyush, mwalle, takahiro.kuwano, miquel.raynal, richard,
vigneshr, robh, krzk+dt, conor+dt, srini, Nicolas.Ferre,
alexandre.belloni, claudiu.beznea, linux, richardcochran, arnd,
linux-mtd, devicetree, linux-kernel, linux-arm-kernel, netdev
In-Reply-To: <CAD++jLntmnwU3gAQfDn2nd4CQ_7HY6S_kBguVtZvVT1PktFCPw@mail.gmail.com>
On 7/1/26 4:23 PM, Linus Walleij wrote:
> EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe
>
> On Wed, Jul 1, 2026 at 10:34 AM Michael Walle <michael@walle.cc> wrote:
>
>> If I'm correct, this is the old style, see commit bd912c991d2e
>> ("dt-bindings: nvmem: layouts: add fixed-layout"). So it should
>> eventually look like:
>>
>> sfdp {
>> compatible = "jedec,sfdp";
> (...)
>> Also I'm not sure if we really need to add the "nvmem-cells" here.
>> IIRC in MTD it was there to tell a driver to add an nvmem device to
>> an already existing compatible/node.
>>
>> Apart from the MTD case, I've just found qcom,smem-part,yaml which
>> has compatible = "nvmem-cells".
>
> You're right, I was using old information, discard my comments...
> Reviewed-by: Linus Walleij <linusw@kernel.org>
>
> I think my comment in the driver to check for the compatible
> instead of the node name is still valid though.
Thank you Linus Wallejj and Michael.
I will address the driver changes in the next version.
>
> Yours,
> Linus Walleij
--
Thanks and Regards,
Manikandan M.
^ permalink raw reply
* Re: [PATCH v4 1/7] dt-bindings: mtd: jedec,spi-nor: allow the SFDP to be exposed via NVMEM
From: Krzysztof Kozlowski @ 2026-07-02 6:16 UTC (permalink / raw)
To: Manikandan Muralidharan
Cc: pratyush, mwalle, takahiro.kuwano, miquel.raynal, richard,
vigneshr, robh, krzk+dt, conor+dt, srini, nicolas.ferre,
alexandre.belloni, claudiu.beznea, linux, richardcochran, linusw,
arnd, michael, linux-mtd, devicetree, linux-kernel,
linux-arm-kernel, netdev
In-Reply-To: <20260630092406.150587-2-manikandan.m@microchip.com>
On Tue, Jun 30, 2026 at 02:54:00PM +0530, Manikandan Muralidharan wrote:
> Add an optional "sfdp" child node (compatible "jedec,sfdp") that
> describes the SFDP as a read-only NVMEM provider via nvmem.yaml, so its
What is SFDP?
> contents (e.g. a vendor EUI-48/EUI-64) can be read through NVMEM cells.
>
> Signed-off-by: Manikandan Muralidharan <manikandan.m@microchip.com>
> ---
> .../devicetree/bindings/mtd/jedec,spi-nor.yaml | 18 ++++++++++++++++++
> 1 file changed, 18 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/mtd/jedec,spi-nor.yaml b/Documentation/devicetree/bindings/mtd/jedec,spi-nor.yaml
> index 587af4968255..98fd954598ab 100644
> --- a/Documentation/devicetree/bindings/mtd/jedec,spi-nor.yaml
> +++ b/Documentation/devicetree/bindings/mtd/jedec,spi-nor.yaml
> @@ -103,6 +103,20 @@ properties:
> spi-cpol: true
> spi-cpha: true
>
> + sfdp:
> + $ref: /schemas/nvmem/nvmem.yaml#
> + unevaluatedProperties: false
> + description:
> + The Serial Flash Discoverable Parameters (SFDP) tables exposed as a
> + read-only NVMEM device. This allows standard or vendor-specific SFDP
> + data (for example a factory-programmed EUI-48/EUI-64 identifier) to be
> + consumed through NVMEM cells.
> + properties:
> + compatible:
> + const: jedec,sfdp
> + required:
> + - compatible
Where are any resources? What sort of sub-device is represented here by
an empty device node?
Best regards,
Krzysztof
^ permalink raw reply
* Re: [PATCH net v2] net: airoha: fix MIB stats collection to be lossless
From: Aniket Negi @ 2026-07-02 6:21 UTC (permalink / raw)
To: Lorenzo Bianconi
Cc: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, linux-arm-kernel, linux-mediatek, netdev,
linux-kernel, aniket.negi
In-Reply-To: <akWYEf5wusTq9_ol@lore-desk>
> > Signed-off-by: Aniket Negi <aniket.negi03@gmail.com>
>
> Hi Aniket,
>
> just few nits inline. Fixing them:
>
> Acked-by: Lorenzo Bianconi <lorenzo@kernel.org>
>
Ok sure. I'll update.
> > + dev->stats.mib_prev.tx_runt_cnt = val;
> > +
> > + /* tx_len[0]: RUNT (32-bit, delta) + E64 (64-bit, absolute) → {0, 64} bucket.
> > + * Accumulate RUNT delta in tx_runt_accum64, then assign tx_len[0] as
> > + * accum + E64_abs so each call gives the correct combined total.
> > + */
>
> no new-line here.
>
> > +
> > + dev->stats.tx_len[i] = dev->stats.mib_prev.tx_runt_accum64;
>
Sure, You are reffering to both the spaces above and below comment section?. Same for rx_runt and rx_long/tx_long.
I'll update as following:
+ dev->stats.mib_prev.tx_runt_cnt = val;
+ /* tx_len[0]: RUNT (32-bit, delta) + E64 (64-bit, absolute) → {0, 64} bucket.
+ * Accumulate RUNT delta in tx_runt_accum64, then assign tx_len[0] as
+ * accum + E64_abs so each call gives the correct combined total.
+ */
+ dev->stats.tx_len[i] = dev->stats.mib_prev.tx_runt_accum64;
> > + u32 tx_drops;
> > + u32 tx_broadcast;
> > + u32 tx_multicast;
> > + u32 tx_runt_cnt;
>
> u32 tx_runt;
>
> > + u32 tx_long_cnt;
>
> u32 tx_long;
>
> > + u64 tx_runt_accum64;
>
> 64 tx_runt64;
>
> > + u32 rx_drops;
> > + u32 rx_broadcast;
> > + u32 rx_multicast;
> > + u32 rx_errors;
> > + u32 rx_crc_error;
> > + u32 rx_over_errors;
> > + u32 rx_fragment;
> > + u32 rx_jabber;
> > + u32 rx_runt_cnt;
>
> u32 rx_runt;
>
> > + u32 rx_long_cnt;
>
> u32 rx_long;
>
> > + u64 rx_runt_accum64;
>
> u64 rx_runt64;
>
> > + } mib_prev;
> > };
Acked the change name from tx_runt_cnt to tx_run, tx_long_cnt to tx_long, tx_runt_accum64 to tx_runt64. Same for rx counters.
Best Regards,
Aniket Negi
^ permalink raw reply
* [PATCH net-next 0/2] selftests: drv-net: run XDP tests with both IP versions
From: Nimrod Oren @ 2026-07-02 6:23 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Shuah Khan
Cc: Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
John Fastabend, Stanislav Fomichev, Bobby Eshleman,
Willem de Bruijn, Wei Wang, David Wei, Cosmin Ratiu, netdev,
linux-kselftest, linux-kernel, bpf, Nimrod Oren
The driver selftest environment picks a single address family for tests
which use the generic address fields. When both IPv4 and IPv6 are
available it prefers IPv6, which can leave IPv4 paths untested and hide
IPv4-only bugs.
This happened recently in the XDP selftest, where an IPv4 checksum bug
was missed when IPv6 was also configured. XDP traffic handling has
IP-version-specific code paths, and the follow-up discussion concluded
that the XDP tests should run against both IP versions [1].
This series changes NetDrvEpEnv from exposing only the initially selected
address family to allowing tests to switch the generic address fields
between IP versions. xdp.py uses this to run the XDP traffic cases over
both IPv4 and IPv6. Test variants whose required IP version is not
configured are skipped.
[1] https://lore.kernel.org/20260518143138.74839bf9@kernel.org/
Nimrod Oren (2):
selftests: drv-net: allow switching env IP version
selftests: drv-net: xdp: run with both IP versions
.../selftests/drivers/net/lib/py/env.py | 27 ++++--
tools/testing/selftests/drivers/net/xdp.py | 94 +++++++++++++++----
2 files changed, 97 insertions(+), 24 deletions(-)
--
2.45.0
^ permalink raw reply
* [PATCH net-next 1/2] selftests: drv-net: allow switching env IP version
From: Nimrod Oren @ 2026-07-02 6:23 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Shuah Khan
Cc: Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
John Fastabend, Stanislav Fomichev, Bobby Eshleman,
Willem de Bruijn, Wei Wang, David Wei, Cosmin Ratiu, netdev,
linux-kselftest, linux-kernel, bpf, Nimrod Oren, Carolina Jubran,
Dragos Tatulea
In-Reply-To: <20260702062348.2123960-1-noren@nvidia.com>
NetDrvEpEnv picks a single IP version at init time, preferring IPv6 when
both are configured.
Add NetDrvEpEnv.set_ipver() to reselect the IP version and recompute the
derived address fields.
Reviewed-by: Carolina Jubran <cjubran@nvidia.com>
Reviewed-by: Dragos Tatulea <dtatulea@nvidia.com>
Signed-off-by: Nimrod Oren <noren@nvidia.com>
---
.../selftests/drivers/net/lib/py/env.py | 27 ++++++++++++++-----
1 file changed, 20 insertions(+), 7 deletions(-)
diff --git a/tools/testing/selftests/drivers/net/lib/py/env.py b/tools/testing/selftests/drivers/net/lib/py/env.py
index e4ab99b905b1..e4acf3d8333f 100644
--- a/tools/testing/selftests/drivers/net/lib/py/env.py
+++ b/tools/testing/selftests/drivers/net/lib/py/env.py
@@ -159,13 +159,7 @@ class NetDrvEpEnv(NetDrvEnvBase):
self.remote = Remote(kind, args, src_path)
- self.addr_ipver = "6" if self.addr_v["6"] else "4"
- self.addr = self.addr_v[self.addr_ipver]
- self.remote_addr = self.remote_addr_v[self.addr_ipver]
-
- # Bracketed addresses, some commands need IPv6 to be inside []
- self.baddr = f"[{self.addr_v['6']}]" if self.addr_v["6"] else self.addr_v["4"]
- self.remote_baddr = f"[{self.remote_addr_v['6']}]" if self.remote_addr_v["6"] else self.remote_addr_v["4"]
+ self.set_ipver("6" if self.addr_v["6"] else "4")
self.ifname = self.dev['ifname']
self.ifindex = self.dev['ifindex']
@@ -252,6 +246,25 @@ class NetDrvEpEnv(NetDrvEnvBase):
if not self.addr_v[ipver] or not self.remote_addr_v[ipver]:
raise KsftSkipEx(f"Test requires IPv{ipver} connectivity")
+ def set_ipver(self, ipver):
+ """
+ Modify the IP version used by the generic address fields.
+ """
+ if ipver == getattr(self, "addr_ipver", None):
+ return
+
+ self.require_ipver(ipver)
+
+ self.addr_ipver = ipver
+ self.addr = self.addr_v[ipver]
+ self.remote_addr = self.remote_addr_v[ipver]
+
+ # Bracketed addresses, some commands need IPv6 to be inside []
+ self.baddr = (f"[{self.addr_v['6']}]" if ipver == "6"
+ else self.addr_v["4"])
+ self.remote_baddr = (f"[{self.remote_addr_v['6']}]" if ipver == "6"
+ else self.remote_addr_v["4"])
+
def require_nsim(self, nsim_test=True):
"""Require or exclude netdevsim for this test"""
if nsim_test and self._ns is None:
--
2.45.0
^ permalink raw reply related
* [PATCH net-next 2/2] selftests: drv-net: xdp: run with both IP versions
From: Nimrod Oren @ 2026-07-02 6:23 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Shuah Khan
Cc: Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
John Fastabend, Stanislav Fomichev, Bobby Eshleman,
Willem de Bruijn, Wei Wang, David Wei, Cosmin Ratiu, netdev,
linux-kselftest, linux-kernel, bpf, Nimrod Oren, Carolina Jubran,
Dragos Tatulea
In-Reply-To: <20260702062348.2123960-1-noren@nvidia.com>
Parameterize test cases by IP version using @ksft_variants. Set the
environment's IP version at the start of each case using a new local
helper, which also handles restoring the original value via defer().
The last case is left unparameterized because it does not send traffic
or exercise IP-version-specific code.
While here, fix an int vs str comparison bug `if cfg.addr_ipver == 4`.
Suggested-by: Jakub Kicinski <kuba@kernel.org>
Reviewed-by: Carolina Jubran <cjubran@nvidia.com>
Reviewed-by: Dragos Tatulea <dtatulea@nvidia.com>
Signed-off-by: Nimrod Oren <noren@nvidia.com>
---
tools/testing/selftests/drivers/net/xdp.py | 94 ++++++++++++++++++----
1 file changed, 77 insertions(+), 17 deletions(-)
diff --git a/tools/testing/selftests/drivers/net/xdp.py b/tools/testing/selftests/drivers/net/xdp.py
index 2ad5932299e8..0369929f3c51 100755
--- a/tools/testing/selftests/drivers/net/xdp.py
+++ b/tools/testing/selftests/drivers/net/xdp.py
@@ -172,25 +172,45 @@ def _test_pass(cfg, bpf_info, msg_sz):
ksft_eq(stats[XDPStats.RX.value], stats[XDPStats.PASS.value], "RX and PASS stats mismatch")
-def test_xdp_native_pass_sb(cfg):
+_ipvers = [
+ KsftNamedVariant("ipv4", "4"),
+ KsftNamedVariant("ipv6", "6"),
+]
+
+
+def _set_ipver_defer_restore(cfg, ipver):
+ old_ipver = cfg.addr_ipver
+ cfg.set_ipver(ipver)
+ defer(cfg.set_ipver, old_ipver)
+
+
+@ksft_variants(_ipvers)
+def test_xdp_native_pass_sb(cfg, ipver):
"""
Tests the XDP_PASS action for single buffer case.
Args:
cfg: Configuration object containing network settings.
+ ipver: IP version to use ("4" or "6").
"""
+ _set_ipver_defer_restore(cfg, ipver)
+
bpf_info = BPFProgInfo("xdp_prog", "xdp_native.bpf.o", "xdp", 1500)
_test_pass(cfg, bpf_info, 256)
-def test_xdp_native_pass_mb(cfg):
+@ksft_variants(_ipvers)
+def test_xdp_native_pass_mb(cfg, ipver):
"""
Tests the XDP_PASS action for a multi-buff size.
Args:
cfg: Configuration object containing network settings.
+ ipver: IP version to use ("4" or "6").
"""
+ _set_ipver_defer_restore(cfg, ipver)
+
bpf_info = BPFProgInfo("xdp_prog_frags", "xdp_native.bpf.o", "xdp.frags", 9000)
_test_pass(cfg, bpf_info, 8000)
@@ -219,25 +239,33 @@ def _test_drop(cfg, bpf_info, msg_sz):
ksft_eq(stats[XDPStats.RX.value], stats[XDPStats.DROP.value], "RX and DROP stats mismatch")
-def test_xdp_native_drop_sb(cfg):
+@ksft_variants(_ipvers)
+def test_xdp_native_drop_sb(cfg, ipver):
"""
Tests the XDP_DROP action for a signle-buff case.
Args:
cfg: Configuration object containing network settings.
+ ipver: IP version to use ("4" or "6").
"""
+ _set_ipver_defer_restore(cfg, ipver)
+
bpf_info = BPFProgInfo("xdp_prog", "xdp_native.bpf.o", "xdp", 1500)
_test_drop(cfg, bpf_info, 256)
-def test_xdp_native_drop_mb(cfg):
+@ksft_variants(_ipvers)
+def test_xdp_native_drop_mb(cfg, ipver):
"""
Tests the XDP_DROP action for a multi-buff case.
Args:
cfg: Configuration object containing network settings.
+ ipver: IP version to use ("4" or "6").
"""
+ _set_ipver_defer_restore(cfg, ipver)
+
bpf_info = BPFProgInfo("xdp_prog_frags", "xdp_native.bpf.o", "xdp.frags", 9000)
_test_drop(cfg, bpf_info, 8000)
@@ -287,13 +315,17 @@ def _test_xdp_native_tx(cfg, bpf_info, payload_lens):
ksft_eq(stats[XDPStats.TX.value], expected_pkts, "TX stats mismatch")
-def test_xdp_native_tx_sb(cfg):
+@ksft_variants(_ipvers)
+def test_xdp_native_tx_sb(cfg, ipver):
"""
Tests the XDP_TX action for a single-buff case.
Args:
cfg: Configuration object containing network settings.
+ ipver: IP version to use ("4" or "6").
"""
+ _set_ipver_defer_restore(cfg, ipver)
+
bpf_info = BPFProgInfo("xdp_prog", "xdp_native.bpf.o", "xdp", 1500)
# Ensure there's enough room for an ETH / IP / UDP header
@@ -302,13 +334,17 @@ def test_xdp_native_tx_sb(cfg):
_test_xdp_native_tx(cfg, bpf_info, [0, 1500 // 2, 1500 - pkt_hdr_len])
-def test_xdp_native_tx_mb(cfg):
+@ksft_variants(_ipvers)
+def test_xdp_native_tx_mb(cfg, ipver):
"""
Tests the XDP_TX action for a multi-buff case.
Args:
cfg: Configuration object containing network settings.
+ ipver: IP version to use ("4" or "6").
"""
+ _set_ipver_defer_restore(cfg, ipver)
+
bpf_info = BPFProgInfo("xdp_prog_frags", "xdp_native.bpf.o",
"xdp.frags", 9000)
# The first packet ensures we exercise the fragmented code path.
@@ -447,13 +483,17 @@ def _test_xdp_native_tail_adjst(cfg, pkt_sz_lst, offset_lst):
return {"status": "pass"}
-def test_xdp_native_adjst_tail_grow_data(cfg):
+@ksft_variants(_ipvers)
+def test_xdp_native_adjst_tail_grow_data(cfg, ipver):
"""
Tests the XDP tail adjustment by growing packet data.
Args:
cfg: Configuration object containing network settings.
+ ipver: IP version to use ("4" or "6").
"""
+ _set_ipver_defer_restore(cfg, ipver)
+
pkt_sz_lst = [512, 1024, 2048]
offset_lst = [1, 16, 32, 64, 128, 256]
res = _test_xdp_native_tail_adjst(
@@ -465,13 +505,17 @@ def test_xdp_native_adjst_tail_grow_data(cfg):
_validate_res(res, offset_lst, pkt_sz_lst)
-def test_xdp_native_adjst_tail_shrnk_data(cfg):
+@ksft_variants(_ipvers)
+def test_xdp_native_adjst_tail_shrnk_data(cfg, ipver):
"""
Tests the XDP tail adjustment by shrinking packet data.
Args:
cfg: Configuration object containing network settings.
+ ipver: IP version to use ("4" or "6").
"""
+ _set_ipver_defer_restore(cfg, ipver)
+
pkt_sz_lst = [512, 1024, 2048]
offset_lst = [-16, -32, -64, -128, -256]
res = _test_xdp_native_tail_adjst(
@@ -535,7 +579,7 @@ def _test_xdp_native_head_adjst(cfg, prog, pkt_sz_lst, offset_lst):
# after we eat into it. We send large-enough packets, but if HDS
# is enabled head will only contain headers. Don't try to eat
# more than 28 bytes (UDPv4 + eth hdr left: (14 + 20 + 8) - 14)
- l2_cut_off = 28 if cfg.addr_ipver == 4 else 48
+ l2_cut_off = 28 if cfg.addr_ipver == "4" else 48
if pkt_sz > hds_thresh and offset > l2_cut_off:
ksft_pr(
f"Failed run: pkt_sz ({pkt_sz}) > HDS threshold ({hds_thresh}) and "
@@ -579,18 +623,22 @@ def _test_xdp_native_head_adjst(cfg, prog, pkt_sz_lst, offset_lst):
return {"status": "pass"}
-def test_xdp_native_adjst_head_grow_data(cfg):
+@ksft_variants(_ipvers)
+def test_xdp_native_adjst_head_grow_data(cfg, ipver):
"""
Tests the XDP headroom growth support.
Args:
cfg: Configuration object containing network settings.
+ ipver: IP version to use ("4" or "6").
This function sets up the packet size and offset lists, then calls the
_test_xdp_native_head_adjst_mb function to perform the actual test. The
test is passed if the headroom is successfully extended for given packet
sizes and offsets.
"""
+ _set_ipver_defer_restore(cfg, ipver)
+
pkt_sz_lst = [512, 1024, 2048]
# Negative values result in headroom shrinking, resulting in growing of payload
@@ -600,18 +648,22 @@ def test_xdp_native_adjst_head_grow_data(cfg):
_validate_res(res, offset_lst, pkt_sz_lst)
-def test_xdp_native_adjst_head_shrnk_data(cfg):
+@ksft_variants(_ipvers)
+def test_xdp_native_adjst_head_shrnk_data(cfg, ipver):
"""
Tests the XDP headroom shrinking support.
Args:
cfg: Configuration object containing network settings.
+ ipver: IP version to use ("4" or "6").
This function sets up the packet size and offset lists, then calls the
_test_xdp_native_head_adjst_mb function to perform the actual test. The
test is passed if the headroom is successfully shrunk for given packet
sizes and offsets.
"""
+ _set_ipver_defer_restore(cfg, ipver)
+
pkt_sz_lst = [512, 1024, 2048]
# Positive values result in headroom growing, resulting in shrinking of payload
@@ -621,12 +673,19 @@ def test_xdp_native_adjst_head_shrnk_data(cfg):
_validate_res(res, offset_lst, pkt_sz_lst)
-@ksft_variants([
- KsftNamedVariant("pass", XDPAction.PASS),
- KsftNamedVariant("drop", XDPAction.DROP),
- KsftNamedVariant("tx", XDPAction.TX),
-])
-def test_xdp_native_qstats(cfg, act):
+def _qstats_variants():
+ actions = [
+ ("pass", XDPAction.PASS),
+ ("drop", XDPAction.DROP),
+ ("tx", XDPAction.TX),
+ ]
+ for ipver in ["4", "6"]:
+ for name, act in actions:
+ yield KsftNamedVariant(f"{name}_ipv{ipver}", act, ipver)
+
+
+@ksft_variants(_qstats_variants())
+def test_xdp_native_qstats(cfg, act, ipver):
"""
Send 1000 messages. Expect XDP action specified in @act.
Make sure the packets were counted to interface level qstats
@@ -634,6 +693,7 @@ def test_xdp_native_qstats(cfg, act):
"""
cfg.require_cmd("socat")
+ _set_ipver_defer_restore(cfg, ipver)
bpf_info = BPFProgInfo("xdp_prog", "xdp_native.bpf.o", "xdp", 1500)
prog_info = _load_xdp_prog(cfg, bpf_info)
--
2.45.0
^ permalink raw reply related
* [PATCH net-next v8 0/9] net: dsa: add DSA support for the LAN9645x switch chip family
From: Jens Emil Schulz Østergaard @ 2026-07-02 6:45 UTC (permalink / raw)
To: UNGLinuxDriver, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Woojung Huh,
Russell King, Steen Hegelund, Daniel Machon
Cc: linux-kernel, netdev, devicetree,
Jens Emil Schulz Østergaard
This series provides the Microchip LAN9645X Switch driver.
The LAN9645x is a family of chips with ethernet switch functionality and
multiple peripheral functions. The switch delivers up to 9 ethernet
ports and 12 Gbps switching bandwidth.
The switch chip has 5 integrated copper PHYs, support for 2x RGMII
interfaces, 2x SGMII and one QSGMII interface.
The switch chip is from the same design architecture family as ocelot
and lan966x, and the driver reflects this similarity. However, LAN9645x
does not have an internal CPU in any package, and must be driven
externally. For register IO it supports interfaces such as SPI, I2C and
MDIO.
The chip supports a variety of network features such as
* Mactable for MDB/FDB functionality
* Bridge forwarding offload
* VLAN-aware bridging
* IGMP/MLD snooping
* Link aggregation
* PTP timestamping
* FRER (802.1CB)
* Media Redundancy Protocol
* Parallel Redundancy and High-Availability Seamless Redundancy
(HSR/PRP) in DANH/DANP mode
* Per stream filtering and policing
* Shapers such as Credit Based Shaping and Time Aware Shaing
* Frame preemption
* A TCAM (VCAP) for line-rate frame processing
The LAN9645x family consists of the following SKUs:
LAN96455F
LAN96457F
LAN96459F
LAN96455S
LAN96457S
LAN96459S
The difference between the SKUs is the number of supported ports (5, 7
or 9) and features supported. The F subfamily supports HSR/PRP and TSN,
while the S subfamily does not.
The intended way to bind this driver is using a parent MFD driver,
responsible for the register IO protocol, and distributing regmaps to
child devices. The goal is to use the same approach as the MFD driver in
drivers/mfd/ocelot-spi.c.
This driver expects to request named regmaps from a parent device. This
approach is similar to the DSA driver
drivers/net/dsa/ocelot/ocelot_ext.c
which supports being driven by an external CPU via SPI with parent
device drivers/mfd/ocelot-spi.c.
The MFD driver will come in a later series, because there are
requirements on the number of child devices before a driver qualifies as
a MFD device.
Development is done using the LAN966x as a host CPU, running the lan966x
swichdev driver, using the EVB-LAN9668 EDS2 board.
The datasheet is available here:
https://ww1.microchip.com/downloads/aemDocuments/documents/UNG/ProductDocuments/DataSheets/LAN9645xF-Data-Sheet-DS00006065.pdf
This series will deliver the following features:
* Standalone ports
* Bridge forwarding and FDB offloading
* VLAN-aware bridge
* Stats integration
More support will be added at a later stage. Here is a tentative plan of
future patches for this DSA driver:
* Add LAG support.
* Add MDB support.
* Add TC matchall mirror support.
* Add TC matchall police support.
* Add DCB/qos support.
* Add simple TC support: mqprio, cbs, tbf, ebf.
* Add TC flower filter support.
* Add HSR/PRP offloading support.
* Add PTP support.
* Add TC taprio support.
For completeness I include tentative plan of planned patches for
LAN9645x peripherals:
* Extend pinctrl-ocelot for LAN9645x:
https://lore.kernel.org/linux-gpio/20260119-pinctrl_ocelot_extend_support_for_lan9645x-v1-0-1228155ed0ee@microchip.com/
* Add driver for internal PHY:
https://lore.kernel.org/netdev/20260123-phy_micrel_add_support_for_lan9645x_internal_phy-v1-1-8484b1a5a7fd@microchip.com/
* MFD driver for managing register IO protocol and child device
initialization.
* Extend pinctrl-microchip-sgpio for LAN9645x support.
* Extend i2c_designware for LAN9645x support.
* Add driver for outbound interrupt controller.
* Add serdes driver for lan9645x.
Signed-off-by: Jens Emil Schulz Østergaard <jensemil.schulzostergaard@microchip.com>
---
Changes in v8:
- Individual patches mention specific v8 changes
- Do not use the name CPU_PORT for the chips internal CPU port module,
as it collides with the DSA CPU port (the NPI front port). Drop the
CPU_PORT macro and reference the module as lan9645x->num_phys_ports,
mirroring ocelot/felix.
- Reword the port map and related comments to distinguish the chip CPU
port modules (indices 9-10) from the DSA CPU port.
- Add a port_mux_lock mutex to serialize port mux arbitration in
the phylink mac_prepare path.
- Link to v7: https://lore.kernel.org/r/20260603-dsa_lan9645x_switch_driver_base-v7-0-b2f90e676707@microchip.com
Changes in v7:
- Individual patches mention specific v7 changes
- Do not mark the BPDU range 01:80:C2:00:00:0X as offloaded in the tag driver.
- Refactor cpu queue based frame classification to use categories default, trap
and copy, which mirrors usage instead of being based on frame types.
- Add registers ANA:COMMON:CPUQ_8021_CFG for bpdu cpu queue configuration.
- Use cpu queue LAN9645X_CPUQ_TRAP for bpdu frames.
- Refactor IGMP/MLD/IPMC_CTRL to use new LAN9645X_CPUQ_DEF,
LAN9645X_CPUQ_TRAP and LAN9645X_CPUQ_COPY queues.
- Add __aligned(2) to mac variable in lan9645x_mdb_update_dest.
- Link to v6: https://lore.kernel.org/r/20260527-dsa_lan9645x_switch_driver_base-v6-0-4d409ae64f3c@microchip.com
Changes in v6:
- Individual patches mention specific v6 changes
- Rebased on net-next, bumping DSA_TAG_PROTO_LAN9645X_VALUE to 34
- Link to v5: https://lore.kernel.org/r/20260518-dsa_lan9645x_switch_driver_base-v5-0-968fbf34ffa3@microchip.com
Changes in v5:
- Individual patches mention specific v5 changes
- Undo offset fix in postpull_rcsum. The original logic was correct for
CHECKSUM_COMPLETE host NICs
- Use __always_inline in lan9645x_ifh_{get,set}
- remove double space after = in set_merge_mask
- remove unused fields dd_dis and tsn_dis, and add SKU supported port validation
during setup
- phylink: remove MAC_2500FD
- phylink: add comment about empty supported_interfaces for port 5-6.
- phylink: fix 2:1 rgmii port muxing for port module 4 and 7 to be fully
dynamic and validate requested mux settings.
- phylink: add comment about 2:1 rgmii port muxing for port module 4
and 7.
- rx/tx-internal-delay-ps checked against supported 2ns value
- init lan9645x->npi = -1 at probe and check port < 0 in npi_deinit
- use ds->ageing_time_max
- use packed p->host_flood_req for atomic r/w
- fix typo in set_ageing_time comment
- include lan9645x->bridge deref under lock in brige_join
- switch -EBUSY to -EINVAL for vlan add/del in the reserved range.
- remove reserved HSR vlan
- lan9645x_mac_init returns error on table init timeout
- add comment about skipping LOCKED entries on fdb dump
- skip igmp/mld redir for npi port
- make lan9645x_stats_init void
- add SCNT_TX_BUFDROP to tx_dropped
- change rmon range {0,64} -> {64, 64}. Runt frames counted elsewhere.
- remove rx_crc, rx_symbol_err from rx_packets, as they are already
counted in SZ_* buckets.
- add defensive cancel_delayed_work_sync in stats_free
- Link to v4: https://lore.kernel.org/r/20260430-dsa_lan9645x_switch_driver_base-v4-0-f1b6005fa8b7@microchip.com
Changes in v4:
- v3 was deferred, but I made some changes based on the Sashiko review
- Individual patches mention specific v4 changes
- Fix offset in postpull_rcsum so prefix eth header is cleared, not
actual eth header, so tag driver works with CHECKSUM_COMPLETE host
NICs
- Fix untagged rx on vlan aware port with pvid
- Add comment to QSYS_RES_CFG configuration
- Phylink_mac_prepare: fix to make sure we can dynamically change rgmii
on port 4
- Move ports allocation to probe
- tag_npi_setup: reject cascaded setups
- Skip WARN_ON in lan9645x_to_port
- set_host_flood changed to per port work to coalesce values and skip
atomic allocations
- Fix clear HOST_PVID vlan membership when a port joins a bridge.
- Explicit default value write to tag type register for untagged frames
- Use lan_rmw for ANA_DROP_CFG
- Add comment for error path in lan9645x_vlan_hw_wr
- Remove mac_entries list and just do direct IO to mac table from
fdb_add/fdb_del.
- Clean up fresh mdb when hw mac table write fails.
- Remove rx_uc and tx_uc from ethtool stats list, as they are derivable
from the eth-mac group
- Split stats_init into stats_alloc and stats_init, use alloc in probe
and init in dsa_setup
- Link to v3: https://lore.kernel.org/r/20260410-dsa_lan9645x_switch_driver_base-v3-0-aadc8595306d@microchip.com
Changes in v3:
- Individual patches mention specific v3 changes.
- Add guard before vlan_remove_tag on xmit
- Add pskb_may_pull checks on rx
- Remove additionalProperties: true in bindings
- Remove unnecessary | from description in bindings
- Change top level $ref to dsa.yaml#/$defs/ethernet-ports
- Use ethernet-ports and ethernet-port
- Move ethernet-ports under properties instead of patternProperties
- Move unevaluatedProperties: false after $ref
- Update bindings example to use ethernet-ports and ethernet-port
- Move DEV_MAC_TAGS_CFG to port setup, instead of vlan config, so vlan
overhead is always included in port frame maxlen calculation.
- Remove code disabling ipv6 on conduit
- Use of_property_read_u32 for {rx,tx}-internal-delay-ps
- Use dsa_user_ports(ds) instead of
GENMASK(lan9645x->num_phys_ports - 1, 0) as base flood mask.
- Add comment explaining obey vlan
- Allow disabling aging with explicit zero parameters.
- Fix non-forwarding STP states in bridge fwd calculation.
- Restore host flood state on bridge leave.
- Avoid mac_entry dealloc when mac table writes fail.
- Avoid mdb_entry dealloc when mac table writes fail.
- Dealloc mac_entries on deinit.
- Dealloc mdb_entries on deinit.
- Link to v2: https://lore.kernel.org/r/20260324-dsa_lan9645x_switch_driver_base-v2-0-f7504e3b0681@microchip.com
Changes in v2:
- Individual patches have specific v2 changes.
- Ran DSA, and std counters, selftests, which prompted several changes.
The following selftests pass, except for some expected failures:
- bridge_vlan_aware.sh
- bridge_vlan_unaware.sh
- bridge_vlan_mcast.sh
- no_forwarding.sh
- bridge_mdb.sh
- bridge_mld.sh
- test_fdb_stress_test.sh
- .../drivers/net/hw/ethtool_rmon.sh
- .../drivers/net/hw/ethtool_std_stats.sh (from Ioana's series)
- Added new patch for MDB management, as this was required for selftests.
- Added port_set_host_flood to enable unknown traffic to standalone during
promisc/ALL_MULTI (selftests).
- Remove the dubugfs.
- Link to v1: https://lore.kernel.org/r/20260303-dsa_lan9645x_switch_driver_base-v1-0-bff8ca1396f5@microchip.com
---
Jens Emil Schulz Østergaard (9):
net: dsa: add tag driver for LAN9645X
dt-bindings: net: lan9645x: add LAN9645X switch bindings
net: dsa: lan9645x: add autogenerated register macros
net: dsa: lan9645x: add basic dsa driver for LAN9645X
net: dsa: lan9645x: add bridge support
net: dsa: lan9645x: add vlan support
net: dsa: lan9645x: add mac table integration
net: dsa: lan9645x: add mdb management
net: dsa: lan9645x: add port statistics
.../net/dsa/microchip,lan96455s-switch.yaml | 111 ++
MAINTAINERS | 10 +
drivers/net/dsa/Kconfig | 2 +
drivers/net/dsa/microchip/Makefile | 1 +
drivers/net/dsa/microchip/lan9645x/Kconfig | 11 +
drivers/net/dsa/microchip/lan9645x/Makefile | 12 +
drivers/net/dsa/microchip/lan9645x/lan9645x_mac.c | 307 ++++
drivers/net/dsa/microchip/lan9645x/lan9645x_main.c | 1041 +++++++++++
drivers/net/dsa/microchip/lan9645x/lan9645x_main.h | 441 +++++
drivers/net/dsa/microchip/lan9645x/lan9645x_mdb.c | 398 ++++
drivers/net/dsa/microchip/lan9645x/lan9645x_npi.c | 85 +
.../net/dsa/microchip/lan9645x/lan9645x_phylink.c | 414 +++++
drivers/net/dsa/microchip/lan9645x/lan9645x_port.c | 226 +++
drivers/net/dsa/microchip/lan9645x/lan9645x_regs.h | 1937 ++++++++++++++++++++
.../net/dsa/microchip/lan9645x/lan9645x_stats.c | 929 ++++++++++
.../net/dsa/microchip/lan9645x/lan9645x_stats.h | 279 +++
drivers/net/dsa/microchip/lan9645x/lan9645x_vlan.c | 403 ++++
include/linux/dsa/lan9645x.h | 144 ++
include/net/dsa.h | 2 +
net/dsa/Kconfig | 11 +
net/dsa/Makefile | 1 +
net/dsa/tag_lan9645x.c | 311 ++++
22 files changed, 7076 insertions(+)
---
base-commit: 1c664ec4b9ea827b609d296921ed5bad8a40a158
change-id: 20260210-dsa_lan9645x_switch_driver_base-312bbfc37edb
Best regards,
--
Jens Emil Schulz Østergaard <jensemil.schulzostergaard@microchip.com>
^ permalink raw reply
* [PATCH net-next v8 2/9] dt-bindings: net: lan9645x: add LAN9645X switch bindings
From: Jens Emil Schulz Østergaard @ 2026-07-02 6:45 UTC (permalink / raw)
To: UNGLinuxDriver, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Woojung Huh,
Russell King, Steen Hegelund, Daniel Machon
Cc: linux-kernel, netdev, devicetree,
Jens Emil Schulz Østergaard
In-Reply-To: <20260702-dsa_lan9645x_switch_driver_base-v8-0-90228d8bba58@microchip.com>
Add bindings for LAN9645X switch. We use a fallback compatible for the
smallest SKU microchip,lan96455s-switch.
Reviewed-by: Steen Hegelund <Steen.Hegelund@microchip.com>
Signed-off-by: Jens Emil Schulz Østergaard <jensemil.schulzostergaard@microchip.com>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
---
Changes in v5:
- No changes.
Changes in v4:
- No changes.
Changes in v3:
- remove additionalProperties: true
- remove unnecessary | from description
- change top level $ref to dsa.yaml#/$defs/ethernet-ports
- use ethernet-ports and ethernet-port
- move ethernet-ports under properties instead of patternProperties
- move unevaluatedProperties: false after $ref
- update example to use ethernet-ports and ethernet-port
Changes in v2:
- rename file to microchip,lan96455s-switch.yaml
- remove led vendor property
- add {rx,tx}-internal-delay-ps for rgmii delay
- remove labels from example
- remove container node from example
---
.../net/dsa/microchip,lan96455s-switch.yaml | 111 +++++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 112 insertions(+)
diff --git a/Documentation/devicetree/bindings/net/dsa/microchip,lan96455s-switch.yaml b/Documentation/devicetree/bindings/net/dsa/microchip,lan96455s-switch.yaml
new file mode 100644
index 000000000000..043fb48922b4
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/dsa/microchip,lan96455s-switch.yaml
@@ -0,0 +1,111 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/dsa/microchip,lan96455s-switch.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Microchip LAN9645x Ethernet switch
+
+maintainers:
+ - Jens Emil Schulz Østergaard <jensemil.schulzostergaard@microchip.com>
+
+description:
+ The LAN9645x switch is a multi-port Gigabit AVB/TSN Ethernet switch with
+ five integrated 10/100/1000Base-T PHYs. In addition to the integrated PHYs,
+ it supports up to 2 RGMII/RMII, up to 2 BASE-X/SERDES/2.5GBASE-X and one
+ Quad-SGMII interfaces.
+
+properties:
+ compatible:
+ oneOf:
+ - enum:
+ - microchip,lan96455s-switch
+ - items:
+ - enum:
+ - microchip,lan96455f-switch
+ - microchip,lan96457f-switch
+ - microchip,lan96459f-switch
+ - microchip,lan96457s-switch
+ - microchip,lan96459s-switch
+ - const: microchip,lan96455s-switch
+
+ reg:
+ maxItems: 1
+
+ ethernet-ports:
+ type: object
+ patternProperties:
+ "^ethernet-port@[0-8]$":
+ type: object
+ description: Ethernet switch ports
+
+ $ref: dsa-port.yaml#
+ unevaluatedProperties: false
+
+ properties:
+ rx-internal-delay-ps:
+ const: 2000
+
+ tx-internal-delay-ps:
+ const: 2000
+
+$ref: dsa.yaml#/$defs/ethernet-ports
+
+required:
+ - compatible
+ - reg
+ - ethernet-ports
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ ethernet-switch@4000 {
+ compatible = "microchip,lan96459f-switch", "microchip,lan96455s-switch";
+ reg = <0x4000 0x244>;
+
+ ethernet-ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ethernet-port@0 {
+ reg = <0>;
+ phy-mode = "gmii";
+ phy-handle = <&cuphy0>;
+ };
+
+ ethernet-port@1 {
+ reg = <1>;
+ phy-mode = "gmii";
+ phy-handle = <&cuphy1>;
+ };
+
+ ethernet-port@2 {
+ reg = <2>;
+ phy-mode = "gmii";
+ phy-handle = <&cuphy2>;
+ };
+
+ ethernet-port@3 {
+ reg = <3>;
+ phy-mode = "gmii";
+ phy-handle = <&cuphy3>;
+ };
+
+ ethernet-port@7 {
+ reg = <7>;
+ phy-mode = "rgmii";
+ ethernet = <&cpu_host_port>;
+ rx-internal-delay-ps = <2000>;
+ tx-internal-delay-ps = <2000>;
+
+ fixed-link {
+ speed = <1000>;
+ full-duplex;
+ pause;
+ };
+ };
+ };
+ };
+...
+
diff --git a/MAINTAINERS b/MAINTAINERS
index 397c6ce916fb..6f63b90588ce 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17632,6 +17632,7 @@ M: Jens Emil Schulz Østergaard <jensemil.schulzostergaard@microchip.com>
M: UNGLinuxDriver@microchip.com
L: netdev@vger.kernel.org
S: Maintained
+F: Documentation/devicetree/bindings/net/dsa/microchip,lan96455s-switch.yaml
F: include/linux/dsa/lan9645x.h
F: net/dsa/tag_lan9645x.c
--
2.52.0
^ permalink raw reply related
* [PATCH net-next v8 1/9] net: dsa: add tag driver for LAN9645X
From: Jens Emil Schulz Østergaard @ 2026-07-02 6:45 UTC (permalink / raw)
To: UNGLinuxDriver, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Woojung Huh,
Russell King, Steen Hegelund, Daniel Machon
Cc: linux-kernel, netdev, devicetree,
Jens Emil Schulz Østergaard
In-Reply-To: <20260702-dsa_lan9645x_switch_driver_base-v8-0-90228d8bba58@microchip.com>
Add tag driver for LAN9645x using a front port as CPU port. This mode
is called an NPI port in the datasheet.
Use long prefix on extraction (RX) and no prefix on injection (TX). A
long prefix on extraction helps get through the conduit port on host
side, since it will see a broadcast MAC.
The LAN9645x chip is in the same design architecture family as ocelot
and lan966x. The tagging protocol has the same structure as these chips,
but the particular fields are different or have different sizes.
Therefore, this tag driver is similar to tag_ocelot.c, but the
differences in fields makes it hard to reuse.
LAN9645x supports 3 different tag formats for extraction/injection of
frames from a CPU port: long prefix, short prefix and no prefix.
The tag is prepended to the frame. The critical data for the chip is
contained in an internal frame header (IFH) which is 28 bytes. The
prefix formats look like this:
Long prefix (16 bytes) + IFH:
- DMAC = 0xffffffffffff on extraction.
- SMAC = 0xfeffffffffff on extraction.
- ETYPE = 0x8880
- payload = 0x0011
- IFH
Short prefix (4 bytes) + IFH:
- 0x8880
- 0x0011
- IFH
No prefix:
- IFH
The format can be configured asymmetrically on RX and TX.
The IFH get/set functions are declared as inline. All the field
constants are compile-time known, so when these calls are inlined
efficient code is generated with branches pruned and loops unrolled.
During testing it was observed that without explicit inlining GCC would
have trouble inlining the functions, which hurt performance.
Reviewed-by: Steen Hegelund <Steen.Hegelund@microchip.com>
Signed-off-by: Jens Emil Schulz Østergaard <jensemil.schulzostergaard@microchip.com>
---
Changes in v8:
- Drop the cpu_port local and use ds->num_ports directly as the IFH
source port (the CPU port module index).
- Reword the reflection comment to refer to "the CPU".
Changes in v7:
- Introduce cpu queue based frame classification, and refactor to the
categories default, trap and copy, which mirrors usage instead of
being based on frame types.
Changes in v6:
- rebased on net-next, bumping DSA_TAG_PROTO_LAN9645X_VALUE to 34
Changes in v5:
- Undo offset fix in postpull_rcsum. The original logic was correct for
CHECKSUM_COMPLETE host NICs
- Use __always_inline in lan9645x_ifh_{get,set}
- remove double space after = in set_merge_mask
Changes in v4:
- Fix offset in postpull_rcsum so prefix eth header is cleared, not
actual eth header, so tag driver works with CHECKSUM_COMPLETE host
NICs
- Fix untagged rx on vlan aware port with pvid
Changes in v3:
- guard vlan_remove_tag behind skb_headlen(skb) >= VLAN_ETH_HLEN on xmit
- add pskb_may_pull checks in rx path
Changes in v2:
- sorting in net/dsa/Kconfig
- sorting in net/dsa/Makefile
- remove default zero promisc_on_conduit
- move functions to to .c file
- add justification for inline usage to commit message
- add __skb_put_padto on xmit path
- fix hwaccel_put_tag
---
MAINTAINERS | 8 ++
include/linux/dsa/lan9645x.h | 144 ++++++++++++++++++++
include/net/dsa.h | 2 +
net/dsa/Kconfig | 11 ++
net/dsa/Makefile | 1 +
net/dsa/tag_lan9645x.c | 311 +++++++++++++++++++++++++++++++++++++++++++
6 files changed, 477 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 15011f5752a9..397c6ce916fb 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17627,6 +17627,14 @@ L: netdev@vger.kernel.org
S: Maintained
F: drivers/net/phy/microchip_t1.c
+MICROCHIP LAN9645X ETHERNET SWITCH DRIVER
+M: Jens Emil Schulz Østergaard <jensemil.schulzostergaard@microchip.com>
+M: UNGLinuxDriver@microchip.com
+L: netdev@vger.kernel.org
+S: Maintained
+F: include/linux/dsa/lan9645x.h
+F: net/dsa/tag_lan9645x.c
+
MICROCHIP LAN966X ETHERNET DRIVER
M: Horatiu Vultur <horatiu.vultur@microchip.com>
M: UNGLinuxDriver@microchip.com
diff --git a/include/linux/dsa/lan9645x.h b/include/linux/dsa/lan9645x.h
new file mode 100644
index 000000000000..9896fab649c4
--- /dev/null
+++ b/include/linux/dsa/lan9645x.h
@@ -0,0 +1,144 @@
+/* SPDX-License-Identifier: GPL-2.0
+ * Copyright (C) 2026 Microchip Technology Inc.
+ */
+
+#ifndef _NET_DSA_TAG_LAN9645X_H_
+#define _NET_DSA_TAG_LAN9645X_H_
+
+#include <net/dsa.h>
+
+/* LAN9645x supports 3 different formats on an NPI port, long prefix, short
+ * prefix and no prefix. The format can be configured asymmetrically on RX and
+ * TX. We use long prefix on extraction (RX), and no prefix on injection.
+ * The long prefix on extraction helps get through the conduit port on host
+ * side, since it will see a broadcast MAC.
+ *
+ * The internal frame header (IFH) is 28 bytes, and the fields are documented
+ * below.
+ *
+ * Long prefix, 16 bytes + IFH:
+ * - DMAC = 0xFFFFFFFFFFFF on extraction.
+ * - SMAC = 0xFEFFFFFFFFFF on extraction.
+ * - ETYPE = 0x8880
+ * - payload = 0x0011
+ * - IFH
+ *
+ * Short prefix, 4 bytes + IFH:
+ * - 0x8880
+ * - 0x0011
+ * - IFH
+ *
+ * No prefix:
+ * - IFH
+ *
+ */
+#define LAN9645X_IFH_TAG_TYPE_C 0
+#define LAN9645X_IFH_TAG_TYPE_S 1
+#define LAN9645X_IFH_LEN_U32 7
+#define LAN9645X_IFH_LEN (LAN9645X_IFH_LEN_U32 * sizeof(u32))
+#define LAN9645X_IFH_BITS (LAN9645X_IFH_LEN * BITS_PER_BYTE)
+#define LAN9645X_SHORT_PREFIX_LEN 4
+#define LAN9645X_LONG_PREFIX_LEN 16
+#define LAN9645X_TOTAL_TAG_LEN (LAN9645X_LONG_PREFIX_LEN + LAN9645X_IFH_LEN)
+
+#define IFH_INJ_TIMESTAMP 192
+#define IFH_BYPASS 191
+#define IFH_MASQ 190
+#define IFH_TIMESTAMP 186
+#define IFH_TIMESTAMP_NS 194
+#define IFH_TIMESTAMP_SUBNS 186
+#define IFH_MASQ_PORT 186
+#define IFH_RCT_INJ 185
+#define IFH_LEN 171
+#define IFH_WRDMODE 169
+#define IFH_RTAGD 167
+#define IFH_CUTTHRU 166
+#define IFH_REW_CMD 156
+#define IFH_REW_OAM 155
+#define IFH_PDU_TYPE 151
+#define IFH_FCS_UPD 150
+#define IFH_DP 149
+#define IFH_RTE_INB_UPDATE 148
+#define IFH_POP_CNT 146
+#define IFH_ETYPE_OFS 144
+#define IFH_SRCPORT 140
+#define IFH_SEQ_NUM 120
+#define IFH_TAG_TYPE 119
+#define IFH_TCI 103
+#define IFH_DSCP 97
+#define IFH_QOS_CLASS 94
+#define IFH_CPUQ 86
+#define IFH_LEARN_FLAGS 84
+#define IFH_SFLOW_ID 80
+#define IFH_ACL_HIT 79
+#define IFH_ACL_IDX 73
+#define IFH_ISDX 65
+#define IFH_DSTS 55
+#define IFH_FLOOD 53
+#define IFH_SEQ_OP 51
+#define IFH_IPV 48
+#define IFH_AFI 47
+#define IFH_RTP_ID 37
+#define IFH_RTP_SUBID 36
+#define IFH_PN_DATA_STATUS 28
+#define IFH_PN_TRANSF_STATUS_ZERO 27
+#define IFH_PN_CC 11
+#define IFH_DUPL_DISC_ENA 10
+#define IFH_RCT_AVAIL 9
+
+#define IFH_INJ_TIMESTAMP_SZ 32
+#define IFH_BYPASS_SZ 1
+#define IFH_MASQ_SZ 1
+#define IFH_TIMESTAMP_SZ 38
+#define IFH_TIMESTAMP_NS_SZ 30
+#define IFH_TIMESTAMP_SUBNS_SZ 8
+#define IFH_MASQ_PORT_SZ 4
+#define IFH_RCT_INJ_SZ 1
+#define IFH_LEN_SZ 14
+#define IFH_WRDMODE_SZ 2
+#define IFH_RTAGD_SZ 2
+#define IFH_CUTTHRU_SZ 1
+#define IFH_REW_CMD_SZ 10
+#define IFH_REW_OAM_SZ 1
+#define IFH_PDU_TYPE_SZ 4
+#define IFH_FCS_UPD_SZ 1
+#define IFH_DP_SZ 1
+#define IFH_RTE_INB_UPDATE_SZ 1
+#define IFH_POP_CNT_SZ 2
+#define IFH_ETYPE_OFS_SZ 2
+#define IFH_SRCPORT_SZ 4
+#define IFH_SEQ_NUM_SZ 16
+#define IFH_TAG_TYPE_SZ 1
+#define IFH_TCI_SZ 16
+#define IFH_DSCP_SZ 6
+#define IFH_QOS_CLASS_SZ 3
+#define IFH_CPUQ_SZ 8
+#define IFH_LEARN_FLAGS_SZ 2
+#define IFH_SFLOW_ID_SZ 4
+#define IFH_ACL_HIT_SZ 1
+#define IFH_ACL_IDX_SZ 6
+#define IFH_ISDX_SZ 8
+#define IFH_DSTS_SZ 10
+#define IFH_FLOOD_SZ 2
+#define IFH_SEQ_OP_SZ 2
+#define IFH_IPV_SZ 3
+#define IFH_AFI_SZ 1
+#define IFH_RTP_ID_SZ 10
+#define IFH_RTP_SUBID_SZ 1
+#define IFH_PN_DATA_STATUS_SZ 8
+#define IFH_PN_TRANSF_STATUS_ZERO_SZ 1
+#define IFH_PN_CC_SZ 16
+#define IFH_DUPL_DISC_ENA_SZ 1
+#define IFH_RCT_AVAIL_SZ 1
+
+/* Chip has 8 cpu queues. The cpu queues used by a frame is passed as a mask in
+ * the IFH on extraction. We use this to avoid classifying BPDU, IGMP and MLD
+ * frames in the tag driver.
+ */
+enum {
+ LAN9645X_CPUQ_DEF = 0,
+ LAN9645X_CPUQ_TRAP = 1,
+ LAN9645X_CPUQ_COPY = 2,
+};
+
+#endif /* _NET_DSA_TAG_LAN9645X_H_ */
diff --git a/include/net/dsa.h b/include/net/dsa.h
index 8c16ef23cc10..6ebd8c635d17 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -59,6 +59,7 @@ struct tc_action;
#define DSA_TAG_PROTO_MXL_GSW1XX_VALUE 31
#define DSA_TAG_PROTO_MXL862_VALUE 32
#define DSA_TAG_PROTO_NETC_VALUE 33
+#define DSA_TAG_PROTO_LAN9645X_VALUE 34
enum dsa_tag_protocol {
DSA_TAG_PROTO_NONE = DSA_TAG_PROTO_NONE_VALUE,
@@ -95,6 +96,7 @@ enum dsa_tag_protocol {
DSA_TAG_PROTO_MXL_GSW1XX = DSA_TAG_PROTO_MXL_GSW1XX_VALUE,
DSA_TAG_PROTO_MXL862 = DSA_TAG_PROTO_MXL862_VALUE,
DSA_TAG_PROTO_NETC = DSA_TAG_PROTO_NETC_VALUE,
+ DSA_TAG_PROTO_LAN9645X = DSA_TAG_PROTO_LAN9645X_VALUE,
};
struct dsa_switch;
diff --git a/net/dsa/Kconfig b/net/dsa/Kconfig
index d5e725b90d78..a0a0096e7e43 100644
--- a/net/dsa/Kconfig
+++ b/net/dsa/Kconfig
@@ -75,6 +75,17 @@ config NET_DSA_TAG_HELLCREEK
Say Y or M if you want to enable support for tagging frames
for the Hirschmann Hellcreek TSN switches.
+config NET_DSA_TAG_LAN9645X
+ tristate "Tag driver for Lan9645x switches"
+ help
+ Say Y or M if you want to enable NPI tagging for the Lan9645x switches.
+ In this mode, the frames over the Ethernet CPU port are prepended with
+ a hardware-defined injection/extraction frame header.
+ On injection a 28 byte internal frame header (IFH) is used. On
+ extraction a 16 byte prefix is prepended before the internal frame
+ header. This prefix starts with a broadcast MAC, to ease passage
+ through the host side RX filter.
+
config NET_DSA_TAG_GSWIP
tristate "Tag driver for Lantiq / Intel GSWIP switches"
help
diff --git a/net/dsa/Makefile b/net/dsa/Makefile
index b8c2667cd14a..600975e0f2f7 100644
--- a/net/dsa/Makefile
+++ b/net/dsa/Makefile
@@ -27,6 +27,7 @@ obj-$(CONFIG_NET_DSA_TAG_GSWIP) += tag_gswip.o
obj-$(CONFIG_NET_DSA_TAG_HELLCREEK) += tag_hellcreek.o
obj-$(CONFIG_NET_DSA_TAG_KSZ) += tag_ksz.o
obj-$(CONFIG_NET_DSA_TAG_LAN9303) += tag_lan9303.o
+obj-$(CONFIG_NET_DSA_TAG_LAN9645X) += tag_lan9645x.o
obj-$(CONFIG_NET_DSA_TAG_MTK) += tag_mtk.o
obj-$(CONFIG_NET_DSA_TAG_MXL_862XX) += tag_mxl862xx.o
obj-$(CONFIG_NET_DSA_TAG_MXL_GSW1XX) += tag_mxl-gsw1xx.o
diff --git a/net/dsa/tag_lan9645x.c b/net/dsa/tag_lan9645x.c
new file mode 100644
index 000000000000..c9049e3620a1
--- /dev/null
+++ b/net/dsa/tag_lan9645x.c
@@ -0,0 +1,311 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2026 Microchip Technology Inc.
+ */
+
+#include <linux/dsa/lan9645x.h>
+
+#include "tag.h"
+
+#define LAN9645X_NAME "lan9645x"
+
+#define BTM_MSK(n) ((u8)GENMASK(n, 0))
+#define TOP_MSK(n) ((u8)GENMASK(7, n))
+
+static inline void set_merge_mask(u8 *on_zero, u8 on_one, u8 mask)
+{
+ *on_zero = *on_zero ^ ((*on_zero ^ on_one) & mask);
+}
+
+/* The internal frame header (IFH) is a big-endian 28 byte unpadded bit array.
+ * Frames can be prepended with an IFH on injection and extraction. There
+ * are two field layouts, one for extraction and one for injection.
+ *
+ * IFH bits go from high to low, for instance
+ * ifh[0] = [223:216]
+ * ifh[27] = [7:0]
+ *
+ * Here is an example of setting a value starting at bit 13 of bit length 17.
+ *
+ * val = 0x1ff
+ * pos = 13
+ * length = 17
+ *
+ *
+ * IFH[] 0 23 24 25 26 27
+ *
+ * end_u8 start_u8
+ * +--------+----------------+--------+--------+--------+--------+--------+
+ * | | | | | | | |
+ * IFH | | .... | | vvvvvvvvvvvvvvvvvvv | |
+ * | | | | | | | | | |
+ * +--------+----------------+--------+--+-----+--------+--+-----+--------+
+ * Bits 223 39 32 31| 24 23 16 15| 8 7 0
+ * | |
+ * | |
+ * | |
+ * v v
+ * end = 29 pos = 13
+ * end_rem = 5 pos_rem = 5
+ * end_u8 = 3 start_u8 = 1
+ * BTM_MSK(5)= 0x3f TOP_MSK(5) = 0xe0
+ *
+ *
+ * In end_u8 and start_u8 we must merge the existing IFH byte with the new
+ * value. In the 'middle' bytes of the value we can overwrite the corresponding
+ * IFH byte.
+ */
+static __always_inline void lan9645x_ifh_set(u8 *ifh, u32 val, size_t pos,
+ size_t length)
+{
+ size_t end = (pos + length) - 1;
+ size_t end_rem = end & 0x7;
+ size_t pos_rem = pos & 0x7;
+ size_t start_u8 = pos >> 3;
+ size_t end_u8 = end >> 3;
+ u8 end_mask, start_mask;
+ size_t vshift;
+ u8 *ptr;
+
+ BUILD_BUG_ON_MSG(length > 32, "IFH field size wider than 32.");
+ BUILD_BUG_ON_MSG(length == 0, "IFH field size of 0.");
+ BUILD_BUG_ON_MSG(pos + length > LAN9645X_IFH_BITS,
+ "IFH field overflows IFH");
+
+ end_mask = BTM_MSK(end_rem);
+ start_mask = TOP_MSK(pos_rem);
+
+ ptr = &ifh[LAN9645X_IFH_LEN - 1 - end_u8];
+
+ if (end_u8 == start_u8)
+ return set_merge_mask(ptr, val << pos_rem,
+ end_mask & start_mask);
+
+ vshift = length - end_rem - 1;
+ set_merge_mask(ptr++, val >> vshift, end_mask);
+
+ for (size_t j = 1; j < end_u8 - start_u8; j++) {
+ vshift -= 8;
+ *ptr++ = val >> vshift;
+ }
+
+ set_merge_mask(ptr, val << pos_rem, start_mask);
+}
+
+static __always_inline u32 lan9645x_ifh_get(const u8 *ifh, size_t pos,
+ size_t length)
+{
+ size_t end = (pos + length) - 1;
+ size_t end_rem = end & 0x7;
+ size_t pos_rem = pos & 0x7;
+ size_t start_u8 = pos >> 3;
+ size_t end_u8 = end >> 3;
+ u8 end_mask, start_mask;
+ const u8 *ptr;
+ u32 val;
+
+ BUILD_BUG_ON_MSG(length > 32, "IFH field size wider than 32.");
+ BUILD_BUG_ON_MSG(length == 0, "IFH field size of 0.");
+ BUILD_BUG_ON_MSG(pos + length > LAN9645X_IFH_BITS,
+ "IFH field overflows IFH");
+
+ end_mask = BTM_MSK(end_rem);
+ start_mask = TOP_MSK(pos_rem);
+
+ ptr = &ifh[LAN9645X_IFH_LEN - 1 - end_u8];
+
+ if (end_u8 == start_u8)
+ return (*ptr & end_mask & start_mask) >> pos_rem;
+
+ val = *ptr++ & end_mask;
+
+ for (size_t j = 1; j < end_u8 - start_u8; j++)
+ val = val << 8 | *ptr++;
+
+ return val << (8 - pos_rem) | (*ptr & start_mask) >> pos_rem;
+}
+
+static void lan9645x_xmit_get_vlan_info(struct sk_buff *skb,
+ struct net_device *br,
+ u32 *vlan_tci, u32 *tag_type)
+{
+ struct vlan_ethhdr *hdr;
+ u16 proto, tci;
+
+ if (!br || !br_vlan_enabled(br)) {
+ *vlan_tci = 0;
+ *tag_type = LAN9645X_IFH_TAG_TYPE_C;
+ return;
+ }
+
+ hdr = (struct vlan_ethhdr *)skb_mac_header(skb);
+ br_vlan_get_proto(br, &proto);
+
+ if (skb_headlen(skb) >= VLAN_ETH_HLEN &&
+ ntohs(hdr->h_vlan_proto) == proto) {
+ vlan_remove_tag(skb, &tci);
+ *vlan_tci = tci;
+ } else {
+ rcu_read_lock();
+ br_vlan_get_pvid_rcu(br, &tci);
+ rcu_read_unlock();
+ *vlan_tci = tci;
+ }
+
+ *tag_type = (proto != ETH_P_8021Q) ? LAN9645X_IFH_TAG_TYPE_S :
+ LAN9645X_IFH_TAG_TYPE_C;
+}
+
+static void lan9645x_offload_fwd_mark(struct sk_buff *skb, u32 cpuq)
+{
+ /* Trapped frames must be forwarded by the stack. */
+ if (cpuq & BIT(LAN9645X_CPUQ_TRAP)) {
+ skb->offload_fwd_mark = 0;
+ return;
+ }
+
+ dsa_default_offload_fwd_mark(skb);
+}
+
+static struct sk_buff *lan9645x_xmit(struct sk_buff *skb,
+ struct net_device *ndev)
+{
+ struct dsa_port *dp = dsa_user_to_port(ndev);
+ struct dsa_switch *ds = dp->ds;
+ u32 vlan_tci, tag_type;
+ u32 qos_class;
+ void *ifh;
+
+ lan9645x_xmit_get_vlan_info(skb, dsa_port_bridge_dev_get(dp), &vlan_tci,
+ &tag_type);
+
+ /* We need to make sure frame has the proper size after IFH is stripped
+ * by hw.
+ */
+ if (__skb_put_padto(skb, ETH_ZLEN, false))
+ return NULL;
+
+ qos_class = netdev_get_num_tc(ndev) ?
+ netdev_get_prio_tc_map(ndev, skb->priority) :
+ skb->priority;
+
+ /* Make room for IFH */
+ ifh = skb_push(skb, LAN9645X_IFH_LEN);
+ memset(ifh, 0, LAN9645X_IFH_LEN);
+
+ lan9645x_ifh_set(ifh, 1, IFH_BYPASS, IFH_BYPASS_SZ);
+ lan9645x_ifh_set(ifh, ds->num_ports, IFH_SRCPORT, IFH_SRCPORT_SZ);
+ lan9645x_ifh_set(ifh, tag_type, IFH_TAG_TYPE, IFH_TAG_TYPE_SZ);
+ lan9645x_ifh_set(ifh, vlan_tci, IFH_TCI, IFH_TCI_SZ);
+ lan9645x_ifh_set(ifh, qos_class, IFH_QOS_CLASS, IFH_QOS_CLASS_SZ);
+ lan9645x_ifh_set(ifh, BIT(dp->index), IFH_DSTS, IFH_DSTS_SZ);
+
+ return skb;
+}
+
+static struct sk_buff *lan9645x_rcv(struct sk_buff *skb,
+ struct net_device *ndev)
+{
+ u32 src_port, qos_class, vlan_tci, tag_type, popcnt, etype_ofs, cpuq;
+ struct dsa_port *dp;
+ u32 ifh_gap_len = 0;
+ u16 vlan_tpid;
+ u8 *ifh;
+
+ /* DSA master already consumed DMAC,SMAC,ETYPE from long prefix. Go back
+ * to beginning of frame.
+ */
+ skb_push(skb, ETH_HLEN);
+
+ if (unlikely(!pskb_may_pull(skb, LAN9645X_TOTAL_TAG_LEN)))
+ return NULL;
+
+ /* IFH starts after our long prefix */
+ ifh = skb_pull(skb, LAN9645X_LONG_PREFIX_LEN);
+
+ popcnt = lan9645x_ifh_get(ifh, IFH_POP_CNT, IFH_POP_CNT_SZ);
+ etype_ofs = lan9645x_ifh_get(ifh, IFH_ETYPE_OFS, IFH_ETYPE_OFS_SZ);
+ src_port = lan9645x_ifh_get(ifh, IFH_SRCPORT, IFH_SRCPORT_SZ);
+ tag_type = lan9645x_ifh_get(ifh, IFH_TAG_TYPE, IFH_TAG_TYPE_SZ);
+ vlan_tci = lan9645x_ifh_get(ifh, IFH_TCI, IFH_TCI_SZ);
+ qos_class = lan9645x_ifh_get(ifh, IFH_QOS_CLASS, IFH_QOS_CLASS_SZ);
+ cpuq = lan9645x_ifh_get(ifh, IFH_CPUQ, IFH_CPUQ_SZ);
+
+ /* Set skb->data at start of real header
+ *
+ * Since REW_PORT_NO_REWRITE=0 is required on the NPI port, we need to
+ * account for any tags popped by the hardware, as that will leave a gap
+ * between the IFH and DMAC.
+ */
+ if (popcnt == 0 && etype_ofs == 0)
+ ifh_gap_len = 2 * VLAN_HLEN;
+ else if (popcnt == 3)
+ ifh_gap_len = VLAN_HLEN;
+
+ skb_pull(skb, LAN9645X_IFH_LEN);
+
+ if (unlikely(!pskb_may_pull(skb, ifh_gap_len + ETH_HLEN)))
+ return NULL;
+
+ skb_pull(skb, ifh_gap_len);
+ skb_reset_mac_header(skb);
+ skb_set_network_header(skb, ETH_HLEN);
+ skb_reset_mac_len(skb);
+
+ /* Reset skb->data past the actual ethernet header. */
+ skb_pull(skb, ETH_HLEN);
+
+ /* We must deliver the skb so skb->csum only covers the data beyond the
+ * real ethernet header. The fake ethernet header in the prefix is
+ * not part of skb->csum already. We must subtract what remains of the
+ * prefix, the ifh and the gap.
+ */
+ skb_postpull_rcsum(skb,
+ skb->data - LAN9645X_TOTAL_TAG_LEN - ifh_gap_len,
+ LAN9645X_TOTAL_TAG_LEN + ifh_gap_len);
+
+ skb->dev = dsa_conduit_find_user(ndev, 0, src_port);
+ if (WARN_ON_ONCE(!skb->dev)) {
+ /* This should never happen since we have disabled reflection
+ * back to the CPU.
+ */
+ return NULL;
+ }
+
+ lan9645x_offload_fwd_mark(skb, cpuq);
+
+ skb->priority = qos_class;
+
+ /* While we have REW_PORT_NO_REWRITE=0 on the NPI port, we still disable
+ * port VLAN tagging with REW_TAG_CFG. Any classified VID, different
+ * from a VID in the frame, will not be written to the frame, but is
+ * only communicated via the IFH. So for VLAN-aware ports we add the IFH
+ * vlan to the skb.
+ */
+ dp = dsa_user_to_port(skb->dev);
+ vlan_tpid = tag_type ? ETH_P_8021AD : ETH_P_8021Q;
+
+ if (dsa_port_is_vlan_filtering(dp) && vlan_tci) {
+ u16 port_pvid = 0;
+
+ br_vlan_get_pvid_rcu(skb->dev, &port_pvid);
+
+ if ((vlan_tci & VLAN_VID_MASK) != port_pvid)
+ __vlan_hwaccel_put_tag(skb, htons(vlan_tpid), vlan_tci);
+ }
+
+ return skb;
+}
+
+static const struct dsa_device_ops lan9645x_netdev_ops = {
+ .name = LAN9645X_NAME,
+ .proto = DSA_TAG_PROTO_LAN9645X,
+ .xmit = lan9645x_xmit,
+ .rcv = lan9645x_rcv,
+ .needed_headroom = LAN9645X_TOTAL_TAG_LEN,
+};
+
+MODULE_DESCRIPTION("DSA tag driver for LAN9645x family of switches, using NPI port");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_LAN9645X, LAN9645X_NAME);
+
+module_dsa_tag_driver(lan9645x_netdev_ops);
--
2.52.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox