* [PATCH v2 net-next 0/9] Switch support
@ 2026-07-02 4:50 Ratheesh Kannoth
2026-07-02 4:50 ` [PATCH v2 net-next 1/9] octeontx2-af: switch: Add AF to switch mbox and skeleton files Ratheesh Kannoth
` (8 more replies)
0 siblings, 9 replies; 16+ messages in thread
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
Marvell OcteonTX2 switch hardware is capable of accelerating L2, L3, and
flow. When representors are enabled through devlink, a logical port is
created in switch hardware for each representor device.
This patch series implements communication from the host OS to the switch
hardware and vice versa.
control plane (FDB / FIB / flow over mailbox)
+---------------------------------------------------------------------+
| HOST OS |
| |
| +------------+ +---------------------+ +---------------+ |
| | bridge / | | PF + notifiers | | TC / flower | |
| | routing | | (FDB, FIB, flow) | | offload | |
| +------+-----+ +-----------+---------+ +-------+-------+ |
| ^ | | |
| | slow path | | |
| | (unmatched pkts) v v |
| +------+------+ +------+------+ +------+------+ |
| | rep-eth0 | | PF / VF | <----> | RVU AF | |
| +------+------+ +-------------+ +------+------+ |
| | | |
| +------+------+ | |
| | rep-eth1 | | |
| +-------------+ | |
| | slow path (miss / control to host stack) | |
+---------------------------------------------------------------------+
| | | |
| | mailbox / PCIe | |
| v v |
+---------------------------------------------------------------------+
| SWITCH HARDWARE |
| |
| +---------------------------------------------------+ |
| | L2 tables | L3 tables | flow TCAM (HW) | |
| +-------+-------------+---------------+-------------+ |
| | fast path (HW) | |
| v v |
| lport0 <========================> lport1 |
| HW forwarding between ports |
| |
| slow path (miss): packets raised to host via representors |
+---------------------------------------------------------------------+
When representors are created, corresponding logical ports are created in
switchdev. Matching traffic is accelerated in switch hardware using
installed L2, L3, and flow rules. Packets that do not match offloaded
state, or that require software handling, take the slow path through
representors and the host networking stack. Control updates from the host
(FDB, FIB, TC flower) are sent to the AF over mailbox and programmed into
switch hardware tables.
Notifier callbacks are registered to receive system events such as FDB
add/delete and FIB add/delete. Flow add/delete operations are handled
through the ingress flow-table offload path. These events are captured and
processed by the NIC driver and forwarded to the switch device through the
AF driver. All message exchanges use the mailbox interface.
Bridge acceleration:
FDB add/delete notifications are processed, and learned SMAC information is
sent to the switch hardware. The switch inserts a hardware rule to
accelerate packets destined to the MAC address. Switch-initiated FDB
refresh is propagated back to the host bridge through an AF to PF/VF
mailbox and switchdev notifier.
L3 acceleration:
IPv4 and IPv6 FIB updates observed through netdev and FIB notifiers are
queued on the PF and sent to the AF. The AF batches fib_entry structures
and forwards them to switchdev when firmware is ready.
Flow acceleration:
TC flower rules accepted by the ingress flow-table offload callback are
translated into fl_tuple state, ingress and egress pcifunc are resolved
through FIB lookup, and flow updates are sent to the switch hardware
through the mailbox interface. Per-cookie packet counters are kept in sync
using NPC MCAM multi-stats when the switch requests a flow refresh.
Ratheesh Kannoth (9):
octeontx2-af: switch: Add AF to switch mbox and skeleton files
Host to switch mailbox definitions for FDB, FIB, flow, and flow stats;
AF-side L2/L3/flow skeleton objects.
octeontx2-af: switch: Add switch dev to AF mboxes
Switch to AF and AF to switchdev mailbox messages; interface query
handler and NPC helpers for flow delete/stats/features.
octeontx2-pf: switch: Add pf files hierarchy
CONFIG_OCTEONTX_SWITCH and stub PF switch objects for FDB, FIB, flow, and
notifier plumbing.
octeontx2-af: switch: Representor for switch port
Copy devlink switch_id to the AF and map representor pcifunc to a switch
port id when eswitch mode is enabled.
octeontx2-af: PAN switch TL1 scheduling and NPC channel control
Allocate multiple TL1 scheduler queues in PAN mode and honor caller
supplied NPC channel masks for multi-link steering.
octeontx2-pf: register switch notifiers for eswitch offload
Register PF notifier blocks for FIB, neighbour, address, netdev, and
switchdev FDB events; split IPv4/IPv6 handling.
octeontx2: plumb bridge FDB updates through AF and switchdev
End-to-end L2 offload from switchdev FDB notifications to AF to
switchdev, including firmware gating and FDB refresh to host.
octeontx2: offload host FIB updates to switch via AF mailbox
Queue IPv4/IPv6 FIB updates from notifiers and batch fib_entry delivery
from AF to switchdev.
octeontx2: add TC flow offload path for switch flows
Ingress flow-table offload for TC flower rules, AF forwarding to
switchdev, and flow counter refresh from switch to host.
.../net/ethernet/marvell/octeontx2/Kconfig | 13 +
.../ethernet/marvell/octeontx2/af/Makefile | 3 +-
.../net/ethernet/marvell/octeontx2/af/mbox.h | 227 ++++++++
.../net/ethernet/marvell/octeontx2/af/rvu.c | 110 ++++
.../net/ethernet/marvell/octeontx2/af/rvu.h | 6 +
.../ethernet/marvell/octeontx2/af/rvu_nix.c | 53 +-
.../ethernet/marvell/octeontx2/af/rvu_npc.c | 76 +++
.../marvell/octeontx2/af/rvu_npc_fs.c | 11 +
.../ethernet/marvell/octeontx2/af/rvu_rep.c | 3 +-
.../marvell/octeontx2/af/switch/rvu_sw.c | 48 ++
.../marvell/octeontx2/af/switch/rvu_sw.h | 14 +
.../marvell/octeontx2/af/switch/rvu_sw_fl.c | 294 ++++++++++
.../marvell/octeontx2/af/switch/rvu_sw_fl.h | 12 +
.../marvell/octeontx2/af/switch/rvu_sw_l2.c | 283 +++++++++
.../marvell/octeontx2/af/switch/rvu_sw_l2.h | 13 +
.../marvell/octeontx2/af/switch/rvu_sw_l3.c | 215 +++++++
.../marvell/octeontx2/af/switch/rvu_sw_l3.h | 11 +
.../ethernet/marvell/octeontx2/nic/Makefile | 13 +-
.../marvell/octeontx2/nic/otx2_txrx.h | 2 +
.../ethernet/marvell/octeontx2/nic/otx2_vf.c | 17 +
.../net/ethernet/marvell/octeontx2/nic/rep.c | 11 +
.../marvell/octeontx2/nic/switch/sw_fdb.c | 144 +++++
.../marvell/octeontx2/nic/switch/sw_fdb.h | 14 +
.../marvell/octeontx2/nic/switch/sw_fib.c | 132 +++++
.../marvell/octeontx2/nic/switch/sw_fib.h | 16 +
.../marvell/octeontx2/nic/switch/sw_fl.c | 546 ++++++++++++++++++
.../marvell/octeontx2/nic/switch/sw_fl.h | 15 +
.../marvell/octeontx2/nic/switch/sw_nb.c | 422 ++++++++++++++
.../marvell/octeontx2/nic/switch/sw_nb.h | 35 ++
.../marvell/octeontx2/nic/switch/sw_nb_v4.c | 335 +++++++++++
.../marvell/octeontx2/nic/switch/sw_nb_v4.h | 21 +
.../marvell/octeontx2/nic/switch/sw_nb_v6.c | 236 ++++++++
.../marvell/octeontx2/nic/switch/sw_nb_v6.h | 21 +
33 files changed, 3361 insertions(+), 11 deletions(-)
---
v1 -> v2: Addressed Jakub comments
https://lore.kernel.org/netdev/20260630024715.4124281-1-rkannoth@marvell.com/
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v2 net-next 1/9] octeontx2-af: switch: Add AF to switch mbox and skeleton files
2026-07-02 4:50 [PATCH v2 net-next 0/9] Switch support Ratheesh Kannoth
@ 2026-07-02 4:50 ` Ratheesh Kannoth
2026-07-02 4:50 ` [PATCH v2 net-next 2/9] octeontx2-af: switch: Add switch dev to AF mboxes Ratheesh Kannoth
` (7 subsequent siblings)
8 siblings, 0 replies; 16+ messages in thread
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
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 [flat|nested] 16+ messages in thread
* [PATCH v2 net-next 2/9] octeontx2-af: switch: Add switch dev to AF mboxes
2026-07-02 4:50 [PATCH v2 net-next 0/9] Switch support Ratheesh Kannoth
2026-07-02 4:50 ` [PATCH v2 net-next 1/9] octeontx2-af: switch: Add AF to switch mbox and skeleton files Ratheesh Kannoth
@ 2026-07-02 4:50 ` Ratheesh Kannoth
2026-07-09 9:56 ` Paolo Abeni
2026-07-02 4:50 ` [PATCH v2 net-next 3/9] octeontx2-pf: switch: Add pf files hierarchy Ratheesh Kannoth
` (6 subsequent siblings)
8 siblings, 1 reply; 16+ messages in thread
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
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 [flat|nested] 16+ messages in thread
* [PATCH v2 net-next 3/9] octeontx2-pf: switch: Add pf files hierarchy
2026-07-02 4:50 [PATCH v2 net-next 0/9] Switch support Ratheesh Kannoth
2026-07-02 4:50 ` [PATCH v2 net-next 1/9] octeontx2-af: switch: Add AF to switch mbox and skeleton files Ratheesh Kannoth
2026-07-02 4:50 ` [PATCH v2 net-next 2/9] octeontx2-af: switch: Add switch dev to AF mboxes Ratheesh Kannoth
@ 2026-07-02 4:50 ` Ratheesh Kannoth
2026-07-02 4:50 ` [PATCH v2 net-next 4/9] octeontx2-af: switch: Representor for switch port Ratheesh Kannoth
` (5 subsequent siblings)
8 siblings, 0 replies; 16+ messages in thread
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
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 [flat|nested] 16+ messages in thread
* [PATCH v2 net-next 4/9] octeontx2-af: switch: Representor for switch port
2026-07-02 4:50 [PATCH v2 net-next 0/9] Switch support Ratheesh Kannoth
` (2 preceding siblings ...)
2026-07-02 4:50 ` [PATCH v2 net-next 3/9] octeontx2-pf: switch: Add pf files hierarchy Ratheesh Kannoth
@ 2026-07-02 4:50 ` Ratheesh Kannoth
2026-07-02 4:50 ` [PATCH v2 net-next 5/9] octeontx2-af: PAN switch TL1 scheduling and NPC channel control Ratheesh Kannoth
` (4 subsequent siblings)
8 siblings, 0 replies; 16+ messages in thread
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
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 [flat|nested] 16+ messages in thread
* [PATCH v2 net-next 5/9] octeontx2-af: PAN switch TL1 scheduling and NPC channel control
2026-07-02 4:50 [PATCH v2 net-next 0/9] Switch support Ratheesh Kannoth
` (3 preceding siblings ...)
2026-07-02 4:50 ` [PATCH v2 net-next 4/9] octeontx2-af: switch: Representor for switch port Ratheesh Kannoth
@ 2026-07-02 4:50 ` Ratheesh Kannoth
2026-07-09 9:56 ` Paolo Abeni
2026-07-02 4:50 ` [PATCH v2 net-next 6/9] octeontx2-pf: register switch notifiers for eswitch offload Ratheesh Kannoth
` (3 subsequent siblings)
8 siblings, 1 reply; 16+ messages in thread
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
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 [flat|nested] 16+ messages in thread
* [PATCH v2 net-next 6/9] octeontx2-pf: register switch notifiers for eswitch offload
2026-07-02 4:50 [PATCH v2 net-next 0/9] Switch support Ratheesh Kannoth
` (4 preceding siblings ...)
2026-07-02 4:50 ` [PATCH v2 net-next 5/9] octeontx2-af: PAN switch TL1 scheduling and NPC channel control Ratheesh Kannoth
@ 2026-07-02 4:50 ` Ratheesh Kannoth
2026-07-09 9:56 ` Paolo Abeni
2026-07-02 4:50 ` [PATCH v2 net-next 7/9] octeontx2: plumb bridge FDB updates through AF and switchdev Ratheesh Kannoth
` (2 subsequent siblings)
8 siblings, 1 reply; 16+ messages in thread
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
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 [flat|nested] 16+ messages in thread
* [PATCH v2 net-next 7/9] octeontx2: plumb bridge FDB updates through AF and switchdev
2026-07-02 4:50 [PATCH v2 net-next 0/9] Switch support Ratheesh Kannoth
` (5 preceding siblings ...)
2026-07-02 4:50 ` [PATCH v2 net-next 6/9] octeontx2-pf: register switch notifiers for eswitch offload Ratheesh Kannoth
@ 2026-07-02 4:50 ` Ratheesh Kannoth
2026-07-09 9:56 ` Paolo Abeni
2026-07-02 4:50 ` [PATCH v2 net-next 8/9] octeontx2: offload host FIB updates to switch via AF mailbox Ratheesh Kannoth
2026-07-02 4:50 ` [PATCH v2 net-next 9/9] octeontx2: add TC flow offload path for switch flows Ratheesh Kannoth
8 siblings, 1 reply; 16+ messages in thread
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
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 [flat|nested] 16+ messages in thread
* [PATCH v2 net-next 8/9] octeontx2: offload host FIB updates to switch via AF mailbox
2026-07-02 4:50 [PATCH v2 net-next 0/9] Switch support Ratheesh Kannoth
` (6 preceding siblings ...)
2026-07-02 4:50 ` [PATCH v2 net-next 7/9] octeontx2: plumb bridge FDB updates through AF and switchdev Ratheesh Kannoth
@ 2026-07-02 4:50 ` Ratheesh Kannoth
2026-07-09 9:56 ` Paolo Abeni
2026-07-02 4:50 ` [PATCH v2 net-next 9/9] octeontx2: add TC flow offload path for switch flows Ratheesh Kannoth
8 siblings, 1 reply; 16+ messages in thread
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
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 [flat|nested] 16+ messages in thread
* [PATCH v2 net-next 9/9] octeontx2: add TC flow offload path for switch flows
2026-07-02 4:50 [PATCH v2 net-next 0/9] Switch support Ratheesh Kannoth
` (7 preceding siblings ...)
2026-07-02 4:50 ` [PATCH v2 net-next 8/9] octeontx2: offload host FIB updates to switch via AF mailbox Ratheesh Kannoth
@ 2026-07-02 4:50 ` Ratheesh Kannoth
2026-07-09 9:56 ` Paolo Abeni
8 siblings, 1 reply; 16+ messages in thread
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
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 [flat|nested] 16+ messages in thread
* Re: [PATCH v2 net-next 2/9] octeontx2-af: switch: Add switch dev to AF mboxes
2026-07-02 4:50 ` [PATCH v2 net-next 2/9] octeontx2-af: switch: Add switch dev to AF mboxes Ratheesh Kannoth
@ 2026-07-09 9:56 ` Paolo Abeni
0 siblings, 0 replies; 16+ messages in thread
From: Paolo Abeni @ 2026-07-09 9:56 UTC (permalink / raw)
To: rkannoth
Cc: linux-kernel, netdev, andrew+netdev, davem, edumazet, kuba,
pabeni, sgoutham
From: AI Reviewer <ai@example.com>
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
Full review at: https://netdev-ai.bots.linux.dev/sashiko/
---
Subject: octeontx2-af: switch: Add switch dev to AF mboxes
This patch introduces a set of new AF mailbox messages so that a
separate Marvell switch device (running its own Linux) can query
interface topology, mcam features, mcam multi-entry stats, and
multi-entry flow delete/free from the AF driver, and adds a stub
swdev2af_notify handler plus two up-direction message definitions.
A few questions on the diff below.
> 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) \
[Low]
The generated enum symbol for this entry becomes
MBOX_MSG_GET_IFACE_GET_INFO, which has a duplicated GET_ prefix and
does not match the handler/struct naming (iface_get_info,
iface_get_info_rsp). Was IFACE_GET_INFO intended here? This enum name
becomes part of the mbox ID namespace once released.
> +M(SWDEV2AF_NOTIFY, 0x015, swdev2af_notify, \
> + swdev2af_notify_req, msg_rsp) \
> /* CGX mbox IDs (range 0x200 - 0x3FF) */ \
[ ... ]
> @@ -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 */
> +};
[Low]
Is there a reason cnt is a signed int in both the request and the
response here, when npc_flow_del_n_free_req above uses u16 cnt?
The handler validates via:
if (!req->cnt || req->cnt > 256) {
which is a signed compare, so a negative req->cnt passes and rsp->cnt
is then assigned from req->cnt.
Any caller (current or future) that treats rsp->cnt as an unsigned
length before iterating rsp->stat[] would go out of bounds. Would a
u16 for both cnt fields be safer?
> +
> 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;
> + };
> + };
> +};
[Low]
Is unsigned long intentional in a mailbox payload? All other structs
in mbox.h use fixed-width types (u8/u16/u32/u64/__be16). The commit
message notes the switch runs on a separate Linux OS, so if the two
endpoints ever differ in bitness the on-wire size of cookie changes
silently. Same question applies to struct fl_info below. Would u64
be more appropriate?
> +
> +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;
> +};
[Low]
Was it intended to place is_vf and is_sdp as bitfields inside a u64
storage unit? That reserves 8 bytes per entry for two bits, and this
struct is embedded IFACE_MAX times in iface_get_info_rsp. Would
u8 is_vf:1; u8 is_sdp:1; work here?
> +
> +#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];
> +};
[Medium]
Is IFACE_MAX = 288 sufficient? rvu_hwinfo.total_vfs is a u16 populated
from a 12-bit HW register field, so total VFs can exceed 288 on some
SoCs. The handler silently truncates (goto done) and sets rsp->cnt to
the truncated count with no overflow indication, so the caller cannot
distinguish "all interfaces" from "truncated". This fixed cap also
becomes part of the effective wire layout once released.
> +
> +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];
> + };
> + };
> +};
[Low]
Is msg_type meant to be a bitmask or a selector? It is declared u64
with BIT_ULL(0..2) flags, which suggests multiple bits may be set
simultaneously, but the payload is an anonymous union whose members
are mutually exclusive. Would an enum-style tag value (0/1/2) match
the actual semantics better than BIT_ULL()? The choice is baked into
the mailbox ABI once merged.
> +
> 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++) {
[Medium]
Should this handler filter what it returns based on req->hdr.pcifunc?
As written, it walks every PF and every VF and returns pcifunc,
rx/tx channel base+count, SQ/CQ/RQ counts, tx_link, NIX block and
SDP flag for all of them, with no is_pffunc_af() gate and no
ownership/privilege check.
In multi-tenant SR-IOV where distinct PFs belong to distinct tenants,
a non-privileged PF sending mbox 0x014 would receive the full
resource layout of neighbouring PFs and VFs.
Was this intended to be an AF-only or privileged-caller-only message?
> + if (tot >= IFACE_MAX)
> + goto done;
> +
[ ... ]
> +done:
> + rsp->cnt = tot;
> +
> + return 0;
> +}
> +
[ ... ]
> 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;
> + }
[Low]
Can this branch ever fire? Looking at rvu_mbox_handler_npc_delete_flow()
in rvu_npc_fs.c, it unconditionally ends with return 0 and logs
per-rule failures via dev_err inside its own loop, so ret here should
always be 0. Is the delete-flow error handling reachable, or is this
dead code that hides failures?
> +
> + 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;
> +}
> +
[ ... ]
> @@ -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;
> + }
[High]
Does this early return leak mcam->lock? mutex_lock(&mcam->lock) was
taken just above the loop, and this path returns -EINVAL without
calling mutex_unlock().
Since req->entry[i] and req->hdr.pcifunc come from the mailbox, any
PF/VF can send a request containing an entry it does not own or one
that is out of range and permanently wedge mcam->lock. That mutex
serializes all NPC MCAM operations AF-wide, so once leaked, subsequent
allocate/free/read/enable/disable/stats operations from any PF or VF
would block forever.
Neighbouring handlers in this file (npc_mcam_entry_stats,
npc_mcam_free_entry, npc_mcam_read_entry, npc_mcam_dis_entry) unlock
before returning; would a "goto exit" style unlock path work here as
well?
[High]
Should this handler branch on is_cn20k(rvu->pdev) the way
rvu_mbox_handler_npc_mcam_entry_stats() does?
The single-entry stats handler has:
if (is_cn20k(rvu->pdev)) {
regval = rvu_read64(rvu, blkaddr,
NPC_AF_CN20K_MCAMEX_BANKX_STAT_EXT(index,
bank));
rsp->stat_ena = 1;
rsp->stat = regval;
...
return 0;
}
The new multi-entry handler skips this and always issues the legacy
STAT_ACT + MATCH_STATX sequence, which does not apply on CN20K
silicon. Would that produce garbage (or unmapped-address) reads on
CN20K?
> +
> + 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;
[Medium]
Should this loop also check rvu->hw->npc_stat_ena before deriving cntr
from STAT_ACT[8:0]?
The single-entry handler does:
if (!(regval & rvu->hw->npc_stat_ena)) {
rsp->stat_ena = 0;
mutex_unlock(&mcam->lock);
return 0;
}
For entries whose statistics counter is not enabled (a legal state),
this new code will take whatever the low 9 bits happen to be as a
counter index and return MATCH_STATX(random_index) to the caller as
that entry's stat value. Would checking npc_stat_ena per entry be
appropriate here too?
> + }
> +
> + rsp->cnt = req->cnt;
> +
> + mutex_unlock(&mcam->lock);
> + return 0;
> +}
> +
--
This is an AI-generated review.
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 net-next 5/9] octeontx2-af: PAN switch TL1 scheduling and NPC channel control
2026-07-02 4:50 ` [PATCH v2 net-next 5/9] octeontx2-af: PAN switch TL1 scheduling and NPC channel control Ratheesh Kannoth
@ 2026-07-09 9:56 ` Paolo Abeni
0 siblings, 0 replies; 16+ messages in thread
From: Paolo Abeni @ 2026-07-09 9:56 UTC (permalink / raw)
To: rkannoth
Cc: linux-kernel, netdev, andrew+netdev, davem, edumazet, kuba,
pabeni, sgoutham
From: AI Reviewer <ai@example.com>
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
Full review at: https://netdev-ai.bots.linux.dev/sashiko/
---
octeontx2-af: PAN switch TL1 scheduling and NPC channel control
This change adds NIX_TXSCH_ALLOC_FLAG_PAN so nix_txsch_alloc can request
more than one aggregate-level (TL1) index for PAN/switch mode, adds
TXSCHQ_FREE_PAN_TL1 to permit freeing those indices via
nix_txschq_free_one, and adds a set_chanmask field to
npc_install_flow_req. It also widens the NIX LF SQ bitmap allocation
and extends struct sg_list.
A few questions inline below.
> 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;
[Critical]
Should these two new flags be gated on the requester being the AF via
is_pffunc_af(req->hdr.pcifunc)?
Before this change, the aggregate level (TL1) was intentionally opaque
to non-AF callers: nix_txschq_free_one() short-circuited with
if (lvl >= hw->cap.nix_tx_aggr_lvl) return 0; and nix_txsch_alloc()
forced a single TL queue tied to the caller's mapped Tx link.
With the new flags there does not appear to be any is_pffunc_af() check
on the alloc or free paths, so any PF/VF can pass NIX_TXSCH_ALLOC_FLAG_PAN
to bypass the Tx-link mapping and touch TL1 slots that belong to
other links or other pcifuncs, or pass TXSCHQ_FREE_PAN_TL1 to free a
TL1 index. The existing comment in nix_txschq_free() says:
/* Do not clear pcifunc in txsch->pfvf_map[schq] because VF might
* be using this TL1 queue
*/
so if a PF frees a TL1 that a VF is still transmitting through, after
rvu_free_rsrc(&txsch->schq, schq) and
pfvf_map[schq] = TXSCH_MAP(0, NIX_TXSCHQ_FREE) that hardware slot is
reallocatable while the VF still uses it. Is that intended?
> @@ -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;
[Medium]
The commit message says:
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.
Where is set_chanmask consumed? A grep across the tree finds only this
struct field definition and no reader; the existing non-AF override in
rvu_npc_fs.c is unchanged:
if (!is_pffunc_af(req->hdr.pcifunc))
req->chan_mask = rvu_get_cpt_chan_mask(rvu);
Should the corresponding "keep chan_mask" hunk that consults
set_chanmask be part of this commit, or was it meant for a follow-up?
> 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
[ ... ]
> @@ -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;
> }
[Critical]
Can this branch let a caller-controlled count reach the alloc loop
without a MAX_TXSCHQ_PER_FUNC ceiling?
For non-PAN, aggregate-level requests were previously rejected unless
req_schq == 1. With NIX_TXSCH_ALLOC_FLAG_PAN, the check is skipped and
this function returns 0 before the general guard further down:
if (free_cnt < req_schq || req->schq[lvl] > MAX_TXSCHQ_PER_FUNC ||
req->schq_contig[lvl] > MAX_TXSCHQ_PER_FUNC)
return NIX_AF_ERR_TLX_ALLOC_FAIL;
rvu_mbox_handler_nix_txsch_alloc() later copies the request into rsp
and iterates:
for (idx = 0; idx < req->schq_contig[lvl]; idx++) {
schq = rsp->schq_contig_list[lvl][idx];
...
pfvf_map[schq] = TXSCH_MAP(pcifunc, 0);
nix_reset_tx_linkcfg(rvu, blkaddr, lvl, schq);
nix_reset_tx_shaping(rvu, blkaddr, nixlf, lvl, schq);
nix_reset_tx_schedule(rvu, blkaddr, lvl, schq);
}
rsp->schq_contig_list[lvl] and rsp->schq_list[lvl] are each
MAX_TXSCHQ_PER_FUNC entries. If req->schq_contig[lvl] exceeds
MAX_TXSCHQ_PER_FUNC, idx runs past the array, schq is read from
adjacent memory, and pfvf_map[schq] = TXSCH_MAP(pcifunc, 0) writes at
an attacker-controlled offset. Should the PAN branch keep the
MAX_TXSCHQ_PER_FUNC bound?
[High]
The PAN link index is computed as:
link = hw->cgx_links + hw->lbk_links + 1;
and the caller then does end = link + 1. For the TL1 level,
txsch->schq.max is programmed from NIX_AF_TL1_CONST in
nix_setup_txschq():
txsch->schq.max = cfg & 0xFFFF;
Is there anything guaranteeing that hw->cgx_links + hw->lbk_links + 2
is less than txsch->schq.max for TL1? The non-PAN path is bounded
because nix_get_tx_link() only returns values in the range
[0, cgx_links + lbk_links]. The new path adds one beyond that with no
ceiling check, so set_bit(schq, txsch->schq.bmap) below and later
NIX_AF_TL1X_CIR(schq) register writes could target undefined TL1
indices on silicon where TL1 count is small.
> @@ -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;
> + }
[Critical]
A few concerns on this new block.
First, idx advances unconditionally in the for-clause even when the
continue-on-busy path is taken. Consider start=L, end=L+1 where slot
L is already set in the bitmap and req->schq_contig[lvl] == 1:
iter 1: schq=L busy -> continue (idx becomes 1)
iter 2: schq=L+1 free -> set_bit(L+1); rsp->schq_contig_list[lvl][1] = L+1
rsp->schq_contig_list[lvl][0] stays as 0. The caller then loops
idx = 0 .. req->schq_contig[lvl] - 1 in rvu_mbox_handler_nix_txsch_alloc()
and picks up schq = rsp->schq_contig_list[lvl][0] = 0. That triggers
pfvf_map[0] = TXSCH_MAP(pcifunc, 0) if NIX_TXSCHQ_CFG_DONE is not set,
and nix_reset_tx_linkcfg/shaping/schedule(rvu, blkaddr, lvl, 0),
disturbing TL1[0]. Can this hijack TL1[0] from its real owner?
Second, this loop calls set_bit(schq, txsch->schq.bmap) once for each
free slot in [start, end], but never decrements
rsp->schq_contig[lvl]/rsp->schq[lvl] or otherwise reports how many
were placed into the response list. With req->schq_contig[lvl] == 1
and both slots free, both bits get set, but only one is reported back.
The bit for the extra slot then has no matching entry in pfvf_map, and
nix_txschq_free_one() gates on:
if (TXSCH_MAP_FUNC(pfvf_map[schq]) != pcifunc) {
rc = NIX_AF_ERR_TLX_INVALID;
goto err;
}
so the client cannot free it. Is the resulting bitmap leak intended?
Third, on the "no slot free" path, alloc stays false, dev_err() is
logged, and the function returns without clearing rsp->schq_contig[lvl]
or rsp->schq[lvl]. The mbox handler returns success, so consumers see
non-zero counts and then read rsp->schq_contig_list[lvl][0] == 0
(zero-initialized rsp) as a valid schq. Should this path signal
failure back to the caller?
> + /* 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 {
[High]
The commit message states:
Add TXSCHQ_FREE_PAN_TL1 so TL1 entries in that path can be freed
via nix_txsch_free where they were previously skipped.
Is nix_txsch_free() actually updated by this patch? Only
nix_txschq_free_one() gets a new gate. The bulk path nix_txschq_free()
still has if (lvl >= hw->cap.nix_tx_aggr_lvl) continue; in both its
scan and free loops, so aggregate-level bits set by the new
nix_txsch_alloc() block above are never cleared by that path, whether
it is reached via TXSCHQ_FREE_ALL or via nix_lf_teardown() on LF close
or FLR. Was a matching hunk in nix_txschq_free() intended?
> @@ -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;
--
This is an AI-generated review.
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 net-next 6/9] octeontx2-pf: register switch notifiers for eswitch offload
2026-07-02 4:50 ` [PATCH v2 net-next 6/9] octeontx2-pf: register switch notifiers for eswitch offload Ratheesh Kannoth
@ 2026-07-09 9:56 ` Paolo Abeni
0 siblings, 0 replies; 16+ messages in thread
From: Paolo Abeni @ 2026-07-09 9:56 UTC (permalink / raw)
To: rkannoth
Cc: linux-kernel, netdev, andrew+netdev, davem, edumazet, kuba,
pabeni, sgoutham
From: AI Reviewer <ai@example.com>
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
Full review at: https://netdev-ai.bots.linux.dev/sashiko/
---
octeontx2-pf: register switch notifiers for eswitch offload
This change registers switchdev, FIB, netevent, inetaddr, inet6addr,
and netdevice notifiers when devlink switch mode is enabled, adds
sw_nb_v4.c/sw_nb_v6.c for family-specific handlers, and extends
sw_nb.c with device filtering for Cavium ports behind bridges and
VLANs.
> 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
> @@ -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;
> }
[Medium]
Is the return value from sw_nb_register() and sw_nb_unregister()
intentionally discarded here?
sw_nb_register() has six register_*_notifier() calls and a full
err1..err5 unwind path, but the ternary drops any error and the caller
returns 0. The mbox esw_cfg_req has already been committed to the AF
before this line runs, so on notifier registration failure the AF sees
switch mode enabled while the PF has no observation infrastructure,
and devlink still reports success.
[Medium]
The notifier_block instances (sw_nb_fdb, sw_nb_fib, sw_nb_netevent,
sw_nb_v4_inetaddr, sw_nb_v6_inetaddr, sw_nb_netdev) and sw_nb_pf_netdev
are file-scope singletons in sw_nb.c. When devlink switch mode is
enabled on a second octeontx2 PF (multi-port CN10K parts have a devlink
instance per PF), sw_nb_register() re-registers the same static
notifier_block pointers.
For notifier chains that keep a single linked list of nodes, adding the
same node twice either fails with -EEXIST (silently, per the previous
comment) or corrupts the chain. sw_nb_pf_netdev is also unconditionally
overwritten so any netdev_info/netdev_dbg(sw_nb_pf_netdev, ...) is
attributed to the wrong PF, and once the first PF's netdev is freed
the pointer becomes a use-after-free for any callback that still
dereferences it.
Should these notifier_blocks live in a per-PF structure instead?
> 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
[ ... ]
> +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);
> +}
[High]
Is the assumption in the comment ("rtnl_lcok is already acquired")
correct for all callers?
sw_nb_is_valid_dev() is invoked from:
sw_nb_net_event() - netevent chain, atomic_notifier
sw_nb_v4_fib_event() - FIB chain, atomic_notifier
sw_nb_v6_fib_event() - FIB chain, atomic_notifier
sw_nb_fdb_event() - switchdev, atomic
sw_nb_netdev_event() - netdevice chain, rtnl-held
register_netevent_notifier() and register_fib_notifier() install into
atomic notifier chains (see net/core/netevent.c and
net/core/fib_notifier.c), so those callbacks are not guaranteed to run
with rtnl held.
netdev_walk_all_lower_dev() (the non-RCU variant) walks
dev->adj_list.lower without rcu_dereference, and
netdev_master_upper_dev_get_rcu() dereferences adj_list.upper without
an outer rcu_read_lock. Concurrent adjacency changes under rtnl can
then race the walk. Also, note the typo "rtnl_lcok" in the comment.
[ ... ]
> +static int sw_nb_netdev_event(struct notifier_block *unused,
> + unsigned long event, void *ptr)
> +{
[ ... ]
> +}
> +
> +static struct notifier_block sw_nb_netdev = {
> + .notifier_call = sw_nb_netdev_event,
> +};
> +
> +int sw_nb_unregister(struct net_device *netdev)
> +{
[ ... ]
> +}
> +EXPORT_SYMBOL(sw_nb_unregister);
> +
> +int sw_nb_register(struct net_device *netdev)
> +{
[ ... ]
> +}
> +EXPORT_SYMBOL(sw_nb_register);
> 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
[ ... ]
> +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);
[High]
Can kcalloc() return NULL here? The very next line dereferences entry
without a NULL check. The same pattern is present in
sw_nb_net_v4_neigh_update(), sw_nb_v6_netdev_event(),
sw_nb_v6_inetaddr_event(), and sw_nb_net_v6_neigh_update(). Two other
handlers in this same patch (sw_nb_v4_netdev_event and
sw_nb_v4_fib_event) do have the NULL check.
These callbacks run from atomic notifier chains (netevent, inet6addr)
during frequent events such as ARP/ND updates and address changes,
which are triggerable by an unprivileged party sending traffic on the
interface.
[Medium]
For the bridge-master branch below:
> + 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;
What happens when the bridge has zero slaves? pf_dev stays equal to
the bridge netdev itself, and netdev_priv(bridge) returns
struct net_bridge, not struct otx2_nic. Reading pcifunc off that
pointer produces an out-of-type read.
Unlike sw_nb_v4_fib_event() and sw_nb_net_v4_neigh_update(), this
handler is not gated by sw_nb_is_valid_dev(), so an empty bridge with
no ports still reaches this code.
[ ... ]
> +int sw_nb_v4_fib_event(struct notifier_block *nb,
> + unsigned long event, void *ptr)
> +{
[ ... ]
> + 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;
> + }
[ ... ]
> + 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;
[High]
Does this leak entries and haddr on the early-return paths?
The if (!cnt) return path returns before freeing entries or haddr.
The if (!hcnt) return path frees entries just above but then returns
without freeing haddr.
The second kcalloc failure path returns having freed only the first
entries allocation, leaving haddr allocated.
FIB events fire on every route change; a system where
sw_nb_is_valid_dev() rejects every nexthop will leak on every event.
This appears to be addressed in the follow-up commit
7f7c9aba2d92 ("octeontx2: offload host FIB updates to switch via AF
mailbox") which adds kfree() calls on each branch, but the leak is
present in this commit as-is.
[ ... ]
> +int sw_nb_net_v4_neigh_update(struct notifier_block *nb,
> + unsigned long event, void *ptr)
> +{
[ ... ]
> + 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);
[Medium]
Why does this call neigh_ha_snapshot() followed immediately by a
plain ether_addr_copy() on n->ha?
neigh_ha_snapshot() uses read_seqbegin/read_seqretry against
n->ha_lock precisely because n->ha updates from neigh_update() take
write_seqlock(&n->ha_lock) and are not atomic word-writes on all
architectures. The subsequent ether_addr_copy() is an unsynchronized
read of the same field and overwrites the snapshot with a possibly
torn value.
> 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
[ ... ]
> +int sw_nb_v6_netdev_event(struct notifier_block *unused,
> + unsigned long event, void *ptr)
> +{
[ ... ]
> + 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);
[High]
Is GFP_KERNEL safe under rcu_read_lock()? GFP_KERNEL implies
__GFP_DIRECT_RECLAIM and may sleep, which is illegal inside an RCU
read-side critical section and will trip
"BUG: sleeping function called from invalid context" with
CONFIG_DEBUG_ATOMIC_SLEEP.
Would GFP_ATOMIC, or moving the allocation outside the
rcu_read_lock() region, be more appropriate?
[High]
Is there a missing NULL check on this kcalloc()? The next statement
dereferences entry unconditionally.
[ ... ]
> +int sw_nb_v6_inetaddr_event(struct notifier_block *nb,
> + unsigned long event, void *ptr)
> +{
[ ... ]
> + entry = kcalloc(1, sizeof(*entry), GFP_ATOMIC);
> + entry->cmd = sw_nb_inetaddr_event_to_otx2_event(event, dev);
[High]
Same pattern as noted above - can this dereference NULL when kcalloc()
fails under memory pressure?
> +int sw_nb_net_v6_neigh_update(struct notifier_block *nb,
> + unsigned long event, void *ptr)
> +{
[ ... ]
> + entry = kcalloc(1, sizeof(*entry), GFP_ATOMIC);
> + entry->cmd = OTX2_NEIGH_UPDATE;
[High]
Same as above - is a NULL check missing before dereferencing entry?
--
This is an AI-generated review.
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 net-next 7/9] octeontx2: plumb bridge FDB updates through AF and switchdev
2026-07-02 4:50 ` [PATCH v2 net-next 7/9] octeontx2: plumb bridge FDB updates through AF and switchdev Ratheesh Kannoth
@ 2026-07-09 9:56 ` Paolo Abeni
0 siblings, 0 replies; 16+ messages in thread
From: Paolo Abeni @ 2026-07-09 9:56 UTC (permalink / raw)
To: rkannoth
Cc: linux-kernel, netdev, andrew+netdev, davem, edumazet, kuba,
pabeni, sgoutham
From: AI Reviewer <ai@example.com>
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
Full review at: https://netdev-ai.bots.linux.dev/sashiko/
---
octeontx2: plumb bridge FDB updates through AF and switchdev
This patch wires switchdev FDB add/delete notifications on the PF into
mailbox fdb_notify messages toward the AF, adds an AF worker to push L2
rules to the switchdev firmware, and introduces a new AF-to-PF/VF
MBOX_MSG_AF2PF_FDB_REFRESH message that ends up calling
call_switchdev_notifiers(SWITCHDEV_FDB_ADD_TO_BRIDGE, ...) on the target.
A few questions inline below.
> 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
[ ... ]
> @@ -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)
[Medium]
Adding MBOX_MSG_AF2PF_FDB_REFRESH to the same case as MBOX_MSG_CGX_LINK_EVENT
suppresses the msg->rc check. If the PF returns a non-zero rc for
AF2PF_FDB_REFRESH (for example MBOX_MSG_INVALID, see the next comment about
PF-side dispatch), is the intent to silently swallow it here or would it
be preferable to log it like every other message?
> @@ -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);
[High]
rvu_sw_shutdown() runs at the top of rvu_remove(), well before
rvu_mbox_destroy(&rvu->afpf_wq_info). While rvu_sw_l2_shutdown() does
cancel_work_sync() and destroy_workqueue() on both workqueues, it never
clears RVU_SWITCH_FLAG_FW_READY, never nulls the file-static
rvu_sw_l2_offl_wq / fdb_refresh_wq pointers, and does not stop new mbox
handlers from producing.
Can this race happen?
CPU0 (rvu_remove) CPU1 (afpf mbox worker)
rvu_sw_l2_shutdown()
cancel_work_sync(&fdb_...)
destroy_workqueue(fdb_refresh_wq) /* wq freed */
rvu_mbox_handler_fdb_notify()
if (flags & FW_READY) /* still set */
queue_work(rvu_sw_l2_offl_wq, ...);
/* UAF on freed wq */
Should the shutdown order be inverted (tear down mbox producers first, or
clear FW_READY under a lock that mbox handlers observe, then flush and
destroy the workqueues, then null the pointers)?
> 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
[ ... ]
> @@ -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;
> +}
[High]
Is there a reason this handler accepts SWDEV2AF messages from any pcifunc?
There is no is_cgx_config_permitted() style check that the sender is the
trusted switch-firmware PF.
The FW_STATUS path stores the caller-supplied req->pcifunc into
rvu->rswitch.pcifunc, and the REFRESH_FDB path forwards a caller-controlled
(pcifunc, mac) tuple to any PF via AF2PF_FDB_REFRESH — which ends up
invoking call_switchdev_notifiers(SWITCHDEV_FDB_ADD_TO_BRIDGE, ...) on the
target PF's netdev.
In an SR-IOV deployment, does this allow any VF to inject arbitrary
MAC->port entries into another tenant's bridge, or hijack the switch-firmware
identity by sending FW_STATUS(fw_up=true) with its own pcifunc?
[Medium]
The switch has no default: arm. SWDEV2AF_MSG_TYPE_REFRESH_FL is already
defined as BIT_ULL(2) but has no case here, so REFRESH_FL messages return
rc = 0 with no side effect and no diagnostic. Would returning
-EOPNOTSUPP / -EINVAL for unknown msg_type values make version-mismatch
issues visible?
[Low]
Also, since the SWDEV2AF_MSG_TYPE_* values are BIT_ULL(0), BIT_ULL(1),
BIT_ULL(2), the switch-equality only matches when exactly one bit is set.
Is msg_type intended to be a bitmask (in which case case labels won't
combine) or a plain enum (in which case using BIT_ULL() as the values is
misleading)?
> 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
[ ... ]
> +/* 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;
[Low]
Nit: "There is only on switch HW" is missing the word "one".
[ ... ]
> +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));
[Critical]
Where is the bounds check on pcifunc / vidx? pcifunc originates from
req->pcifunc of the peer-sent swdev2af_notify_req and reaches here verbatim
via rvu_sw_l2_fdb_list_entry_add() -> l2_entry->port_id.
In the else branch, when rvu_get_pf(rvu->pdev, pcifunc) == 0:
vidx = pcifunc - 1;
otx2_mbox_alloc_msg_rsp(&rvu->afvf_wq_info.mbox_up, vidx, ...);
If a peer sends REFRESH_FDB with pcifunc == 0, vidx becomes -1 and
otx2_mbox_alloc_msg_rsp() does:
struct otx2_mbox_dev *mdev = &mbox->dev[devid];
spin_lock(&mdev->mbox_lock);
which is an out-of-bounds access on mbox->dev[-1] followed by
otx2_mbox_wait_for_zero() / otx2_mbox_msg_send_up() dereferencing the
same address.
Similarly in the if branch, pf is used as devid for
rvu->afpf_wq_info.mbox_up without checking pf < afpf_wq_info.mbox_up.ndevs.
Would something like is_pf_func_valid() or an explicit ndevs range check
before dispatching the message be needed here?
[ ... ]
> +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;
> +}
[High]
Where does fw_is_up ever get reset back to false? It is never cleared —
not on fw_up=false, not on rvu_sw_l2_shutdown(), not on the -ENOMEM
paths above.
Consequences:
1. A firmware bounce (up -> down -> up) enters the second fw_up=true
message with fw_is_up already true. The guard fails, and the
else tail runs instead — actively clearing FW_READY,
flush_work()ing, and setting rswitch->pcifunc = -1.
2. Any fw_up=false event after init permanently disables offload,
since fw_up=true never re-enters the allocation branch.
3. rswitch->pcifunc is u16 and is assigned -1, which becomes 0xFFFF,
then flows back into rvu_get_pf() as a bogus PF index used as
an mbox devid.
Should fw_is_up be cleared on fw_up=false and on shutdown?
[High]
On the second alloc_workqueue failing while the first has succeeded,
this returns -ENOMEM without destroying rvu_sw_l2_offl_wq. Because
fw_is_up was set to true a few lines earlier, no subsequent FW_STATUS
message can retry the init. Additionally, rvu_sw_l2_shutdown() begins
with:
if (!fdb_refresh_wq)
return;
so if the driver ends up in this partial-init state, shutdown does
nothing and rvu_sw_l2_offl_wq (with a live worker kthread) leaks for
the module lifetime.
Could the error path destroy_workqueue(rvu_sw_l2_offl_wq), NULL the
pointer, and clear fw_is_up before returning?
[Medium]
The tail unconditionally runs:
flush_work(&l2_offl_work.work);
but INIT_WORK(&l2_offl_work.work, ...) only runs inside the if (fw_up &&
!fw_is_up) branch. If the very first swdev2af message received is
FW_STATUS(fw_up=false) — or any message that misses the init branch —
the work_struct is still BSS-zero-initialized when flush_work runs.
__flush_work() does WARN_ON(!work->func). Is that intended?
[Low]
Also, is the workqueue name "swdev_fdb_refresg_wq" a typo of "refresh"?
The name shows up in /proc/ and worker thread names.
[ ... ]
> +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;
> +}
[High]
This handler is reachable directly from rvu_mbox_handler_swdev2af_notify()
via SWDEV2AF_MSG_TYPE_REFRESH_FDB, and it does not check
RVU_SWITCH_FLAG_FW_READY or that fdb_refresh_wq is non-NULL (the sibling
rvu_mbox_handler_fdb_notify() below does check FW_READY).
If a peer sends REFRESH_FDB before any FW_STATUS(fw_up=true), fdb_refresh_wq
is still NULL and queue_work(NULL, ...) reaches __queue_work() which
dereferences wq->flags. Is the mbox ordering guaranteed by some external
mechanism, or should this path also gate on FW_READY / a non-NULL wq?
[Medium]
This function allocates and appends unconditionally on every incoming
REFRESH_FDB message, with no cap on list length or per-caller quota.
Combined with the identical pattern in rvu_mbox_handler_fdb_notify()
below, can a peer that sends messages faster than the workqueue drains
them drive unbounded kernel memory allocation until OOM? The workqueue
handler processes only 16 entries per invocation before re-queueing under
a mutex + mbox send + wait.
[ ... ]
> 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;
> }
[High]
Is the RVU_SWITCH_FLAG_FW_READY check safe against concurrent teardown?
The flag is read here, several statements happen (kcalloc, list mutex
acquire/release), and then queue_work() runs. Between the check and
queue_work(), another thread running rvu_sw_l2_init_offl_wq(fw_up=false)
or rvu_sw_shutdown() can clear the flag and destroy rvu_sw_l2_offl_wq,
leaving queue_work() to touch a freed workqueue.
An ad-hoc flag check without a covering lock (or RCU with
synchronize_rcu() before destroy_workqueue()) does not provide
synchronization. How is this expected to work?
> +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);
[High]
cancel_work_sync() only cancels currently-pending work; it does not stop
new invocations. Because RVU_SWITCH_FLAG_FW_READY is not cleared here,
rvu_mbox_handler_fdb_notify() can still pass its FW_READY check and call
queue_work(rvu_sw_l2_offl_wq, ...) after this destroy_workqueue(). Also,
rvu_sw_l2_fdb_list_entry_add() has no FW_READY guard at all.
Should the FW_READY flag be cleared (and the mbox handlers taught to see
that) before cancel_work_sync + destroy_workqueue, and should the two
global workqueue pointers be nulled at the end so the shutdown guard
"if (!fdb_refresh_wq) return;" actually distinguishes torn-down from
initialized?
> 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
[ ... ]
> @@ -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;
> +
[High]
The AF-to-PF path is wired only in otx2_vf.c. Is the PF-side dispatcher
(otx2_process_mbox_msg_up() in nic/otx2_pf.c) updated in a companion patch?
Grepping this tree, otx2_pf.c's switch expands MBOX_UP_CGX_MESSAGES,
MBOX_UP_MCS_MESSAGES and MBOX_UP_REP_MESSAGES, but not
MBOX_UP_AF2PF_FDB_REFRESH_MESSAGES. When rvu_sw_l2_fdb_refresh() takes
its if (pf) branch and sends AF2PF_FDB_REFRESH over afpf_wq_info.mbox_up,
the receiving PF hits the default: arm and calls
otx2_reply_invalid_msg(), so the SWITCHDEV_FDB_ADD_TO_BRIDGE notifier is
never invoked on PF netdevs.
Since bridges are typically hosted on PF netdevs, this seems to defeat
the "invoke the switchdev notifier so the host bridge can learn the
updated FDB entry" goal from the commit message for the PF case.
[Medium]
Looking at struct af2pf_fdb_refresh_req, there is only pcifunc + mac[6]
— no flags — and otx2_mbox_up_handler_af2pf_fdb_refresh() hardcodes
SWITCHDEV_FDB_ADD_TO_BRIDGE. The downstream fdb_notify direction carries
FDB_ADD/FDB_DEL bits in req->flags, but the upstream refresh direction
can only add. If firmware ages an entry out, how does the host bridge
learn to delete it — is a delete-refresh planned, and if so should the
mailbox structure grow a flags field now to avoid an ABI change later?
> 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
[ ... ]
> +static DEFINE_SPINLOCK(sw_fdb_llock);
> +static LIST_HEAD(sw_fdb_lh);
[ ... ]
> +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);
[ ... ]
> + 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;
> +}
[High]
Can this deadlock? sw_fdb_add_to_list() is called from sw_nb_fdb_event()
via call_switchdev_notifiers(), and switchdev_notif_chain is an
ATOMIC_NOTIFIER_HEAD in net/switchdev/switchdev.c, so notifier callbacks
run in the caller's context — including softirq/BH context from bridge
learn paths like br_handle_frame_finish -> br_fdb_update ->
br_switchdev_fdb_notify -> call_switchdev_notifiers.
Meanwhile sw_fdb_wq_handler() runs in process context and takes
spin_lock(&sw_fdb_llock) without disabling BH.
process ctx (workqueue) softirq on same CPU
spin_lock(&sw_fdb_llock) (bridge learn arrives)
call_switchdev_notifiers()
sw_nb_fdb_event()
sw_fdb_add_to_list()
spin_lock(&sw_fdb_llock)
/* spins forever */
Should this be spin_lock_bh() / spin_unlock_bh() everywhere sw_fdb_llock
is taken?
[ ... ]
> 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;
> }
[High]
sw_fdb_init() now returns -ENOMEM on alloc_workqueue failure, but
sw_nb_register() (drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c)
calls it as:
sw_fdb_init();
sw_fib_init();
sw_fl_init();
with the return value discarded. If sw_fdb_wq stays NULL, any subsequent
SWITCHDEV_FDB_ADD/DEL_TO_DEVICE event calls sw_fdb_add_to_list() ->
queue_work(NULL, ...), and later sw_fdb_deinit() calls
destroy_workqueue(NULL), each of which dereferences the NULL wq pointer.
Would it make sense to propagate the error out of sw_nb_register()?
[ ... ]
> +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);
[Medium]
otx2_mbox_up_handler_af2pf_fdb_refresh() is defined and exported from
sw_fdb.c, which the Makefile links into rvu_nicpf. otx2_vf.c (which
lives in rvu_nicvf) calls it, creating a hard module dependency: the VF
driver will fail to load without the PF driver present. Is that
intended? In VF-passthrough VMs, only rvu_nicvf is loaded.
Also, is EXPORT_SYMBOL (rather than EXPORT_SYMBOL_GPL) intentional for a
driver-internal helper?
--
This is an AI-generated review.
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 net-next 8/9] octeontx2: offload host FIB updates to switch via AF mailbox
2026-07-02 4:50 ` [PATCH v2 net-next 8/9] octeontx2: offload host FIB updates to switch via AF mailbox Ratheesh Kannoth
@ 2026-07-09 9:56 ` Paolo Abeni
0 siblings, 0 replies; 16+ messages in thread
From: Paolo Abeni @ 2026-07-09 9:56 UTC (permalink / raw)
To: rkannoth
Cc: linux-kernel, netdev, andrew+netdev, davem, edumazet, kuba,
pabeni, sgoutham
From: AI Reviewer <ai@example.com>
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
Full review at: https://netdev-ai.bots.linux.dev/sashiko/
---
octeontx2: offload host FIB updates to switch via AF mailbox
This change wires up an IPv4/IPv6 FIB offload path: the switch PF pushes
fib_entry batches to the RVU AF via a fib_notify mbox, and the AF forwards
them back down to the switch PF via a new af2swdev FIB_CMD. A pair of
workqueues on both ends batch and post the entries.
Some questions follow inline.
> 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];
> };
[Critical]
Can this shrink cause an out-of-bounds write in
rvu_sw_l3_offl_rule_push()? The AF-side handler still accepts up to 16
entries per fib_notify:
if (req->cnt > 16)
return -EINVAL;
and the batching guard in sw_l3_offl_work_handler()
if (lcnt + l3_entry->cnt > 8 && !list_empty(&l3_local_lh))
only splits when the local list is already non-empty, so the first
l3_entry in a batch is always accepted regardless of its cnt. A single
fib_notify with cnt in [13,16] therefore reaches
memcpy(dst, entry, sizeof(*entry) * cnt);
writing up to 16 struct fib_entry into req->entry[12], and overflows the
mbox message by 1..4 entries (roughly 60..240 bytes) into adjacent mbox
memory.
Also, req->cnt is a u8 while tot_cnt is int, so any total that batches
past 255 would silently truncate on the assignment
req->cnt = tot_cnt;
Would it be clearer to either raise entry[] back to 16, cap fib_notify at
12, or clamp/split at the memcpy site with tot_cnt validated against a
u8?
> 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
[ ... ]
> +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;
[Low]
Is the second declaration of sw_l3_offl_wq intentional? It appears once
above the struct l3_entry definition and again immediately after, so the
translation unit has two tentative definitions of the same object.
> +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)
> +{
[ ... ]
> + 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;
> +}
[High]
When otx2_mbox_wait_for_zero() returns false (mbox still busy after the
~1ms wait), otx2_mbox_msg_send_up() is skipped but the loop above has
already drained every l3_entry from lh and kfree()d them.
Does this silently drop the batch? The allocated mbox message is left
dangling in the up-channel, the fib_entry payload has been freed, and
the caller only logs pr_err() without any re-queue, so the switch state
appears to permanently diverge from the host FIB whenever the mbox is
momentarily busy.
> +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);
[Medium]
Do req_cnt, ack_cnt, and req_processed model any real acknowledgement
protocol? They are only ever written from inside this function and
neither otx2_mbox_wait_for_zero() nor otx2_mbox_msg_send_up() touches
them, so the (req > ack) branch effectively only ever fires on the first
invocation after new entries arrive and then
atomic64_set(&ack_cnt, req);
immediately equalizes them. Would this be clearer as a plain "if new
work arrived, re-poll shortly, otherwise poll slowly" without the ack
counters?
[Medium]
Do the two early-return branches leak the entries parked in l3_local_lh?
The while loop above moves items from l3_offl_lh into the module-static
l3_local_lh, and then both
if (req > ack) { ... return; }
if (req == proc) { ... return; }
return without splicing l3_local_lh into l3lh for processing. If
rvu_sw_l3_shutdown() then runs, it only drains l3_offl_lh:
while (1) {
entry = list_first_entry_or_null(&l3_offl_lh,
struct l3_entry, list);
so entries parked in l3_local_lh remain allocated. Would it be safer to
splice l3_local_lh back into l3_offl_lh before returning, or drain both
lists in the shutdown path?
> +
> +process:
> + lcnt = 0;
[ ... ]
> +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;
[High]
Should this return an error instead of 0 when firmware is not ready?
Returning 0 tells the caller the update was accepted, but nothing is
buffered or resynced later, so any FIB event that arrives during the
pre-ready window is silently dropped. Because FIB deltas are
edge-triggered, a single missed add/del leaves the offload permanently
divergent from the host FIB with no user-visible signal.
> +
> + 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;
> }
[High]
Is the return value of alloc_workqueue() handled anywhere? Under memory
pressure it can return NULL and queue_delayed_work() will then
dereference a NULL pool. Additionally l3_offl_work_running is set to
true unconditionally, so on the next invocation the branch is skipped
and every future fib_notify piles a new l3_entry onto l3_offl_lh with
no worker to drain it.
[High]
Is this check-then-set race-safe? It runs outside l3_offl_llock, and
mbox handlers can dispatch from separate PF worker contexts. Two
concurrent invocations can both observe l3_offl_work_running == false,
both call alloc_workqueue(), and one of the pointers is then overwritten
into sw_l3_offl_wq while the other workqueue leaks along with any
delayed work already queued on it.
Related: rvu_sw_l3_shutdown() destroys sw_l3_offl_wq but does not clear
sw_l3_offl_wq to NULL or reset l3_offl_work_running:
> +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);
> +}
After shutdown, if rvu_mbox_handler_fib_notify() runs again it sees
l3_offl_work_running still true and calls queue_delayed_work() on the
destroyed workqueue. Would it help to move the workqueue lifetime to
subsystem init/deinit (like rvu_sw_l2) and always clear both pieces of
state here?
> 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
[ ... ]
> +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);
> + }
[High]
Should the return value of sw_fib_notify() be checked? It can fail via
cnt > 16 returning -EINVAL, mbox alloc returning -ENOMEM, or
otx2_sync_mbox_msg() transport failure, and all three paths simply drop
the FIB delta with no retry and no diagnostic. Because FIB updates are
edge-triggered, a single lost message leaves the switch state
permanently out of sync.
> +
> + 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;
[High]
Does this leak the caller's entry on ENOMEM? Every call site in this
patch (sw_nb_v4_netdev_event, sw_nb_v4_inetaddr_event,
sw_nb_v4_fib_event x2, sw_nb_net_v4_neigh_update, sw_nb_v6_netdev_event,
sw_nb_v6_fib_event, sw_nb_net_v6_neigh_update, sw_nb_v6_inetaddr_event)
hands ownership of the entry to this function and drops the reference:
- kfree(entry);
+ sw_fib_add_to_list(pf_dev, entry, 1);
The return value is ignored in every caller, so on GFP_ATOMIC failure
the fib_entry buffer is stranded. Would it make sense to kfree(entry)
on the error path (or take ownership only after the lentry allocation
succeeds)?
[Medium]
Is there a cnt upper bound? sw_fib_notify() rejects cnt > 16 but
sw_fib_add_to_list() does not, and sw_nb_v4_fib_event() computes
cnt = iter - entries;
bounded by fi->fib_nhs. For multipath configurations with more than 16
next-hops the entry is happily queued and then silently discarded inside
the worker. Should the notifier either split the batch or refuse early
so the sender can adjust?
> +
> + 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;
> +}
[High]
Is sw_fib_llock protected against softirq reentry here? This function
uses plain spin_lock()/spin_unlock() but sw_fib_work_handler() and
sw_fib_deinit() both acquire the same lock with spin_lock_bh().
sw_nb_net_v4_neigh_update() and sw_nb_net_v6_neigh_update() are called
from the netevent chain, which the neighbour subsystem can invoke in
softirq context (e.g., from __neigh_update() paths driven by RX
processing). If CPU X is holding sw_fib_llock via spin_lock() in
process context and a softirq on the same CPU reaches
sw_fib_add_to_list() as well, the softirq spins waiting on a lock that
only its preempted process context can release.
Would spin_lock_bh() be more appropriate here?
[ ... ]
> +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);
> + }
> }
[High]
Is there anything in this module that prevents sw_fib_add_to_list() from
being called during or after sw_fib_deinit()? The current caller order
in sw_nb_unregister() unregisters notifiers first, but sw_fib.c has no
self-contained shutdown flag or nulling of sw_fib_wq. A stray notifier
callback that reaches sw_fib_add_to_list() after destroy_workqueue()
would call queue_delayed_work() on a freed workqueue pointer. Would
setting sw_fib_wq to NULL under the lock and checking it in
sw_fib_add_to_list() be robust against future ordering changes?
> 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
> @@ -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;
> }
[High]
Does this drop the guard on the wrong caller? The other callers of the
v6 helpers in this file (register/unregister, sw_nb_v6_fib_event,
sw_nb_v6_inetaddr_event dispatch, netevent v6 dispatch) still sit under
their own IS_ENABLED(CONFIG_IPV6) blocks, and drivers/net/ethernet/
marvell/octeontx2/nic/Makefile only compiles sw_nb_v6.o under
ifdef CONFIG_IPV6
so sw_nb_v6.c's body is wrapped in the same #if. On
CONFIG_OCTEONTX_SWITCH=y with CONFIG_IPV6=n, Kconfig does not select or
depend on IPV6, so the reference to sw_nb_v6_netdev_event() would remain
in the object file with no definition to link against. Should this
guard be restored, or should sw_nb_v6.h grow a static inline stub for
!CONFIG_IPV6?
> 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
> @@ -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;
> }
[High]
Not introduced by this patch, but the switch to sw_fib_add_to_list()
lengthens the exposure of a pre-existing kcalloc(GFP_ATOMIC) that is
never NULL-checked:
entry = kcalloc(1, sizeof(*entry), GFP_ATOMIC);
entry->cmd = sw_nb_inetaddr_event_to_otx2_event(event, dev);
Previously a NULL entry crashed here at the immediate dereference;
now the pointer also gets stored in a work item. The same pattern
exists in sw_nb_net_v4_neigh_update(), sw_nb_v6_netdev_event()
(GFP_KERNEL), sw_nb_net_v6_neigh_update(), and
sw_nb_v6_inetaddr_event(). Could the NULL checks be added while
touching these paths?
--
This is an AI-generated review.
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 net-next 9/9] octeontx2: add TC flow offload path for switch flows
2026-07-02 4:50 ` [PATCH v2 net-next 9/9] octeontx2: add TC flow offload path for switch flows Ratheesh Kannoth
@ 2026-07-09 9:56 ` Paolo Abeni
0 siblings, 0 replies; 16+ messages in thread
From: Paolo Abeni @ 2026-07-09 9:56 UTC (permalink / raw)
To: rkannoth
Cc: linux-kernel, netdev, andrew+netdev, davem, edumazet, kuba,
pabeni, sgoutham
From: AI Reviewer <ai@example.com>
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
Full review at: https://netdev-ai.bots.linux.dev/sashiko/
---
octeontx2: add TC flow offload path for switch flows
This patch registers a TC flow offload path that translates flower rules
into fl_tuple state, resolves ingress/egress pcifunc via FIB, and forwards
the results to the AF over the PF mailbox. A few observations follow
inline.
> diff --git a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.c b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.c
> --- 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 @@
> 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);
[High]
Is the change from u32 to u16 here intentional? The next line builds
a value that spans bits 0..31:
port_id = FIELD_PREP(GENMASK_ULL(31, 16), rep_id) |
FIELD_PREP(GENMASK_ULL(15, 0), pcifunc);
Storing that back into a u16 port_id drops the rep_id (bits 16..31)
before the return, so callers that stash the result as u32 (l2_entry,
l3_entry, fl_entry->port_id) always see zero in the upper half. Would
that not defeat rep_id-based port disambiguation once rep_id != 0?
> 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
> --- 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 @@
> +static struct workqueue_struct *sw_fl_offl_wq;
> +
> +struct fl_entry {
[ ... ]
> +};
> +
> +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;
[Low]
sw_fl_offl_wq is declared twice as a static in the same translation
unit. Both declarations resolve to the same tentative definition, but
was the duplication intentional?
> +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++;
> + }
[Critical]
Can this overflow the fixed-size arrays sized by cnt? The guard only
bounds cnt at 256, but tot can grow up to 2 * cnt = 512 when every
entry has uni_di == 0. Three arrays are then indexed by tot:
u16 i2idx_map[256]; /* stack */
req->entry[256]; /* struct npc_mcam_get_mul_stats_req */
rsp->stat[256]; /* struct npc_mcam_get_mul_stats_rsp */
> + 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];
> + }
[Critical]
With cnt = 256 and all uni_di = 0, would this loop write i2idx_map[0..511]
and req->entry[0..511], corrupting the kernel stack and the adjacent heap
region? The subsequent read loop then reads rsp->stat[i++] beyond its
256-slot array too. rvu_mbox_handler_npc_mcam_mul_stats() rejects
req->cnt > 256 later, but the OOB writes happen before that check.
Should the bound be cnt <= 128 (so tot <= 256), or should tot itself be
capped?
[Critical]
Separately, is cnt validated against the wire format? struct
swdev2af_notify_req embeds struct fl_info fl[64], and
rvu_mbox_handler_swdev2af_notify() forwards req->cnt to this helper
without checking it. For any 64 < cnt <= 256 the loops here read
fl[64..cnt-1] out of the mailbox payload into adjacent memory, then
propagate those bytes into req->entry[] and back through the mailbox.
Should the guard be cnt < 0 || cnt > ARRAY_SIZE(fl) rather than
cnt > 256 (cnt is a signed int, so negative values also slip through
today)?
> +int rvu_mbox_handler_fl_notify(struct rvu *rvu,
> + struct fl_notify_req *req,
> + struct msg_rsp *rsp)
> {
> + struct fl_entry *fl_entry;
[ ... ]
> + 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;
> }
[High]
On the very first invocation, if alloc_workqueue() returns NULL (for
example under memory pressure), fl_offl_work_running stays false and
sw_fl_offl_wq stays NULL, and queue_delayed_work(NULL, ...) is then
dispatched unconditionally after the mutex is dropped. Does that not
NULL-deref inside __queue_delayed_work()? Also, fl_entry has already
been list_add_tail'd, so if this path returns early there is nothing to
drain the list, and rvu_sw_fl_shutdown() early-returns on
!sw_fl_offl_wq, so the queued entry looks like it would leak.
> +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(&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);
> +}
[High]
After destroy_workqueue(), sw_fl_offl_wq is not set to NULL and
fl_offl_work_running is not reset. If a concurrent (or later)
rvu_mbox_handler_fl_notify() runs, it observes fl_offl_work_running ==
true, skips reallocation, and calls queue_delayed_work(sw_fl_offl_wq,
...) on a freed workqueue. Is that intentional, or should shutdown
clear both under fl_offl_llock so subsequent handlers either reallocate
or bail out?
[Medium]
Also, the early return on !sw_fl_offl_wq skips the cleanup of
sw_fl_stats_lh below. Nodes on that list are allocated by
rvu_sw_fl_stats_sync2db_one_entry() independently of whether the
offload workqueue ever came up, so on a shutdown where the workqueue
was never allocated they leak. Would moving the stats-list cleanup
above the early return match the allocation lifetime?
> diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.c b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.c
> --- 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 @@
[ ... ]
> +static int sw_fl_parse_actions(struct otx2_nic *nic,
[ ... ]
> + 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;
[Medium]
On a CT offload error that is not -EEXIST, the code only prints and
breaks out of the switch without setting the CT bit and without
returning the error. Processing continues; if any later action sets
*op (for example FLOW_ACTION_REDIRECT), sw_fl_add() sees a non-zero op
that lacks the CT bit, skips the CT early-return, and offloads the
rule as a plain REDIRECT. Should the error be returned to the caller
so the block callback surfaces it, rather than silently converting a
CT rule into a non-CT one?
> +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;
[ ... ]
> + 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;
[High]
Is an IS_ERR(rsp) check missing here? otx2_mbox_get_rsp() returns
ERR_PTR(-ENODEV) on failure, and every other caller in this driver
(cn10k.c, cn10k_macsec.c, otx2_common.c, otx2_tc.c, ...) uses
IS_ERR(rsp) before dereferencing. Without the check, an error return
here is dereferenced as a valid pointer.
> +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;
[ ... ]
> +}
> +
> 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)
> {
[ ... ]
> + cancel_work_sync(&sw_fl_work);
> + destroy_workqueue(sw_fl_wq);
[ ... ]
> }
[Medium]
init_done and sw_fl_wq are used together as an ad-hoc gate: the
callback reads init_done as a plain load and then paths off that read
call queue_work(sw_fl_wq, ...). Since the two stores are plain writes
in sw_fl_init(), can a weakly ordered CPU see init_done == true while
still seeing sw_fl_wq as NULL? A smp_store_release() / smp_load_acquire()
pair, or moving the flag/wq under a real lock, would remove the
ordering hole.
[High]
Also, sw_fl_deinit() destroys sw_fl_wq without clearing init_done or
NULLing sw_fl_wq. Since the callback is registered via
nf_flow_table_offload_add_cb() and TC block infrastructure, callbacks
that arrive after deinit will still see init_done == true and can
queue_work() on a freed workqueue. Is there a missing callback
unregistration step here?
> 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
> --- /dev/null
> +++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_trace.h
> @@ -0,0 +1,82 @@
> +#undef TRACE_SYSTEM
> +#define TRACE_SYSTEM rvu
[Medium]
The trace system name rvu is already owned by
drivers/net/ethernet/marvell/octeontx2/af/rvu_trace.h, which files the
core AF-PF mailbox protocol events (otx2_msg_alloc / otx2_msg_send /
otx2_msg_process) under /sys/kernel/tracing/events/rvu/. Adding a
second CREATE_TRACE_POINTS site in nic/switch/sw_trace.c under the
same system name mixes these driver-private debug events with the
pre-existing protocol events in user-visible tracefs. Would a
distinct name (rvu_sw, octeontx2_sw, ...) avoid the collision?
> +TRACE_EVENT(sw_fl_dump,
> + TP_PROTO(const char *fname, int line, struct fl_tuple *ftuple),
[ ... ]
> + 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,
[ ... ]
> +);
> +
> +TRACE_EVENT(sw_act_dump,
> + TP_PROTO(const char *fname, int line, u32 act),
[ ... ]
> + TP_printk("[%s:%d] %u",
> + __get_str(fname), __entry->line, __entry->act)
> +);
[Low]
These tracepoints capture __func__ and __LINE__ from the call site and
render them in TP_printk. Since tracepoint output is treated as
stable ABI (see Documentation/trace/tracepoints.rst), any future
refactor in sw_fl.c will silently change the visible trace output.
Since the events are also EXPORT_TRACEPOINT_SYMBOL'd despite only being
used from this driver, would netdev_dbg / dynamic-debug fit the intent
better than TRACE_EVENT here?
--
This is an AI-generated review.
^ permalink raw reply [flat|nested] 16+ messages in thread
end of thread, other threads:[~2026-07-09 9:56 UTC | newest]
Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-02 4:50 [PATCH v2 net-next 0/9] Switch support Ratheesh Kannoth
2026-07-02 4:50 ` [PATCH v2 net-next 1/9] octeontx2-af: switch: Add AF to switch mbox and skeleton files Ratheesh Kannoth
2026-07-02 4:50 ` [PATCH v2 net-next 2/9] octeontx2-af: switch: Add switch dev to AF mboxes Ratheesh Kannoth
2026-07-09 9:56 ` Paolo Abeni
2026-07-02 4:50 ` [PATCH v2 net-next 3/9] octeontx2-pf: switch: Add pf files hierarchy Ratheesh Kannoth
2026-07-02 4:50 ` [PATCH v2 net-next 4/9] octeontx2-af: switch: Representor for switch port Ratheesh Kannoth
2026-07-02 4:50 ` [PATCH v2 net-next 5/9] octeontx2-af: PAN switch TL1 scheduling and NPC channel control Ratheesh Kannoth
2026-07-09 9:56 ` Paolo Abeni
2026-07-02 4:50 ` [PATCH v2 net-next 6/9] octeontx2-pf: register switch notifiers for eswitch offload Ratheesh Kannoth
2026-07-09 9:56 ` Paolo Abeni
2026-07-02 4:50 ` [PATCH v2 net-next 7/9] octeontx2: plumb bridge FDB updates through AF and switchdev Ratheesh Kannoth
2026-07-09 9:56 ` Paolo Abeni
2026-07-02 4:50 ` [PATCH v2 net-next 8/9] octeontx2: offload host FIB updates to switch via AF mailbox Ratheesh Kannoth
2026-07-09 9:56 ` Paolo Abeni
2026-07-02 4:50 ` [PATCH v2 net-next 9/9] octeontx2: add TC flow offload path for switch flows Ratheesh Kannoth
2026-07-09 9:56 ` Paolo Abeni
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox