AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/07/14 05:54 flow patch-triage
2m Args:
null

Results:
{
  "EnableConfigs": [
    "OCTEONTX_SWITCH",
    "OCTEONTX2_PF",
    "OCTEONTX2_AF"
  ],
  "FocusSymbols": [
    "rvu_mbox_handler_iface_get_info",
    "rvu_mbox_handler_npc_flow_del_n_free",
    "rvu_mbox_handler_npc_mcam_mul_stats",
    "rvu_mbox_handler_npc_mcam_get_features",
    "rvu_mbox_handler_swdev2af_notify",
    "rvu_mbox_handler_fl_get_stats",
    "rvu_mbox_handler_fl_notify",
    "rvu_mbox_handler_fdb_notify",
    "rvu_mbox_handler_fib_notify",
    "otx2vf_mbox_af2pf_fdb_refresh",
    "otx2_mbox_up_handler_af2pf_fdb_refresh",
    "sw_nb_fdb_event",
    "sw_nb_fib_event",
    "sw_nb_net_event",
    "sw_nb_netdev_event",
    "sw_fl_setup_ft_block_ingress_cb",
    "sw_nb_v4_fib_event",
    "sw_nb_net_v4_neigh_update",
    "sw_nb_v4_inetaddr_event",
    "sw_nb_v4_netdev_event",
    "sw_nb_v6_fib_event",
    "sw_nb_net_v6_neigh_update",
    "sw_nb_v6_inetaddr_event",
    "sw_nb_v6_netdev_event"
  ],
  "Reasoning": "The patch adds support for Marvell OcteonTX2 switch driver, introducing new mailbox messages, switchdev offloading, FDB, FIB, and flow offloading. This is a functional change that adds new reachable code paths.",
  "WorthFuzzing": true
}

1/1 2026/07/14 05:54 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit d7ce965bece40aa4bbe94f1b4b8aeb38abe2e44b\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Tue Jul 14 05:54:25 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/drivers/net/ethernet/marvell/octeontx2/Kconfig b/drivers/net/ethernet/marvell/octeontx2/Kconfig\nindex 47e549c581f0d2..e2fb6dd71078b2 100644\n--- a/drivers/net/ethernet/marvell/octeontx2/Kconfig\n+++ b/drivers/net/ethernet/marvell/octeontx2/Kconfig\n@@ -28,6 +28,16 @@ config NDC_DIS_DYNAMIC_CACHING\n \t  , NPA stack pages etc in NDC. Also locks down NIX SQ/CQ/RQ/RSS and\n \t  NPA Aura/Pool contexts.\n \n+config OCTEONTX_SWITCH\n+\tbool \"Marvell OcteonTX2 switch driver\"\n+\tdepends on (64BIT \u0026\u0026 COMPILE_TEST) || ARM64\n+\tdepends on OCTEONTX2_PF\n+\tdefault n\n+\thelp\n+\t  This driver supports Marvell's OcteonTX2 switch.\n+\t  Marvell SWITCH HW can offload L2, L3 flow. ARM core interacts\n+\t  with Marvell SW HW thru mbox.\n+\n config OCTEONTX2_PF\n \ttristate \"Marvell OcteonTX2 NIC Physical Function driver\"\n \tselect OCTEONTX2_MBOX\ndiff --git a/drivers/net/ethernet/marvell/octeontx2/af/Makefile b/drivers/net/ethernet/marvell/octeontx2/af/Makefile\nindex 91b7d6e96a6148..73f20a44f1a0fa 100644\n--- a/drivers/net/ethernet/marvell/octeontx2/af/Makefile\n+++ b/drivers/net/ethernet/marvell/octeontx2/af/Makefile\n@@ -3,7 +3,7 @@\n # Makefile for Marvell's RVU Admin Function driver\n #\n \n-ccflags-y += -I$(src)\n+ccflags-y += -I$(src) -I$(src)/switch/\n obj-$(CONFIG_OCTEONTX2_MBOX) += rvu_mbox.o\n obj-$(CONFIG_OCTEONTX2_AF) += rvu_af.o\n \n@@ -12,5 +12,6 @@ rvu_af-y := cgx.o rvu.o rvu_cgx.o rvu_npa.o rvu_nix.o \\\n \t\t  rvu_reg.o rvu_npc.o rvu_debugfs.o ptp.o rvu_npc_fs.o \\\n \t\t  rvu_cpt.o rvu_devlink.o rpm.o rvu_cn10k.o rvu_switch.o \\\n \t\t  rvu_sdp.o rvu_npc_hash.o mcs.o mcs_rvu_if.o mcs_cnf10kb.o \\\n+\t\t  switch/rvu_sw.o switch/rvu_sw_l2.o switch/rvu_sw_l3.o switch/rvu_sw_fl.o \\\n \t\t  rvu_rep.o cn20k/mbox_init.o cn20k/nix.o cn20k/debugfs.o \\\n \t\t  cn20k/npa.o cn20k/npc.o\ndiff --git a/drivers/net/ethernet/marvell/octeontx2/af/mbox.h b/drivers/net/ethernet/marvell/octeontx2/af/mbox.h\nindex f87cdf1b971db1..e8cc7b68ad75e2 100644\n--- a/drivers/net/ethernet/marvell/octeontx2/af/mbox.h\n+++ b/drivers/net/ethernet/marvell/octeontx2/af/mbox.h\n@@ -164,6 +164,18 @@ M(PTP_GET_CAP,\t\t0x00c, ptp_get_cap, msg_req, ptp_get_cap_rsp)\t\\\n M(GET_REP_CNT,\t\t0x00d, get_rep_cnt, msg_req, get_rep_cnt_rsp)\t\\\n M(ESW_CFG,\t\t0x00e, esw_cfg, esw_cfg_req, msg_rsp)\t\\\n M(REP_EVENT_NOTIFY,     0x00f, rep_event_notify, rep_event, msg_rsp) \\\n+M(FDB_NOTIFY,\t\t0x010,  fdb_notify,\t\t\t\t\\\n+\t\t\t\tfdb_notify_req, msg_rsp)\t\t\\\n+M(FIB_NOTIFY,\t\t0x011,  fib_notify,\t\t\t\t\\\n+\t\t\t\tfib_notify_req, msg_rsp)\t\t\\\n+M(FL_NOTIFY,\t\t0x012,  fl_notify,\t\t\t\t\\\n+\t\t\t\tfl_notify_req, msg_rsp)\t\t\\\n+M(FL_GET_STATS,\t\t0x013,  fl_get_stats,\t\t\t\t\\\n+\t\t\t\tfl_get_stats_req, fl_get_stats_rsp)\t\\\n+M(IFACE_GET_INFO,\t0x014, iface_get_info, msg_req,\t\\\n+\t\t\t\tiface_get_info_rsp)\t\t\t\\\n+M(SWDEV2AF_NOTIFY,\t0x015,  swdev2af_notify,\t\t\\\n+\t\t\t\tswdev2af_notify_req, msg_rsp)\t\t\\\n /* CGX mbox IDs (range 0x200 - 0x3FF) */\t\t\t\t\\\n M(CGX_START_RXTX,\t0x200, cgx_start_rxtx, msg_req, msg_rsp)\t\\\n M(CGX_STOP_RXTX,\t0x201, cgx_stop_rxtx, msg_req, msg_rsp)\t\t\\\n@@ -309,6 +321,14 @@ M(NPC_MCAM_GET_DFT_RL_IDXS, 0x601e, npc_get_dft_rl_idxs,\t\\\n M(NPC_MCAM_GET_NPC_PFL_INFO, 0x601f, npc_get_pfl_info,\t\t\\\n \t\t\t\t\tmsg_req,\t\t\\\n \t\t\t\t\tnpc_get_pfl_info_rsp)\t\\\n+M(NPC_MCAM_FLOW_DEL_N_FREE,\t0x6020, npc_flow_del_n_free,\t\t\\\n+\t\t\t\t npc_flow_del_n_free_req, msg_rsp)\t\\\n+M(NPC_MCAM_GET_MUL_STATS, 0x6021, npc_mcam_mul_stats,\t\t\t\\\n+\t\t\t\t   npc_mcam_get_mul_stats_req,\t\t\\\n+\t\t\t\t   npc_mcam_get_mul_stats_rsp)\t\t\\\n+M(NPC_MCAM_GET_FEATURES, 0x6022, npc_mcam_get_features,\t\t\t\\\n+\t\t\t\t   msg_req,\t\t\t\t\\\n+\t\t\t\t   npc_mcam_get_features_rsp)\t\t\\\n /* NIX mbox IDs (range 0x8000 - 0xFFFF) */\t\t\t\t\\\n M(NIX_LF_ALLOC,\t\t0x8000, nix_lf_alloc,\t\t\t\t\\\n \t\t\t\t nix_lf_alloc_req, nix_lf_alloc_rsp)\t\\\n@@ -438,6 +458,12 @@ M(MCS_INTR_NOTIFY,\t0xE00, mcs_intr_notify, mcs_intr_info, msg_rsp)\n #define MBOX_UP_REP_MESSAGES\t\t\t\t\t\t\\\n M(REP_EVENT_UP_NOTIFY,\t0xEF0, rep_event_up_notify, rep_event, msg_rsp) \\\n \n+#define MBOX_UP_AF2SWDEV_MESSAGES\t\t\t\t\t\\\n+M(AF2SWDEV,\t0xEF1, af2swdev_notify, af2swdev_notify_req, msg_rsp)\n+\n+#define MBOX_UP_AF2PF_FDB_REFRESH_MESSAGES\t\t\t\t\t\\\n+M(AF2PF_FDB_REFRESH,  0xEF2, af2pf_fdb_refresh, af2pf_fdb_refresh_req, msg_rsp)\n+\n enum {\n #define M(_name, _id, _1, _2, _3) MBOX_MSG_ ## _name = _id,\n MBOX_MESSAGES\n@@ -445,6 +471,8 @@ MBOX_UP_CGX_MESSAGES\n MBOX_UP_CPT_MESSAGES\n MBOX_UP_MCS_MESSAGES\n MBOX_UP_REP_MESSAGES\n+MBOX_UP_AF2SWDEV_MESSAGES\n+MBOX_UP_AF2PF_FDB_REFRESH_MESSAGES\n #undef M\n };\n \n@@ -1130,6 +1158,13 @@ struct nix_txsch_alloc_req {\n \t/* Scheduler queue count request at each level */\n \tu16 schq_contig[NIX_TXSCH_LVL_CNT]; /* No of contiguous queues */\n \tu16 schq[NIX_TXSCH_LVL_CNT]; /* No of non-contiguous queues */\n+\t/* Set only by the single switchdev PF (rvu-\u003erswitch.pcifunc). This is\n+\t * not the eswitch representor (rvu-\u003erep_pcifunc). That PF requests two\n+\t * aggregate-level TL2 queues on the PAN link, one for CGX and one for\n+\t * SDP steering. No other PF or VF sets this flag.\n+\t */\n+#define NIX_TXSCH_ALLOC_FLAG_PAN BIT_ULL(0)\n+\tu64 flags;\n };\n \n struct nix_txsch_alloc_rsp {\n@@ -1148,6 +1183,10 @@ struct nix_txsch_alloc_rsp {\n struct nix_txsch_free_req {\n \tstruct mbox_msghdr hdr;\n #define TXSCHQ_FREE_ALL BIT_ULL(0)\n+\t/* Frees PAN TL2 queues allocated with NIX_TXSCH_ALLOC_FLAG_PAN. Used\n+\t * only by the switchdev PF (rvu-\u003erswitch.pcifunc), not by other PFs/VFs.\n+\t */\n+#define TXSCHQ_FREE_PAN_TL1 BIT_ULL(1)\n \tu16 flags;\n \t/* Scheduler queue level to be freed */\n \tu16 schq_lvl;\n@@ -1581,6 +1620,30 @@ struct npc_mcam_alloc_entry_rsp {\n \tu16 entry_list[NPC_MAX_NONCONTIG_ENTRIES];\n };\n \n+struct npc_flow_del_n_free_req {\n+\tstruct mbox_msghdr hdr;\n+\tu16 cnt;\n+\tu16 entry[256]; /* Entry index to be freed */\n+};\n+\n+struct npc_mcam_get_features_rsp {\n+\tstruct mbox_msghdr hdr;\n+\tu64 rx_features;\n+\tu64 tx_features;\n+};\n+\n+struct npc_mcam_get_mul_stats_req {\n+\tstruct mbox_msghdr hdr;\n+\tu16 cnt;\n+\tu16 entry[256]; /* mcam entry */\n+};\n+\n+struct npc_mcam_get_mul_stats_rsp {\n+\tstruct mbox_msghdr hdr;\n+\tu16 cnt;\n+\tu64 stat[256];  /* counter stats */\n+};\n+\n struct npc_mcam_free_entry_req {\n \tstruct mbox_msghdr hdr;\n \tu16 entry; /* Entry index to be freed */\n@@ -1782,6 +1845,7 @@ struct get_rep_cnt_rsp {\n struct esw_cfg_req {\n \tstruct mbox_msghdr hdr;\n \tu8 ena;\n+\tunsigned char switch_id[MAX_PHYS_ITEM_ID_LEN];\n \tu64 rsvd;\n };\n \n@@ -1807,6 +1871,189 @@ struct rep_event {\n \tstruct rep_evt_data evt_data;\n };\n \n+#define FDB_ADD  BIT_ULL(0)\n+#define FDB_DEL\t BIT_ULL(1)\n+#define FIB_CMD\t BIT_ULL(2)\n+#define FL_ADD\t BIT_ULL(3)\n+#define FL_DEL\t BIT_ULL(4)\n+#define DP_ADD\t BIT_ULL(5)\n+\n+struct fdb_notify_req {\n+\tstruct  mbox_msghdr hdr;\n+\tu64 flags;\n+\tu8  mac[ETH_ALEN];\n+};\n+\n+struct fib_entry {\n+\tu64 cmd;\n+\tu64 gw_valid : 1;\n+\tu64 mac_valid : 1;\n+\tu64 vlan_valid: 1;\n+\tu64 host    : 1;\n+\tu64 bridge  : 1;\n+\tu64 ipv6  : 1;\n+\t__be16 vlan_tag;\n+\tu32 dst_len;\n+\tu8 dst6_plen;\n+\tu8 gw6_plen;\n+\tunion {\n+\t\t__be32 dst;\n+\t\t__be32 dst6[4];\n+\t};\n+\tunion {\n+\t\t__be32 gw;\n+\t\t__be32 gw6[4];\n+\t};\n+\tu16 port_id;\n+\tu8 nud_state;\n+\tu8 mac[ETH_ALEN];\n+};\n+\n+struct fib_notify_req {\n+\tstruct  mbox_msghdr hdr;\n+\tu16 cnt;\n+\tu16 rsvd[3]; /* explicit padding for entry[] 8-byte alignment */\n+\tstruct fib_entry entry[16];\n+};\n+\n+struct fl_tuple {\n+\t__be32 ip4src;\n+\t__be32 m_ip4src;\n+\t__be32 ip4dst;\n+\t__be32 m_ip4dst;\n+\t__be16 sport;\n+\t__be16 m_sport;\n+\t__be16 dport;\n+\t__be16 m_dport;\n+\t__be16 eth_type;\n+\t__be16 m_eth_type;\n+\tu8 proto;\n+\tu8 smac[6];\n+\tu8 m_smac[6];\n+\tu8 dmac[6];\n+\tu8 m_dmac[6];\n+\tu64 is_xdev_br : 1;\n+\tu64 is_indev_br : 1;\n+\tu64 uni_di  : 1;\n+\tu16 in_pf;\n+\tu16 xmit_pf;\n+\tu16 rsvd;\n+\tu64 features;\n+\tstruct {\t\t\t\t/* FLOW_ACTION_MANGLE */\n+\t\tu8\t\toffset;\n+\t\tu8\t\ttype;\n+\t\tu16\t\trsvd;\n+\t\tu32\t\tmask;\n+\t\tu32\t\tval;\n+#define MANGLE_ARR_SZ 9\n+\t} mangle[MANGLE_ARR_SZ]; /* 2 for ETH, 1 for VLAN, 4 for IPv6, 2 for L4. */\n+#define MANGLE_LAYER_CNT 4\n+\tu8 mangle_map[MANGLE_LAYER_CNT]; /* 1 for ETH,  1 for VLAN, 1 for L3, 1 for L4 */\n+\tu8 mangle_cnt;\n+};\n+\n+struct fl_notify_req {\n+\tstruct  mbox_msghdr hdr;\n+\tu64 cookie;\n+\tu64 flags;\n+\tu64 features;\n+\tstruct fl_tuple tuple;\n+};\n+\n+struct fl_get_stats_req {\n+\tstruct  mbox_msghdr hdr;\n+\tu64 cookie;\n+};\n+\n+struct fl_get_stats_rsp {\n+\tstruct  mbox_msghdr hdr;\n+\tu64 pkts_diff;\n+};\n+\n+struct af2swdev_notify_req {\n+\tstruct mbox_msghdr hdr;\n+\tu64 flags;\n+\tu32 port_id;\n+\tu32 switch_id;\n+\tunion {\n+\t\tstruct {\n+\t\t\tu8 mac[6];\n+\t\t};\n+\t\tstruct {\n+\t\t\tu8 cnt;\n+\t\t\tstruct fib_entry entry[16];\n+\t\t};\n+\n+\t\tstruct {\n+\t\t\tu64 cookie;\n+\t\t\tu64 features;\n+\t\t\tstruct fl_tuple tuple;\n+\t\t};\n+\t};\n+};\n+\n+struct af2pf_fdb_refresh_req {\n+\tstruct mbox_msghdr hdr;\n+\tu16 pcifunc;\n+\tu8 mac[6];\n+\tu64 flags;\n+};\n+\n+struct iface_info {\n+\tu8 is_vf : 1;\n+\tu8 is_sdp : 1;\n+\tu8 rsvd : 6;\n+\tu16 pcifunc;\n+\tu16 rx_chan_base;\n+\tu16 tx_chan_base;\n+\tu16 sq_cnt;\n+\tu16 cq_cnt;\n+\tu16 rq_cnt;\n+\tu8 rx_chan_cnt;\n+\tu8 tx_chan_cnt;\n+\tu8 tx_link;\n+\tu8 nix;\n+};\n+\n+/* Max supported */\n+#define IFACE_MAX (256 + 32) /* 32 PFs + 256 VFs */\n+\n+struct iface_get_info_rsp {\n+\tstruct  mbox_msghdr hdr;\n+\tu16 cnt;\n+\tu8 truncated;\n+\tu8 rsvd[5];\n+\tstruct iface_info info[IFACE_MAX];\n+};\n+\n+struct fl_info {\n+\tu64 cookie;\n+\tu16 mcam_idx[2];\n+\tu8 dis : 1;\n+\tu8 uni_di : 1;\n+};\n+\n+struct swdev2af_notify_req {\n+\tstruct  mbox_msghdr hdr;\n+\tu64 msg_type;\n+/* Mutually exclusive message selectors (not a combinable bitmask). */\n+#define SWDEV2AF_MSG_TYPE_FW_STATUS BIT_ULL(0)\n+#define\tSWDEV2AF_MSG_TYPE_REFRESH_FDB BIT_ULL(1)\n+#define\tSWDEV2AF_MSG_TYPE_REFRESH_FL BIT_ULL(2)\n+\tu16 pcifunc;\n+\tunion {\n+\t\tbool fw_up;\t\t// FW_STATUS message\n+\n+\t\tu8 mac[ETH_ALEN];\t// fdb refresh message\n+\n+\t\tstruct {\t\t// fl refresh message\n+\t\t\tu8 cnt;\n+\t\t\tu8 rsvd[7];\n+\t\t\tstruct fl_info fl[64];\n+\t\t};\n+\t};\n+};\n+\n struct flow_msg {\n \tunsigned char dmac[6];\n \tunsigned char smac[6];\n@@ -1881,6 +2128,10 @@ struct npc_install_flow_req {\n \tu8 hw_prio;\n \tu8  req_kw_type; /* Key type to be written */\n \tu8 alloc_entry;\t/* only for cn20k */\n+\t/* When set, rvu_mbox_handler_npc_install_flow() keeps caller chan_mask\n+\t * for switchdev-installed flows instead of the default CPT override.\n+\t */\n+\tu8 set_chanmask;\n /* For now use any priority, once AF driver is changed to\n  * allocate least priority entry instead of mid zone then make\n  * NPC_MCAM_LEAST_PRIO as 3\ndiff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu.c\nindex ffba56ee8a60cd..217ce85033ac43 100644\n--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu.c\n+++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu.c\n@@ -23,6 +23,7 @@\n #include \"cn20k/reg.h\"\n #include \"cn20k/api.h\"\n #include \"cn20k/npc.h\"\n+#include \"switch/rvu_sw.h\"\n \n #define DRV_NAME\t\"rvu_af\"\n #define DRV_STRING      \"Marvell OcteonTX2 RVU Admin Function Driver\"\n@@ -1990,6 +1991,153 @@ int rvu_mbox_handler_msix_offset(struct rvu *rvu, struct msg_req *req,\n \treturn 0;\n }\n \n+static bool rvu_iface_get_info_permitted(struct rvu *rvu, u16 pcifunc)\n+{\n+\tif (rvu-\u003erswitch.mode == DEVLINK_ESWITCH_MODE_SWITCHDEV)\n+\t\treturn false;\n+\n+\treturn true;\n+}\n+\n+int rvu_mbox_handler_iface_get_info(struct rvu *rvu, struct msg_req *req,\n+\t\t\t\t    struct iface_get_info_rsp *rsp)\n+{\n+\tstruct iface_info *info;\n+\tbool truncated = false;\n+\tstruct rvu_pfvf *pfvf;\n+\tint pf, vf, numvfs;\n+\tu16 pcifunc;\n+\tint tot = 0;\n+\tu64 cfg;\n+\n+\tif (!rvu_iface_get_info_permitted(rvu, req-\u003ehdr.pcifunc))\n+\t\treturn -EPERM;\n+\n+\tmemset(rsp, 0, sizeof(*rsp));\n+\tinfo = rsp-\u003einfo;\n+\tfor (pf = 0; pf \u003c rvu-\u003ehw-\u003etotal_pfs; pf++) {\n+\t\tif (tot \u003e= IFACE_MAX) {\n+\t\t\ttruncated = true;\n+\t\t\tgoto done;\n+\t\t}\n+\n+\t\tcfg = rvu_read64(rvu, BLKADDR_RVUM, RVU_PRIV_PFX_CFG(pf));\n+\t\tnumvfs = (cfg \u003e\u003e 12) \u0026 0xFF;\n+\n+\t\t/* Skip not enabled PFs */\n+\t\tif (!(cfg \u0026 BIT_ULL(20)))\n+\t\t\tgoto chk_vfs;\n+\n+\t\t/* If Admin function, check on VFs */\n+\t\tif (cfg \u0026 BIT_ULL(21))\n+\t\t\tgoto chk_vfs;\n+\n+\t\tpcifunc = rvu_make_pcifunc(rvu-\u003epdev, pf, 0);\n+\t\tpfvf = rvu_get_pfvf(rvu, pcifunc);\n+\n+\t\t/* Populate iff at least one Tx channel */\n+\t\tif (!pfvf-\u003etx_chan_cnt)\n+\t\t\tgoto chk_vfs;\n+\n+\t\tinfo-\u003eis_vf = 0;\n+\t\tinfo-\u003epcifunc = pcifunc;\n+\t\tinfo-\u003erx_chan_base = pfvf-\u003erx_chan_base;\n+\t\tinfo-\u003erx_chan_cnt = pfvf-\u003erx_chan_cnt;\n+\t\tinfo-\u003etx_chan_base = pfvf-\u003etx_chan_base;\n+\t\tinfo-\u003etx_chan_cnt = pfvf-\u003etx_chan_cnt;\n+\t\tinfo-\u003etx_link = nix_get_tx_link(rvu, pcifunc);\n+\t\tif (is_sdp_pfvf(rvu, pcifunc))\n+\t\t\tinfo-\u003eis_sdp = 1;\n+\n+\t\t/* If interfaces are not UP, there are no queues */\n+\t\tinfo-\u003esq_cnt = 0;\n+\t\tinfo-\u003ecq_cnt = 0;\n+\t\tinfo-\u003erq_cnt = 0;\n+\n+\t\tmutex_lock(\u0026rvu-\u003ersrc_lock);\n+\t\tif (pfvf-\u003esq_bmap) {\n+\t\t\tint sq_bmap_bits = rvu_is_switch_pcifunc(rvu, pcifunc) ?\n+\t\t\t\t\t   NIX_SQ_BMAP_BITS : pfvf-\u003esq_ctx-\u003eqsize;\n+\n+\t\t\tinfo-\u003esq_cnt = bitmap_weight(pfvf-\u003esq_bmap, sq_bmap_bits);\n+\t\t}\n+\n+\t\tif (pfvf-\u003ecq_bmap)\n+\t\t\tinfo-\u003ecq_cnt = bitmap_weight(pfvf-\u003ecq_bmap, BITS_PER_LONG);\n+\n+\t\tif (pfvf-\u003erq_bmap)\n+\t\t\tinfo-\u003erq_cnt = bitmap_weight(pfvf-\u003erq_bmap, BITS_PER_LONG);\n+\t\tmutex_unlock(\u0026rvu-\u003ersrc_lock);\n+\n+\t\tif (pfvf-\u003enix_blkaddr == BLKADDR_NIX0)\n+\t\t\tinfo-\u003enix = 0;\n+\t\telse\n+\t\t\tinfo-\u003enix = 1;\n+\n+\t\tinfo++;\n+\t\ttot++;\n+\n+chk_vfs:\n+\t\tfor (vf = 0; vf \u003c numvfs; vf++) {\n+\t\t\tif (tot \u003e= IFACE_MAX) {\n+\t\t\t\ttruncated = true;\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\n+\t\t\tpcifunc = rvu_make_pcifunc(rvu-\u003epdev, pf, vf + 1);\n+\t\t\tpfvf = rvu_get_pfvf(rvu, pcifunc);\n+\n+\t\t\tif (!pfvf-\u003etx_chan_cnt)\n+\t\t\t\tcontinue;\n+\n+\t\t\tinfo-\u003eis_vf = 1;\n+\t\t\tinfo-\u003epcifunc = pcifunc;\n+\t\t\tinfo-\u003erx_chan_base = pfvf-\u003erx_chan_base;\n+\t\t\tinfo-\u003erx_chan_cnt = pfvf-\u003erx_chan_cnt;\n+\t\t\tinfo-\u003etx_chan_base = pfvf-\u003etx_chan_base;\n+\t\t\tinfo-\u003etx_chan_cnt = pfvf-\u003etx_chan_cnt;\n+\t\t\tinfo-\u003etx_link = nix_get_tx_link(rvu, pcifunc);\n+\t\t\tif (is_sdp_pfvf(rvu, pcifunc))\n+\t\t\t\tinfo-\u003eis_sdp = 1;\n+\n+\t\t\t/* If interfaces are not UP, there are no queues */\n+\t\t\tinfo-\u003esq_cnt = 0;\n+\t\t\tinfo-\u003ecq_cnt = 0;\n+\t\t\tinfo-\u003erq_cnt = 0;\n+\n+\t\t\tmutex_lock(\u0026rvu-\u003ersrc_lock);\n+\t\t\tif (pfvf-\u003esq_bmap) {\n+\t\t\t\tint sq_bmap_bits = rvu_is_switch_pcifunc(rvu, pcifunc) ?\n+\t\t\t\t\t\t   NIX_SQ_BMAP_BITS : pfvf-\u003esq_ctx-\u003eqsize;\n+\n+\t\t\t\tinfo-\u003esq_cnt = bitmap_weight(pfvf-\u003esq_bmap, sq_bmap_bits);\n+\t\t\t}\n+\n+\t\t\tif (pfvf-\u003ecq_bmap)\n+\t\t\t\tinfo-\u003ecq_cnt = bitmap_weight(pfvf-\u003ecq_bmap, BITS_PER_LONG);\n+\n+\t\t\tif (pfvf-\u003erq_bmap)\n+\t\t\t\tinfo-\u003erq_cnt = bitmap_weight(pfvf-\u003erq_bmap, BITS_PER_LONG);\n+\n+\t\t\tmutex_unlock(\u0026rvu-\u003ersrc_lock);\n+\n+\t\t\tif (pfvf-\u003enix_blkaddr == BLKADDR_NIX0)\n+\t\t\t\tinfo-\u003enix = 0;\n+\t\t\telse\n+\t\t\t\tinfo-\u003enix = 1;\n+\n+\t\t\tinfo++;\n+\n+\t\t\ttot++;\n+\t\t}\n+\t}\n+done:\n+\trsp-\u003ecnt = tot;\n+\trsp-\u003etruncated = truncated;\n+\n+\treturn 0;\n+}\n+\n int rvu_mbox_handler_free_rsrc_cnt(struct rvu *rvu, struct msg_req *req,\n \t\t\t\t   struct free_rsrcs_rsp *rsp)\n {\n@@ -3712,6 +3860,7 @@ static void rvu_remove(struct pci_dev *pdev)\n \trvu_cgx_exit(rvu);\n \trvu_fwdata_exit(rvu);\n \trvu_mcs_exit(rvu);\n+\trvu_sw_shutdown();\n \trvu_mbox_destroy(\u0026rvu-\u003eafpf_wq_info);\n \trvu_disable_sriov(rvu);\n \trvu_reset_all_blocks(rvu);\ndiff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu.h b/drivers/net/ethernet/marvell/octeontx2/af/rvu.h\nindex c5610f24268795..0662cc6134b066 100644\n--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu.h\n+++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu.h\n@@ -335,6 +335,7 @@ struct nix_txsch {\n \tu8   lvl;\n #define NIX_TXSCHQ_FREE\t\t      BIT_ULL(1)\n #define NIX_TXSCHQ_CFG_DONE\t      BIT_ULL(0)\n+#define NIX_SQ_BMAP_BITS\t      (BITS_PER_LONG * 16)\n #define TXSCH_MAP_FUNC(__pfvf_map)    ((__pfvf_map) \u0026 0xFFFF)\n #define TXSCH_MAP_FLAGS(__pfvf_map)   ((__pfvf_map) \u003e\u003e 16)\n #define TXSCH_MAP(__func, __flags)    (((__func) \u0026 0xFFFF) | ((__flags) \u003c\u003c 16))\n@@ -576,6 +577,10 @@ struct rvu_switch {\n \tu16 *entry2pcifunc;\n \tu16 mode;\n \tu16 start_entry;\n+\tunsigned char switch_id[MAX_PHYS_ITEM_ID_LEN];\n+#define RVU_SWITCH_FLAG_FW_READY BIT_ULL(0)\n+\tu64 flags;\n+\tu16 pcifunc;\n };\n \n struct rep_evtq_ent {\n@@ -900,6 +905,11 @@ static inline bool is_pffunc_af(u16 pcifunc)\n \treturn !pcifunc;\n }\n \n+static inline bool rvu_is_switch_pcifunc(struct rvu *rvu, u16 pcifunc)\n+{\n+\treturn rvu-\u003erswitch.pcifunc \u0026\u0026 pcifunc == rvu-\u003erswitch.pcifunc;\n+}\n+\n static inline bool is_rvu_fwdata_valid(struct rvu *rvu)\n {\n \treturn (rvu-\u003efwdata-\u003eheader_magic == RVU_FWDATA_HEADER_MAGIC) \u0026\u0026\n@@ -1158,6 +1168,7 @@ void rvu_program_channels(struct rvu *rvu);\n \n /* CN10K NIX */\n void rvu_nix_block_cn10k_init(struct rvu *rvu, struct nix_hw *nix_hw);\n+int nix_get_tx_link(struct rvu *rvu, u16 pcifunc);\n \n /* CN10K RVU - LMT*/\n void rvu_reset_lmt_map_tbl(struct rvu *rvu, u16 pcifunc);\n@@ -1196,4 +1207,5 @@ int rvu_rep_install_mcam_rules(struct rvu *rvu);\n void rvu_rep_update_rules(struct rvu *rvu, u16 pcifunc, bool ena);\n int rvu_rep_notify_pfvf_state(struct rvu *rvu, u16 pcifunc, bool enable);\n int npc_mcam_verify_entry(struct npc_mcam *mcam, u16 pcifunc, int entry);\n+u16 rvu_rep_get_vlan_id(struct rvu *rvu, u16 pcifunc);\n #endif /* RVU_H */\ndiff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c\nindex 78667a0977c013..1a0ba148478e52 100644\n--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c\n+++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c\n@@ -32,7 +32,6 @@ static int nix_free_all_bandprof(struct rvu *rvu, u16 pcifunc);\n static void nix_clear_ratelimit_aggr(struct rvu *rvu, struct nix_hw *nix_hw,\n \t\t\t\t     u32 leaf_prof);\n static const char *nix_get_ctx_name(int ctype);\n-static int nix_get_tx_link(struct rvu *rvu, u16 pcifunc);\n \n enum mc_tbl_sz {\n \tMC_TBL_SZ_256,\n@@ -906,6 +905,8 @@ static void nix_setup_lso(struct rvu *rvu, struct nix_hw *nix_hw, int blkaddr)\n \n static void nix_ctx_free(struct rvu *rvu, struct rvu_pfvf *pfvf)\n {\n+\tmutex_lock(\u0026rvu-\u003ersrc_lock);\n+\n \tkfree(pfvf-\u003erq_bmap);\n \tkfree(pfvf-\u003esq_bmap);\n \tkfree(pfvf-\u003ecq_bmap);\n@@ -931,6 +932,8 @@ static void nix_ctx_free(struct rvu *rvu, struct rvu_pfvf *pfvf)\n \tpfvf-\u003erss_ctx = NULL;\n \tpfvf-\u003enix_qints_ctx = NULL;\n \tpfvf-\u003ecq_ints_ctx = NULL;\n+\n+\tmutex_unlock(\u0026rvu-\u003ersrc_lock);\n }\n \n static int nixlf_rss_ctx_init(struct rvu *rvu, int blkaddr,\n@@ -1597,7 +1600,11 @@ int rvu_mbox_handler_nix_lf_alloc(struct rvu *rvu,\n \tif (rc)\n \t\tgoto free_mem;\n \n-\tpfvf-\u003esq_bmap = kcalloc(req-\u003esq_cnt, sizeof(long), GFP_KERNEL);\n+\tif (rvu_is_switch_pcifunc(rvu, pcifunc))\n+\t\tpfvf-\u003esq_bmap = kcalloc(BITS_TO_LONGS(NIX_SQ_BMAP_BITS),\n+\t\t\t\t\tsizeof(long), GFP_KERNEL);\n+\telse\n+\t\tpfvf-\u003esq_bmap = kcalloc(req-\u003esq_cnt, sizeof(long), GFP_KERNEL);\n \tif (!pfvf-\u003esq_bmap) {\n \t\trc = -ENOMEM;\n \t\tgoto free_mem;\n@@ -2087,7 +2094,7 @@ static void nix_clear_tx_xoff(struct rvu *rvu, int blkaddr,\n \trvu_write64(rvu, blkaddr, reg, 0x0);\n }\n \n-static int nix_get_tx_link(struct rvu *rvu, u16 pcifunc)\n+int nix_get_tx_link(struct rvu *rvu, u16 pcifunc)\n {\n \tstruct rvu_hwinfo *hw = rvu-\u003ehw;\n \tint pf = rvu_get_pf(rvu-\u003epdev, pcifunc);\n@@ -2124,6 +2131,25 @@ static void nix_get_txschq_range(struct rvu *rvu, u16 pcifunc,\n \t}\n }\n \n+static int nix_get_pan_tx_link(struct rvu *rvu)\n+{\n+\tstruct rvu_hwinfo *hw = rvu-\u003ehw;\n+\n+\treturn hw-\u003ecgx_links + hw-\u003elbk_links + 1;\n+}\n+\n+static bool nix_txsch_is_pan_schq(struct rvu *rvu, int schq)\n+{\n+\tint pan_link = nix_get_pan_tx_link(rvu);\n+\n+\treturn schq \u003e= pan_link \u0026\u0026 schq \u003c= pan_link + 1;\n+}\n+\n+static bool nix_txsch_pan_allowed(struct rvu *rvu, u16 pcifunc)\n+{\n+\treturn rvu_is_switch_pcifunc(rvu, pcifunc);\n+}\n+\n static int nix_check_txschq_alloc_req(struct rvu *rvu, int lvl, u16 pcifunc,\n \t\t\t\t      struct nix_hw *nix_hw,\n \t\t\t\t      struct nix_txsch_alloc_req *req)\n@@ -2139,12 +2165,27 @@ static int nix_check_txschq_alloc_req(struct rvu *rvu, int lvl, u16 pcifunc,\n \tif (!req_schq)\n \t\treturn 0;\n \n-\tlink = nix_get_tx_link(rvu, pcifunc);\n+\tif (req-\u003eflags \u0026 NIX_TXSCH_ALLOC_FLAG_PAN) {\n+\t\tif (!nix_txsch_pan_allowed(rvu, pcifunc))\n+\t\t\treturn NIX_AF_ERR_TLX_ALLOC_FAIL;\n+\t\tlink = nix_get_pan_tx_link(rvu);\n+\t} else {\n+\t\tlink = nix_get_tx_link(rvu, pcifunc);\n+\t}\n \n \t/* For traffic aggregating scheduler level, one queue is enough */\n \tif (lvl \u003e= hw-\u003ecap.nix_tx_aggr_lvl) {\n-\t\tif (req_schq != 1)\n+\t\tif (req_schq != 1 \u0026\u0026 !(req-\u003eflags \u0026 NIX_TXSCH_ALLOC_FLAG_PAN))\n \t\t\treturn NIX_AF_ERR_TLX_ALLOC_FAIL;\n+\t\tif (req-\u003eschq[lvl] \u003e MAX_TXSCHQ_PER_FUNC ||\n+\t\t    req-\u003eschq_contig[lvl] \u003e MAX_TXSCHQ_PER_FUNC)\n+\t\t\treturn NIX_AF_ERR_TLX_ALLOC_FAIL;\n+\t\tif (req-\u003eflags \u0026 NIX_TXSCH_ALLOC_FLAG_PAN) {\n+\t\t\tif (link \u003e= txsch-\u003eschq.max || link + 1 \u003e= txsch-\u003eschq.max)\n+\t\t\t\treturn NIX_AF_ERR_TLX_ALLOC_FAIL;\n+\t\t\tif (req_schq \u003e 2)\n+\t\t\t\treturn NIX_AF_ERR_TLX_ALLOC_FAIL;\n+\t\t}\n \t\treturn 0;\n \t}\n \n@@ -2173,9 +2214,9 @@ static int nix_check_txschq_alloc_req(struct rvu *rvu, int lvl, u16 pcifunc,\n \treturn 0;\n }\n \n-static void nix_txsch_alloc(struct rvu *rvu, struct nix_txsch *txsch,\n-\t\t\t    struct nix_txsch_alloc_rsp *rsp,\n-\t\t\t    int lvl, int start, int end)\n+static int nix_txsch_alloc(struct rvu *rvu, struct nix_txsch *txsch,\n+\t\t\t   struct nix_txsch_alloc_rsp *rsp,\n+\t\t\t   int lvl, int start, int end)\n {\n \tstruct rvu_hwinfo *hw = rvu-\u003ehw;\n \tu16 pcifunc = rsp-\u003ehdr.pcifunc;\n@@ -2185,6 +2226,46 @@ static void nix_txsch_alloc(struct rvu *rvu, struct nix_txsch *txsch,\n \t * on transmit link to which PF_FUNC is mapped to.\n \t */\n \tif (lvl \u003e= hw-\u003ecap.nix_tx_aggr_lvl) {\n+\t\tif (start != end) {\n+\t\t\tint want_contig = rsp-\u003eschq_contig[lvl];\n+\t\t\tint got_contig = 0, got = 0;\n+\t\t\tint want = rsp-\u003eschq[lvl];\n+\n+\t\t\tfor (schq = start; schq \u003c= end; schq++) {\n+\t\t\t\tif (test_bit(schq, txsch-\u003eschq.bmap))\n+\t\t\t\t\tcontinue;\n+\n+\t\t\t\tif (got_contig \u003c want_contig) {\n+\t\t\t\t\tset_bit(schq, txsch-\u003eschq.bmap);\n+\t\t\t\t\trsp-\u003eschq_contig_list[lvl][got_contig++] = schq;\n+\t\t\t\t\tcontinue;\n+\t\t\t\t}\n+\n+\t\t\t\tif (got \u003c want) {\n+\t\t\t\t\tset_bit(schq, txsch-\u003eschq.bmap);\n+\t\t\t\t\trsp-\u003eschq_list[lvl][got++] = schq;\n+\t\t\t\t}\n+\t\t\t}\n+\n+\t\t\trsp-\u003eschq_contig[lvl] = got_contig;\n+\t\t\trsp-\u003eschq[lvl] = got;\n+\n+\t\t\tif (got_contig \u003c want_contig || got \u003c want) {\n+\t\t\t\tfor (idx = 0; idx \u003c got_contig; idx++)\n+\t\t\t\t\tclear_bit(rsp-\u003eschq_contig_list[lvl][idx],\n+\t\t\t\t\t\t  txsch-\u003eschq.bmap);\n+\t\t\t\tfor (idx = 0; idx \u003c got; idx++)\n+\t\t\t\t\tclear_bit(rsp-\u003eschq_list[lvl][idx],\n+\t\t\t\t\t\t  txsch-\u003eschq.bmap);\n+\t\t\t\trsp-\u003eschq_contig[lvl] = 0;\n+\t\t\t\trsp-\u003eschq[lvl] = 0;\n+\t\t\t\tdev_err(rvu-\u003edev,\n+\t\t\t\t\t\"Could not allocate schq at lvl=%u start=%u end=%u\\n\",\n+\t\t\t\t\tlvl, start, end);\n+\t\t\t\treturn -ENOMEM;\n+\t\t\t}\n+\t\t\treturn 0;\n+\t\t}\n \t\t/* A single TL queue is allocated */\n \t\tif (rsp-\u003eschq_contig[lvl]) {\n \t\t\trsp-\u003eschq_contig[lvl] = 1;\n@@ -2199,7 +2280,7 @@ static void nix_txsch_alloc(struct rvu *rvu, struct nix_txsch *txsch,\n \t\t\trsp-\u003eschq[lvl] = 1;\n \t\t\trsp-\u003eschq_list[lvl][0] = start;\n \t\t}\n-\t\treturn;\n+\t\treturn 0;\n \t}\n \n \t/* Adjust the queue request count if HW supports\n@@ -2211,7 +2292,7 @@ static void nix_txsch_alloc(struct rvu *rvu, struct nix_txsch *txsch,\n \t\tif (idx \u003e= (end - start) || test_bit(schq, txsch-\u003eschq.bmap)) {\n \t\t\trsp-\u003eschq_contig[lvl] = 0;\n \t\t\trsp-\u003eschq[lvl] = 0;\n-\t\t\treturn;\n+\t\t\treturn 0;\n \t\t}\n \n \t\tif (rsp-\u003eschq_contig[lvl]) {\n@@ -2224,7 +2305,7 @@ static void nix_txsch_alloc(struct rvu *rvu, struct nix_txsch *txsch,\n \t\t\tset_bit(schq, txsch-\u003eschq.bmap);\n \t\t\trsp-\u003eschq_list[lvl][0] = schq;\n \t\t}\n-\t\treturn;\n+\t\treturn 0;\n \t}\n \n \t/* Allocate contiguous queue indices requesty first */\n@@ -2255,6 +2336,8 @@ static void nix_txsch_alloc(struct rvu *rvu, struct nix_txsch *txsch,\n \t\t/* Update how many were allocated */\n \t\trsp-\u003eschq[lvl] = idx;\n \t}\n+\n+\treturn 0;\n }\n \n int rvu_mbox_handler_nix_txsch_alloc(struct rvu *rvu,\n@@ -2279,6 +2362,10 @@ int rvu_mbox_handler_nix_txsch_alloc(struct rvu *rvu,\n \tif (!nix_hw)\n \t\treturn NIX_AF_ERR_INVALID_NIXBLK;\n \n+\tif ((req-\u003eflags \u0026 NIX_TXSCH_ALLOC_FLAG_PAN) \u0026\u0026\n+\t    !nix_txsch_pan_allowed(rvu, pcifunc))\n+\t\treturn NIX_AF_ERR_TLX_ALLOC_FAIL;\n+\n \tmutex_lock(\u0026rvu-\u003ersrc_lock);\n \n \t/* Check if request is valid as per HW capabilities\n@@ -2301,11 +2388,14 @@ int rvu_mbox_handler_nix_txsch_alloc(struct rvu *rvu,\n \t\trsp-\u003eschq[lvl] = req-\u003eschq[lvl];\n \t\trsp-\u003eschq_contig[lvl] = req-\u003eschq_contig[lvl];\n \n-\t\tlink = nix_get_tx_link(rvu, pcifunc);\n+\t\tif (req-\u003eflags \u0026 NIX_TXSCH_ALLOC_FLAG_PAN)\n+\t\t\tlink = nix_get_pan_tx_link(rvu);\n+\t\telse\n+\t\t\tlink = nix_get_tx_link(rvu, pcifunc);\n \n \t\tif (lvl \u003e= hw-\u003ecap.nix_tx_aggr_lvl) {\n \t\t\tstart = link;\n-\t\t\tend = link;\n+\t\t\tend = link + !!(req-\u003eflags \u0026 NIX_TXSCH_ALLOC_FLAG_PAN);\n \t\t} else if (hw-\u003ecap.nix_fixed_txschq_mapping) {\n \t\t\tnix_get_txschq_range(rvu, pcifunc, link, \u0026start, \u0026end);\n \t\t} else {\n@@ -2313,10 +2403,11 @@ int rvu_mbox_handler_nix_txsch_alloc(struct rvu *rvu,\n \t\t\tend = txsch-\u003eschq.max;\n \t\t}\n \n-\t\tnix_txsch_alloc(rvu, txsch, rsp, lvl, start, end);\n+\t\tif (nix_txsch_alloc(rvu, txsch, rsp, lvl, start, end))\n+\t\t\tgoto err;\n \n \t\t/* Reset queue config */\n-\t\tfor (idx = 0; idx \u003c req-\u003eschq_contig[lvl]; idx++) {\n+\t\tfor (idx = 0; idx \u003c rsp-\u003eschq_contig[lvl]; idx++) {\n \t\t\tschq = rsp-\u003eschq_contig_list[lvl][idx];\n \t\t\tif (!(TXSCH_MAP_FLAGS(pfvf_map[schq]) \u0026\n \t\t\t    NIX_TXSCHQ_CFG_DONE))\n@@ -2326,7 +2417,7 @@ int rvu_mbox_handler_nix_txsch_alloc(struct rvu *rvu,\n \t\t\tnix_reset_tx_schedule(rvu, blkaddr, lvl, schq);\n \t\t}\n \n-\t\tfor (idx = 0; idx \u003c req-\u003eschq[lvl]; idx++) {\n+\t\tfor (idx = 0; idx \u003c rsp-\u003eschq[lvl]; idx++) {\n \t\t\tschq = rsp-\u003eschq_list[lvl][idx];\n \t\t\tif (!(TXSCH_MAP_FLAGS(pfvf_map[schq]) \u0026\n \t\t\t    NIX_TXSCHQ_CFG_DONE))\n@@ -2622,11 +2713,11 @@ static int nix_txschq_free(struct rvu *rvu, u16 pcifunc)\n \t\t /* TLs above aggregation level are shared across all PF\n \t\t  * and it's VFs, hence skip freeing them.\n \t\t  */\n-\t\tif (lvl \u003e= hw-\u003ecap.nix_tx_aggr_lvl)\n-\t\t\tcontinue;\n-\n \t\ttxsch = \u0026nix_hw-\u003etxsch[lvl];\n \t\tfor (schq = 0; schq \u003c txsch-\u003eschq.max; schq++) {\n+\t\t\tif (lvl \u003e= hw-\u003ecap.nix_tx_aggr_lvl \u0026\u0026\n+\t\t\t    !nix_txsch_is_pan_schq(rvu, schq))\n+\t\t\t\tcontinue;\n \t\t\tif (TXSCH_MAP_FUNC(txsch-\u003epfvf_map[schq]) != pcifunc)\n \t\t\t\tcontinue;\n \t\t\tnix_reset_tx_schedule(rvu, blkaddr, lvl, schq);\n@@ -2670,7 +2761,16 @@ static int nix_txschq_free_one(struct rvu *rvu,\n \tschq = req-\u003eschq;\n \ttxsch = \u0026nix_hw-\u003etxsch[lvl];\n \n-\tif (lvl \u003e= hw-\u003ecap.nix_tx_aggr_lvl || schq \u003e= txsch-\u003eschq.max)\n+\tif (req-\u003eflags \u0026 TXSCHQ_FREE_PAN_TL1) {\n+\t\tif (!nix_txsch_pan_allowed(rvu, pcifunc))\n+\t\t\treturn NIX_AF_ERR_TLX_INVALID;\n+\t\tif (!nix_txsch_is_pan_schq(rvu, schq))\n+\t\t\treturn NIX_AF_ERR_TLX_INVALID;\n+\t} else if (lvl \u003e= hw-\u003ecap.nix_tx_aggr_lvl) {\n+\t\treturn 0;\n+\t}\n+\n+\tif (schq \u003e= txsch-\u003eschq.max)\n \t\treturn 0;\n \n \tpfvf_map = txsch-\u003epfvf_map;\ndiff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c\nindex 08b83de9beb463..b17da72250a367 100644\n--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c\n+++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c\n@@ -3544,6 +3544,40 @@ int rvu_mbox_handler_npc_mcam_free_entry(struct rvu *rvu,\n \treturn rc;\n }\n \n+int rvu_mbox_handler_npc_flow_del_n_free(struct rvu *rvu,\n+\t\t\t\t\t struct npc_flow_del_n_free_req *mreq,\n+\t\t\t\t\t struct msg_rsp *rsp)\n+{\n+\tstruct npc_mcam_free_entry_req sreq = { 0 };\n+\tstruct npc_delete_flow_req dreq = { 0 };\n+\tstruct npc_delete_flow_rsp drsp = { 0 };\n+\tbool err = false;\n+\tint ret = 0, i;\n+\n+\tsreq.hdr.pcifunc = mreq-\u003ehdr.pcifunc;\n+\tdreq.hdr.pcifunc = mreq-\u003ehdr.pcifunc;\n+\n+\tif (!mreq-\u003ecnt || mreq-\u003ecnt \u003e 256) {\n+\t\tdev_err(rvu-\u003edev, \"Invalid cnt=%u\\n\", mreq-\u003ecnt);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tfor (i = 0; i \u003c mreq-\u003ecnt; i++) {\n+\t\tdreq.entry = mreq-\u003eentry[i];\n+\t\trvu_mbox_handler_npc_delete_flow(rvu, \u0026dreq, \u0026drsp);\n+\n+\t\tsreq.entry = mreq-\u003eentry[i];\n+\t\tret = rvu_mbox_handler_npc_mcam_free_entry(rvu, \u0026sreq, rsp);\n+\t\tif (ret) {\n+\t\t\tdev_err(rvu-\u003edev, \"free entry error for i=%d entry=%d\\n\",\n+\t\t\t\ti, mreq-\u003eentry[i]);\n+\t\t\terr = true;\n+\t\t}\n+\t}\n+\n+\treturn err ? -EINVAL : 0;\n+}\n+\n int rvu_mbox_handler_npc_mcam_read_entry(struct rvu *rvu,\n \t\t\t\t\t struct npc_mcam_read_entry_req *req,\n \t\t\t\t\t struct npc_mcam_read_entry_rsp *rsp)\n@@ -4398,6 +4432,72 @@ int rvu_mbox_handler_npc_mcam_entry_stats(struct rvu *rvu,\n \treturn 0;\n }\n \n+int rvu_mbox_handler_npc_mcam_mul_stats(struct rvu *rvu,\n+\t\t\t\t\tstruct npc_mcam_get_mul_stats_req *req,\n+\t\t\t\t\tstruct npc_mcam_get_mul_stats_rsp *rsp)\n+{\n+\tstruct npc_mcam *mcam = \u0026rvu-\u003ehw-\u003emcam;\n+\tu16 pcifunc = req-\u003ehdr.pcifunc;\n+\tint blkaddr, cnt = 0, i;\n+\tu16 index, cntr, entry;\n+\tu64 regval;\n+\tu32 bank;\n+\n+\tif (!req-\u003ecnt || req-\u003ecnt \u003e 256) {\n+\t\tdev_err(rvu-\u003edev, \"%s invalid request cnt=%u\\n\",\n+\t\t\t__func__, req-\u003ecnt);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tblkaddr = rvu_get_blkaddr(rvu, BLKTYPE_NPC, 0);\n+\tif (blkaddr \u003c 0)\n+\t\treturn NPC_MCAM_INVALID_REQ;\n+\n+\tmutex_lock(\u0026mcam-\u003elock);\n+\n+\tfor (i = 0; i \u003c req-\u003ecnt; i++) {\n+\t\tentry = npc_cn20k_vidx2idx(req-\u003eentry[i]);\n+\n+\t\tif (npc_mcam_verify_entry(mcam, pcifunc, entry)) {\n+\t\t\tmutex_unlock(\u0026mcam-\u003elock);\n+\t\t\tdev_err(rvu-\u003edev, \"%s invalid mcam index=%d\\n\",\n+\t\t\t\t__func__, req-\u003eentry[i]);\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\n+\t\tindex = entry \u0026 (mcam-\u003ebanksize - 1);\n+\t\tbank = npc_get_bank(mcam, entry);\n+\n+\t\tif (is_cn20k(rvu-\u003epdev)) {\n+\t\t\tregval = rvu_read64(rvu, blkaddr,\n+\t\t\t\t\t    NPC_AF_CN20K_MCAMEX_BANKX_STAT_EXT(index,\n+\t\t\t\t\t\t\t\t\t       bank));\n+\t\t\trsp-\u003estat[cnt] = regval;\n+\t\t\tcnt++;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\t/* read MCAM entry STAT_ACT register */\n+\t\tregval = rvu_read64(rvu, blkaddr, NPC_AF_MCAMEX_BANKX_STAT_ACT(index, bank));\n+\n+\t\tif (!(regval \u0026 rvu-\u003ehw-\u003enpc_stat_ena)) {\n+\t\t\trsp-\u003estat[cnt] = 0;\n+\t\t\tcnt++;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tcntr = regval \u0026 0x1FF;\n+\n+\t\trsp-\u003estat[cnt] = rvu_read64(rvu, blkaddr, NPC_AF_MATCH_STATX(cntr));\n+\t\trsp-\u003estat[cnt] \u0026= BIT_ULL(48) - 1;\n+\t\tcnt++;\n+\t}\n+\n+\trsp-\u003ecnt = cnt;\n+\tmutex_unlock(\u0026mcam-\u003elock);\n+\treturn 0;\n+}\n+\n void rvu_npc_clear_ucast_entry(struct rvu *rvu, int pcifunc, int nixlf)\n {\n \tstruct npc_mcam *mcam = \u0026rvu-\u003ehw-\u003emcam;\ndiff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc_fs.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc_fs.c\nindex 91b5947dae0600..03bc3e321522fb 100644\n--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc_fs.c\n+++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc_fs.c\n@@ -1828,8 +1828,11 @@ int rvu_mbox_handler_npc_install_flow(struct rvu *rvu,\n \t\ttarget = req-\u003ehdr.pcifunc;\n \t}\n \n-\t/* ignore chan_mask in case pf func is not AF, revisit later */\n-\tif (!is_pffunc_af(req-\u003ehdr.pcifunc))\n+\t/* Non-AF requesters normally get the CPT default chan_mask. set_chanmask\n+\t * preserves caller-supplied chan_mask for switchdev-installed flows; see\n+\t * npc_install_flow_req.set_chanmask.\n+\t */\n+\tif (!is_pffunc_af(req-\u003ehdr.pcifunc) \u0026\u0026 !req-\u003eset_chanmask)\n \t\treq-\u003echan_mask = rvu_get_cpt_chan_mask(rvu);\n \n \terr = npc_check_unsupported_flows(rvu, req-\u003efeatures, req-\u003eintf);\n@@ -1926,6 +1929,17 @@ static int npc_delete_flow(struct rvu *rvu, struct rvu_npc_mcam_rule *rule,\n \treturn rvu_mbox_handler_npc_mcam_dis_entry(rvu, \u0026dis_req, \u0026dis_rsp);\n }\n \n+int rvu_mbox_handler_npc_mcam_get_features(struct rvu *rvu,\n+\t\t\t\t\t   struct msg_req *req,\n+\t\t\t\t\t   struct npc_mcam_get_features_rsp *rsp)\n+{\n+\tstruct npc_mcam *mcam = \u0026rvu-\u003ehw-\u003emcam;\n+\n+\trsp-\u003erx_features = mcam-\u003erx_features;\n+\trsp-\u003etx_features = mcam-\u003etx_features;\n+\treturn 0;\n+}\n+\n int rvu_mbox_handler_npc_delete_flow(struct rvu *rvu,\n \t\t\t\t     struct npc_delete_flow_req *req,\n \t\t\t\t     struct npc_delete_flow_rsp *rsp)\ndiff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_rep.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_rep.c\nindex a2781e0f504e3e..6bb6064b2391b4 100644\n--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_rep.c\n+++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_rep.c\n@@ -189,7 +189,7 @@ int rvu_mbox_handler_nix_lf_stats(struct rvu *rvu,\n \treturn 0;\n }\n \n-static u16 rvu_rep_get_vlan_id(struct rvu *rvu, u16 pcifunc)\n+u16 rvu_rep_get_vlan_id(struct rvu *rvu, u16 pcifunc)\n {\n \tint id;\n \n@@ -436,6 +436,7 @@ int rvu_mbox_handler_esw_cfg(struct rvu *rvu, struct esw_cfg_req *req,\n \t\treturn 0;\n \n \trvu-\u003erep_mode = req-\u003eena;\n+\tmemcpy(rvu-\u003erswitch.switch_id, req-\u003eswitch_id, MAX_PHYS_ITEM_ID_LEN);\n \n \tif (!rvu-\u003erep_mode)\n \t\trvu_npc_free_mcam_entries(rvu, req-\u003ehdr.pcifunc, -1);\ndiff --git a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.c b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.c\nnew file mode 100644\nindex 00000000000000..f233fe7b2983a9\n--- /dev/null\n+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.c\n@@ -0,0 +1,88 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/* Marvell RVU Admin Function driver\n+ *\n+ * Copyright (C) 2026 Marvell.\n+ *\n+ */\n+\n+#include \u003clinux/bitfield.h\u003e\n+#include \"rvu.h\"\n+#include \"rvu_sw_l2.h\"\n+#include \"rvu_sw_l3.h\"\n+#include \"rvu_sw_fl.h\"\n+#include \"rvu_sw.h\"\n+\n+u32 rvu_sw_port_id(struct rvu *rvu, u16 pcifunc)\n+{\n+\tu32 port_id;\n+\tu16 rep_id;\n+\n+\trep_id  = rvu_rep_get_vlan_id(rvu, pcifunc);\n+\n+\tport_id = FIELD_PREP(GENMASK_ULL(31, 16), rep_id) |\n+\t\t  FIELD_PREP(GENMASK_ULL(15, 0), pcifunc);\n+\n+\treturn port_id;\n+}\n+\n+static bool rvu_sw_swdev2af_msg_valid(u64 msg_type)\n+{\n+\treturn msg_type == SWDEV2AF_MSG_TYPE_FW_STATUS ||\n+\t       msg_type == SWDEV2AF_MSG_TYPE_REFRESH_FDB ||\n+\t       msg_type == SWDEV2AF_MSG_TYPE_REFRESH_FL;\n+}\n+\n+static int rvu_sw_swdev2af_sender_check(struct rvu *rvu,\n+\t\t\t\t\tstruct swdev2af_notify_req *req,\n+\t\t\t\t\tu64 msg_type)\n+{\n+\tu16 sender = req-\u003ehdr.pcifunc;\n+\n+\tif (!rvu_sw_swdev2af_msg_valid(msg_type))\n+\t\treturn -EINVAL;\n+\n+\tif (!rvu_is_switch_pcifunc(rvu, sender))\n+\t\treturn -EPERM;\n+\n+\treturn 0;\n+}\n+\n+int rvu_mbox_handler_swdev2af_notify(struct rvu *rvu,\n+\t\t\t\t     struct swdev2af_notify_req *req,\n+\t\t\t\t     struct msg_rsp *rsp)\n+{\n+\tint rc = 0;\n+\n+\trc = rvu_sw_swdev2af_sender_check(rvu, req, req-\u003emsg_type);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\tswitch (req-\u003emsg_type) {\n+\tcase SWDEV2AF_MSG_TYPE_FW_STATUS:\n+\t\trc = rvu_sw_l2_init_offl_wq(rvu, req-\u003ehdr.pcifunc, req-\u003efw_up);\n+\t\tbreak;\n+\n+\tcase SWDEV2AF_MSG_TYPE_REFRESH_FDB:\n+\t\trc = rvu_sw_l2_fdb_list_entry_add(rvu, req-\u003epcifunc, req-\u003emac);\n+\t\tbreak;\n+\n+\tcase SWDEV2AF_MSG_TYPE_REFRESH_FL:\n+\t\tif (req-\u003ecnt \u003c= 0 || req-\u003ecnt \u003e ARRAY_SIZE(req-\u003efl))\n+\t\t\treturn -EINVAL;\n+\t\trc = rvu_sw_fl_stats_sync2db(rvu, req-\u003efl, req-\u003ecnt);\n+\t\tbreak;\n+\n+\tdefault:\n+\t\trc = -EOPNOTSUPP;\n+\t\tbreak;\n+\t}\n+\n+\treturn rc;\n+}\n+\n+void rvu_sw_shutdown(void)\n+{\n+\trvu_sw_l2_shutdown();\n+\trvu_sw_l3_shutdown();\n+\trvu_sw_fl_shutdown();\n+}\ndiff --git a/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.h b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.h\nnew file mode 100644\nindex 00000000000000..a0cb2a9ce7ab43\n--- /dev/null\n+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.h\n@@ -0,0 +1,17 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/* Marvell RVU Admin Function driver\n+ *\n+ * Copyright (C) 2026 Marvell.\n+ *\n+ */\n+\n+#ifndef RVU_SWITCH_H\n+#define RVU_SWITCH_H\n+\n+/* RVU Switch */\n+#define RVU_SW_INVALID_PORT_ID\t((u32)~0U)\n+\n+u32 rvu_sw_port_id(struct rvu *rvu, u16 pcifunc);\n+void rvu_sw_shutdown(void);\n+\n+#endif\ndiff --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\nnew file mode 100644\nindex 00000000000000..34e23d57e64938\n--- /dev/null\n+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_fl.c\n@@ -0,0 +1,352 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/* Marvell RVU Admin Function driver\n+ *\n+ * Copyright (C) 2026 Marvell.\n+ *\n+ */\n+\n+#include \u003clinux/bitfield.h\u003e\n+#include \"rvu.h\"\n+#include \"rvu_sw.h\"\n+#include \"rvu_sw_fl.h\"\n+\n+#define M(_name, _id, _fn_name, _req_type, _rsp_type)\t\t\t\\\n+static struct _req_type __maybe_unused\t\t\t\t\t\\\n+*otx2_mbox_alloc_msg_ ## _fn_name(struct rvu *rvu, int devid)\t\t\\\n+{\t\t\t\t\t\t\t\t\t\\\n+\tstruct _req_type *req;\t\t\t\t\t\t\\\n+\t\t\t\t\t\t\t\t\t\\\n+\treq = (struct _req_type *)otx2_mbox_alloc_msg_rsp(\t\t\\\n+\t\t\u0026rvu-\u003eafpf_wq_info.mbox_up, devid, sizeof(struct _req_type), \\\n+\t\tsizeof(struct _rsp_type));\t\t\t\t\\\n+\tif (!req)\t\t\t\t\t\t\t\\\n+\t\treturn NULL;\t\t\t\t\t\t\\\n+\treq-\u003ehdr.sig = OTX2_MBOX_REQ_SIG;\t\t\t\t\\\n+\treq-\u003ehdr.id = _id;\t\t\t\t\t\t\\\n+\treturn req;\t\t\t\t\t\t\t\\\n+}\n+\n+MBOX_UP_AF2SWDEV_MESSAGES\n+#undef M\n+\n+#define RVU_SW_FL_REFRESH_MAX\t\t\t\t\t\t\\\n+\t((int)ARRAY_SIZE(((struct swdev2af_notify_req *)0)-\u003efl))\n+\n+struct fl_entry {\n+\tstruct list_head list;\n+\tstruct rvu *rvu;\n+\tu32 port_id;\n+\tunsigned long cookie;\n+\tstruct fl_tuple tuple;\n+\tu64 flags;\n+\tu64 features;\n+};\n+\n+static DEFINE_MUTEX(fl_offl_llock);\n+static LIST_HEAD(fl_offl_lh);\n+\n+static struct workqueue_struct *sw_fl_offl_wq;\n+static void sw_fl_offl_work_handler(struct work_struct *work);\n+static DECLARE_DELAYED_WORK(fl_offl_work, sw_fl_offl_work_handler);\n+\n+struct sw_fl_stats_node {\n+\tstruct list_head list;\n+\tunsigned long cookie;\n+\tu16 mcam_idx[2];\n+\tu64 opkts, npkts;\n+\tbool uni_di;\n+};\n+\n+static LIST_HEAD(sw_fl_stats_lh);\n+static DEFINE_MUTEX(sw_fl_stats_lock);\n+\n+static void rvu_sw_fl_queue_work(void)\n+{\n+\tif (sw_fl_offl_wq)\n+\t\tqueue_delayed_work(sw_fl_offl_wq, \u0026fl_offl_work,\n+\t\t\t\t   msecs_to_jiffies(10));\n+}\n+\n+static int rvu_sw_fl_ensure_wq(void)\n+{\n+\tif (sw_fl_offl_wq)\n+\t\treturn 0;\n+\n+\tsw_fl_offl_wq = alloc_workqueue(\"sw_af_fl_wq\", 0, 0);\n+\tif (!sw_fl_offl_wq)\n+\t\treturn -ENOMEM;\n+\n+\treturn 0;\n+}\n+\n+static int\n+rvu_sw_fl_stats_sync2db_one_entry(unsigned long cookie, u8 disabled,\n+\t\t\t\t  u16 mcam_idx[2], bool uni_di, u64 pkts)\n+{\n+\tstruct sw_fl_stats_node *snode, *tmp;\n+\n+\tmutex_lock(\u0026sw_fl_stats_lock);\n+\tlist_for_each_entry_safe(snode, tmp, \u0026sw_fl_stats_lh, list) {\n+\t\tif (snode-\u003ecookie != cookie)\n+\t\t\tcontinue;\n+\n+\t\tif (disabled) {\n+\t\t\tlist_del_init(\u0026snode-\u003elist);\n+\t\t\tmutex_unlock(\u0026sw_fl_stats_lock);\n+\t\t\tkfree(snode);\n+\t\t\treturn 0;\n+\t\t}\n+\n+\t\tif (snode-\u003euni_di != uni_di) {\n+\t\t\tsnode-\u003euni_di = uni_di;\n+\t\t\tsnode-\u003emcam_idx[1] = mcam_idx[1];\n+\t\t}\n+\n+\t\tif (snode-\u003eopkts == pkts) {\n+\t\t\tmutex_unlock(\u0026sw_fl_stats_lock);\n+\t\t\treturn 0;\n+\t\t}\n+\n+\t\tsnode-\u003enpkts = pkts;\n+\t\tmutex_unlock(\u0026sw_fl_stats_lock);\n+\t\treturn 0;\n+\t}\n+\n+\tif (disabled) {\n+\t\tmutex_unlock(\u0026sw_fl_stats_lock);\n+\t\treturn 0;\n+\t}\n+\n+\tsnode = kcalloc(1, sizeof(*snode), GFP_KERNEL);\n+\tif (!snode) {\n+\t\tmutex_unlock(\u0026sw_fl_stats_lock);\n+\t\treturn -ENOMEM;\n+\t}\n+\n+\tsnode-\u003ecookie = cookie;\n+\tsnode-\u003emcam_idx[0] = mcam_idx[0];\n+\tif (!uni_di)\n+\t\tsnode-\u003emcam_idx[1] = mcam_idx[1];\n+\n+\tsnode-\u003enpkts = pkts;\n+\tsnode-\u003euni_di = uni_di;\n+\tINIT_LIST_HEAD(\u0026snode-\u003elist);\n+\n+\tlist_add_tail(\u0026snode-\u003elist, \u0026sw_fl_stats_lh);\n+\tmutex_unlock(\u0026sw_fl_stats_lock);\n+\n+\treturn 0;\n+}\n+\n+int rvu_sw_fl_stats_sync2db(struct rvu *rvu, struct fl_info *fl, int cnt)\n+{\n+\tstruct npc_mcam_get_mul_stats_req *req = NULL;\n+\tstruct npc_mcam_get_mul_stats_rsp *rsp = NULL;\n+\tint i, idx;\n+\tint rc = 0;\n+\tu64 pkts;\n+\n+\tif (cnt \u003c= 0 || cnt \u003e RVU_SW_FL_REFRESH_MAX)\n+\t\treturn -EINVAL;\n+\n+\treq = kcalloc(1, sizeof(*req), GFP_KERNEL);\n+\tif (!req) {\n+\t\trc = -ENOMEM;\n+\t\tgoto fail;\n+\t}\n+\n+\trsp = kcalloc(1, sizeof(*rsp), GFP_KERNEL);\n+\tif (!rsp) {\n+\t\trc = -ENOMEM;\n+\t\tgoto fail;\n+\t}\n+\n+\tidx = 0;\n+\tfor (i = 0; i \u003c cnt; i++) {\n+\t\treq-\u003eentry[idx++] = fl[i].mcam_idx[0];\n+\t\tif (!fl[i].uni_di)\n+\t\t\treq-\u003eentry[idx++] = fl[i].mcam_idx[1];\n+\t}\n+\treq-\u003ecnt = idx;\n+\n+\tif (idx \u003e 256) {\n+\t\trc = -EINVAL;\n+\t\tgoto fail;\n+\t}\n+\n+\tif (rvu_mbox_handler_npc_mcam_mul_stats(rvu, req, rsp)) {\n+\t\tdev_err(rvu-\u003edev, \"Error to get multiple stats\\n\");\n+\t\trc = -EFAULT;\n+\t\tgoto fail;\n+\t}\n+\n+\tidx = 0;\n+\tfor (i = 0; i \u003c cnt; i++) {\n+\t\tpkts = rsp-\u003estat[idx++];\n+\t\tif (!fl[i].uni_di)\n+\t\t\tpkts += rsp-\u003estat[idx++];\n+\n+\t\trc |= rvu_sw_fl_stats_sync2db_one_entry(fl[i].cookie, fl[i].dis,\n+\t\t\t\t\t\t\tfl[i].mcam_idx,\n+\t\t\t\t\t\t\tfl[i].uni_di, pkts);\n+\t}\n+\n+fail:\n+\tkfree(req);\n+\tkfree(rsp);\n+\treturn rc;\n+}\n+\n+static int rvu_sw_fl_offl_rule_push(struct fl_entry *fl_entry)\n+{\n+\tstruct af2swdev_notify_req *req;\n+\tstruct rvu *rvu;\n+\tint swdev_pf;\n+\n+\trvu = fl_entry-\u003ervu;\n+\tswdev_pf = rvu_get_pf(rvu-\u003epdev, rvu-\u003erswitch.pcifunc);\n+\n+\tmutex_lock(\u0026rvu-\u003embox_lock);\n+\treq = otx2_mbox_alloc_msg_af2swdev_notify(rvu, swdev_pf);\n+\tif (!req) {\n+\t\tmutex_unlock(\u0026rvu-\u003embox_lock);\n+\t\treturn -ENOMEM;\n+\t}\n+\n+\treq-\u003etuple = fl_entry-\u003etuple;\n+\treq-\u003eflags = fl_entry-\u003eflags;\n+\treq-\u003ecookie = fl_entry-\u003ecookie;\n+\treq-\u003efeatures = fl_entry-\u003efeatures;\n+\n+\tif (!otx2_mbox_wait_for_zero(\u0026rvu-\u003eafpf_wq_info.mbox_up, swdev_pf)) {\n+\t\tmutex_unlock(\u0026rvu-\u003embox_lock);\n+\t\treturn -EBUSY;\n+\t}\n+\n+\totx2_mbox_msg_send_up(\u0026rvu-\u003eafpf_wq_info.mbox_up, swdev_pf);\n+\n+\tmutex_unlock(\u0026rvu-\u003embox_lock);\n+\treturn 0;\n+}\n+\n+static void sw_fl_offl_work_handler(struct work_struct *work)\n+{\n+\tstruct fl_entry *fl_entry;\n+\n+\tmutex_lock(\u0026fl_offl_llock);\n+\tfl_entry = list_first_entry_or_null(\u0026fl_offl_lh, struct fl_entry, list);\n+\tif (!fl_entry) {\n+\t\tmutex_unlock(\u0026fl_offl_llock);\n+\t\treturn;\n+\t}\n+\n+\tlist_del_init(\u0026fl_entry-\u003elist);\n+\tmutex_unlock(\u0026fl_offl_llock);\n+\n+\tif (rvu_sw_fl_offl_rule_push(fl_entry)) {\n+\t\tmutex_lock(\u0026fl_offl_llock);\n+\t\tlist_add_tail(\u0026fl_entry-\u003elist, \u0026fl_offl_lh);\n+\t\tmutex_unlock(\u0026fl_offl_llock);\n+\t\tif (sw_fl_offl_wq)\n+\t\t\tqueue_delayed_work(sw_fl_offl_wq, \u0026fl_offl_work,\n+\t\t\t\t\t   msecs_to_jiffies(100));\n+\t\treturn;\n+\t}\n+\n+\tkfree(fl_entry);\n+\n+\tmutex_lock(\u0026fl_offl_llock);\n+\tif (!list_empty(\u0026fl_offl_lh))\n+\t\trvu_sw_fl_queue_work();\n+\tmutex_unlock(\u0026fl_offl_llock);\n+}\n+\n+int rvu_mbox_handler_fl_get_stats(struct rvu *rvu,\n+\t\t\t\t  struct fl_get_stats_req *req,\n+\t\t\t\t  struct fl_get_stats_rsp *rsp)\n+{\n+\tstruct sw_fl_stats_node *snode, *tmp;\n+\n+\tmutex_lock(\u0026sw_fl_stats_lock);\n+\tlist_for_each_entry_safe(snode, tmp, \u0026sw_fl_stats_lh, list) {\n+\t\tif (snode-\u003ecookie != req-\u003ecookie)\n+\t\t\tcontinue;\n+\n+\t\trsp-\u003epkts_diff = snode-\u003enpkts - snode-\u003eopkts;\n+\t\tsnode-\u003eopkts = snode-\u003enpkts;\n+\t\tbreak;\n+\t}\n+\tmutex_unlock(\u0026sw_fl_stats_lock);\n+\treturn 0;\n+}\n+\n+int rvu_mbox_handler_fl_notify(struct rvu *rvu,\n+\t\t\t       struct fl_notify_req *req,\n+\t\t\t       struct msg_rsp *rsp)\n+{\n+\tstruct fl_entry *fl_entry;\n+\tint rc;\n+\n+\tif (!(rvu-\u003erswitch.flags \u0026 RVU_SWITCH_FLAG_FW_READY))\n+\t\treturn -EAGAIN;\n+\n+\tfl_entry = kcalloc(1, sizeof(*fl_entry), GFP_KERNEL);\n+\tif (!fl_entry)\n+\t\treturn -ENOMEM;\n+\n+\tfl_entry-\u003eport_id = rvu_sw_port_id(rvu, req-\u003ehdr.pcifunc);\n+\tfl_entry-\u003ervu = rvu;\n+\tINIT_LIST_HEAD(\u0026fl_entry-\u003elist);\n+\tfl_entry-\u003etuple = req-\u003etuple;\n+\tfl_entry-\u003ecookie = req-\u003ecookie;\n+\tfl_entry-\u003eflags = req-\u003eflags;\n+\tfl_entry-\u003efeatures = req-\u003efeatures;\n+\n+\tmutex_lock(\u0026fl_offl_llock);\n+\trc = rvu_sw_fl_ensure_wq();\n+\tif (rc) {\n+\t\tmutex_unlock(\u0026fl_offl_llock);\n+\t\tkfree(fl_entry);\n+\t\treturn rc;\n+\t}\n+\n+\tlist_add_tail(\u0026fl_entry-\u003elist, \u0026fl_offl_lh);\n+\trvu_sw_fl_queue_work();\n+\tmutex_unlock(\u0026fl_offl_llock);\n+\n+\treturn 0;\n+}\n+\n+void rvu_sw_fl_shutdown(void)\n+{\n+\tstruct sw_fl_stats_node *snode, *tmp;\n+\tstruct workqueue_struct *wq;\n+\tstruct fl_entry *entry;\n+\n+\tmutex_lock(\u0026sw_fl_stats_lock);\n+\tlist_for_each_entry_safe(snode, tmp, \u0026sw_fl_stats_lh, list) {\n+\t\tlist_del_init(\u0026snode-\u003elist);\n+\t\tkfree(snode);\n+\t}\n+\tmutex_unlock(\u0026sw_fl_stats_lock);\n+\n+\tif (!sw_fl_offl_wq)\n+\t\treturn;\n+\n+\tcancel_delayed_work_sync(\u0026fl_offl_work);\n+\twq = sw_fl_offl_wq;\n+\tsw_fl_offl_wq = NULL;\n+\tdestroy_workqueue(wq);\n+\n+\tmutex_lock(\u0026fl_offl_llock);\n+\twhile (1) {\n+\t\tentry = list_first_entry_or_null(\u0026fl_offl_lh,\n+\t\t\t\t\t\t struct fl_entry, list);\n+\t\tif (!entry)\n+\t\t\tbreak;\n+\n+\t\tlist_del_init(\u0026entry-\u003elist);\n+\t\tkfree(entry);\n+\t}\n+\tmutex_unlock(\u0026fl_offl_llock);\n+}\ndiff --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\nnew file mode 100644\nindex 00000000000000..f117a96fc33e7b\n--- /dev/null\n+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_fl.h\n@@ -0,0 +1,13 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/* Marvell RVU Admin Function driver\n+ *\n+ * Copyright (C) 2026 Marvell.\n+ *\n+ */\n+\n+#ifndef RVU_SW_FL_H\n+#define RVU_SW_FL_H\n+int rvu_sw_fl_stats_sync2db(struct rvu *rvu, struct fl_info *fl, int cnt);\n+void rvu_sw_fl_shutdown(void);\n+\n+#endif\ndiff --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\nnew file mode 100644\nindex 00000000000000..f61b2d15768b13\n--- /dev/null\n+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l2.c\n@@ -0,0 +1,500 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/* Marvell RVU Admin Function driver\n+ *\n+ * Copyright (C) 2026 Marvell.\n+ *\n+ */\n+\n+#include \u003clinux/bitfield.h\u003e\n+#include \"rvu.h\"\n+#include \"rvu_sw.h\"\n+#include \"rvu_sw_l2.h\"\n+\n+#define M(_name, _id, _fn_name, _req_type, _rsp_type)\t\t\t\\\n+static struct _req_type __maybe_unused\t\t\t\t\t\\\n+*otx2_mbox_alloc_msg_ ## _fn_name(struct rvu *rvu, int devid)\t\t\\\n+{\t\t\t\t\t\t\t\t\t\\\n+\tstruct _req_type *req;\t\t\t\t\t\t\\\n+\t\t\t\t\t\t\t\t\t\\\n+\treq = (struct _req_type *)otx2_mbox_alloc_msg_rsp(\t\t\\\n+\t\t\u0026rvu-\u003eafpf_wq_info.mbox_up, devid, sizeof(struct _req_type), \\\n+\t\tsizeof(struct _rsp_type));\t\t\t\t\\\n+\tif (!req)\t\t\t\t\t\t\t\\\n+\t\treturn NULL;\t\t\t\t\t\t\\\n+\treq-\u003ehdr.sig = OTX2_MBOX_REQ_SIG;\t\t\t\t\\\n+\treq-\u003ehdr.id = _id;\t\t\t\t\t\t\\\n+\treturn req;\t\t\t\t\t\t\t\\\n+}\n+MBOX_UP_AF2SWDEV_MESSAGES\n+MBOX_UP_AF2PF_FDB_REFRESH_MESSAGES\n+#undef M\n+\n+#define RVU_SW_L2_LIST_MAX 4096\n+\n+struct l2_entry {\n+\tstruct list_head list;\n+\tu64 flags;\n+\tu32 port_id;\n+\tu8  mac[ETH_ALEN];\n+};\n+\n+static DEFINE_MUTEX(l2_offl_list_lock);\n+static LIST_HEAD(l2_offl_lh);\n+static atomic_t l2_offl_list_cnt = ATOMIC_INIT(0);\n+\n+static DEFINE_MUTEX(fdb_refresh_list_lock);\n+static LIST_HEAD(fdb_refresh_lh);\n+static atomic_t fdb_refresh_list_cnt = ATOMIC_INIT(0);\n+\n+struct rvu_sw_l2_work {\n+\tstruct rvu *rvu;\n+\tstruct work_struct work;\n+};\n+\n+/* Work queue for switchdev message handling. There is only\n+ * one switch HW per SoC, so one instance of each type of\n+ * workqueue is enough.\n+ */\n+static struct rvu_sw_l2_work l2_offl_work;\n+static struct workqueue_struct *rvu_sw_l2_offl_wq;\n+\n+static struct rvu_sw_l2_work fdb_refresh_work;\n+static struct workqueue_struct *fdb_refresh_wq;\n+\n+static bool fw_is_up;\n+static DEFINE_SPINLOCK(rvu_sw_l2_state_lock);\n+\n+static void rvu_sw_l2_list_cnt_warn(struct device *dev, atomic_t *cnt,\n+\t\t\t\t    const char *name)\n+{\n+\tint n = atomic_read(cnt);\n+\n+\tif (n \u003c 0)\n+\t\tdev_warn(dev, \"L2 %s list count underflow: %d\\n\", name, n);\n+\telse if (n \u003e RVU_SW_L2_LIST_MAX)\n+\t\tdev_warn(dev, \"L2 %s list count overflow: %d (max %d)\\n\",\n+\t\t\t name, n, RVU_SW_L2_LIST_MAX);\n+}\n+\n+static void rvu_sw_l2_list_cnt_inc(struct device *dev, atomic_t *cnt,\n+\t\t\t\t   const char *name)\n+{\n+\tatomic_inc(cnt);\n+\trvu_sw_l2_list_cnt_warn(dev, cnt, name);\n+}\n+\n+static void rvu_sw_l2_list_cnt_dec(struct device *dev, atomic_t *cnt,\n+\t\t\t\t   const char *name)\n+{\n+\tatomic_dec(cnt);\n+\trvu_sw_l2_list_cnt_warn(dev, cnt, name);\n+}\n+\n+static void rvu_sw_l2_destroy_wqs(struct rvu *rvu)\n+{\n+\tstruct workqueue_struct *offl_wq, *refresh_wq;\n+\tstruct l2_entry *entry;\n+\n+\tspin_lock_bh(\u0026rvu_sw_l2_state_lock);\n+\trvu-\u003erswitch.flags \u0026= ~RVU_SWITCH_FLAG_FW_READY;\n+\trvu-\u003erswitch.pcifunc = 0;\n+\tfw_is_up = false;\n+\toffl_wq = rvu_sw_l2_offl_wq;\n+\trefresh_wq = fdb_refresh_wq;\n+\trvu_sw_l2_offl_wq = NULL;\n+\tfdb_refresh_wq = NULL;\n+\tspin_unlock_bh(\u0026rvu_sw_l2_state_lock);\n+\n+\tif (refresh_wq) {\n+\t\tcancel_work_sync(\u0026fdb_refresh_work.work);\n+\t\tdestroy_workqueue(refresh_wq);\n+\n+\t\tmutex_lock(\u0026fdb_refresh_list_lock);\n+\t\trvu_sw_l2_list_cnt_warn(rvu-\u003edev, \u0026fdb_refresh_list_cnt,\n+\t\t\t\t\t\"fdb refresh\");\n+\t\twhile (1) {\n+\t\t\tentry = list_first_entry_or_null(\u0026fdb_refresh_lh,\n+\t\t\t\t\t\t\t struct l2_entry, list);\n+\t\t\tif (!entry)\n+\t\t\t\tbreak;\n+\n+\t\t\tlist_del_init(\u0026entry-\u003elist);\n+\t\t\tkfree(entry);\n+\t\t}\n+\t\tatomic_set(\u0026fdb_refresh_list_cnt, 0);\n+\t\tmutex_unlock(\u0026fdb_refresh_list_lock);\n+\t}\n+\n+\tif (offl_wq) {\n+\t\tcancel_work_sync(\u0026l2_offl_work.work);\n+\t\tdestroy_workqueue(offl_wq);\n+\n+\t\tmutex_lock(\u0026l2_offl_list_lock);\n+\t\trvu_sw_l2_list_cnt_warn(rvu-\u003edev, \u0026l2_offl_list_cnt, \"offload\");\n+\t\twhile (1) {\n+\t\t\tentry = list_first_entry_or_null(\u0026l2_offl_lh,\n+\t\t\t\t\t\t\t struct l2_entry, list);\n+\t\t\tif (!entry)\n+\t\t\t\tbreak;\n+\n+\t\t\tlist_del_init(\u0026entry-\u003elist);\n+\t\t\tkfree(entry);\n+\t\t}\n+\t\tatomic_set(\u0026l2_offl_list_cnt, 0);\n+\t\tmutex_unlock(\u0026l2_offl_list_lock);\n+\t}\n+}\n+\n+/* High-frequency link state transitions or aggressive FDB\n+ * aging intervals can induce rapid fdb churn. To prevent\n+ * thrashing, inhibit hardware offloading of these transient\n+ * forwarding states to the switching ASIC.  When processing an ADD,\n+ * drop a queued DELETE for the same MAC that has not yet been sent to\n+ * hardware; the ADD reflects the desired final state and supersedes it.\n+ */\n+static void rvu_sw_l2_offl_drop_pending_del(u8 *mac)\n+{\n+\tstruct l2_entry *entry, *tmp;\n+\n+\tmutex_lock(\u0026l2_offl_list_lock);\n+\tlist_for_each_entry_safe(entry, tmp, \u0026l2_offl_lh, list) {\n+\t\tif (!ether_addr_equal(mac, entry-\u003emac))\n+\t\t\tcontinue;\n+\n+\t\tif (!(entry-\u003eflags \u0026 FDB_DEL))\n+\t\t\tcontinue;\n+\n+\t\tlist_del_init(\u0026entry-\u003elist);\n+\t\trvu_sw_l2_list_cnt_dec(l2_offl_work.rvu-\u003edev, \u0026l2_offl_list_cnt,\n+\t\t\t\t       \"offload\");\n+\t\tkfree(entry);\n+\t\tbreak;\n+\t}\n+\tmutex_unlock(\u0026l2_offl_list_lock);\n+}\n+\n+static int rvu_sw_l2_offl_rule_push(struct rvu *rvu, struct l2_entry *l2_entry)\n+{\n+\tstruct af2swdev_notify_req *req;\n+\tint swdev_pf;\n+\n+\tswdev_pf = rvu_get_pf(rvu-\u003epdev, rvu-\u003erswitch.pcifunc);\n+\n+\tmutex_lock(\u0026rvu-\u003embox_lock);\n+\treq = otx2_mbox_alloc_msg_af2swdev_notify(rvu, swdev_pf);\n+\tif (!req) {\n+\t\tmutex_unlock(\u0026rvu-\u003embox_lock);\n+\t\treturn -ENOMEM;\n+\t}\n+\n+\tether_addr_copy(req-\u003emac, l2_entry-\u003emac);\n+\treq-\u003eflags = l2_entry-\u003eflags;\n+\treq-\u003eport_id = l2_entry-\u003eport_id;\n+\n+\totx2_mbox_wait_for_zero(\u0026rvu-\u003eafpf_wq_info.mbox_up, swdev_pf);\n+\totx2_mbox_msg_send_up(\u0026rvu-\u003eafpf_wq_info.mbox_up, swdev_pf);\n+\n+\tmutex_unlock(\u0026rvu-\u003embox_lock);\n+\treturn 0;\n+}\n+\n+static int rvu_sw_l2_fdb_refresh_send(struct rvu *rvu, u16 pcifunc, u8 *mac)\n+{\n+\tstruct af2pf_fdb_refresh_req *req;\n+\tint pf, vidx;\n+\n+\tif (!is_pf_func_valid(rvu, pcifunc))\n+\t\treturn -EINVAL;\n+\n+\tpf = rvu_get_pf(rvu-\u003epdev, pcifunc);\n+\n+\tmutex_lock(\u0026rvu-\u003embox_lock);\n+\n+\tif (pf) {\n+\t\tif (pf \u003e= rvu-\u003eafpf_wq_info.mbox_up.ndevs) {\n+\t\t\tmutex_unlock(\u0026rvu-\u003embox_lock);\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\n+\t\treq = otx2_mbox_alloc_msg_af2pf_fdb_refresh(rvu, pf);\n+\t\tif (!req) {\n+\t\t\tmutex_unlock(\u0026rvu-\u003embox_lock);\n+\t\t\treturn -ENOMEM;\n+\t\t}\n+\n+\t\treq-\u003ehdr.pcifunc = pcifunc;\n+\t\tether_addr_copy(req-\u003emac, mac);\n+\t\treq-\u003epcifunc = pcifunc;\n+\t\treq-\u003eflags = FDB_ADD;\n+\n+\t\totx2_mbox_wait_for_zero(\u0026rvu-\u003eafpf_wq_info.mbox_up, pf);\n+\t\totx2_mbox_msg_send_up(\u0026rvu-\u003eafpf_wq_info.mbox_up, pf);\n+\t} else {\n+\t\tvidx = pcifunc - 1;\n+\n+\t\tif (vidx \u003c 0 || vidx \u003e= rvu-\u003eafvf_wq_info.mbox_up.ndevs) {\n+\t\t\tmutex_unlock(\u0026rvu-\u003embox_lock);\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\n+\t\treq = (struct af2pf_fdb_refresh_req *)\n+\t\t\totx2_mbox_alloc_msg_rsp(\u0026rvu-\u003eafvf_wq_info.mbox_up, vidx,\n+\t\t\t\t\t\tsizeof(*req), sizeof(struct msg_rsp));\n+\t\tif (!req) {\n+\t\t\tmutex_unlock(\u0026rvu-\u003embox_lock);\n+\t\t\treturn -ENOMEM;\n+\t\t}\n+\t\treq-\u003ehdr.sig = OTX2_MBOX_REQ_SIG;\n+\t\treq-\u003ehdr.id = MBOX_MSG_AF2PF_FDB_REFRESH;\n+\n+\t\treq-\u003ehdr.pcifunc = pcifunc;\n+\t\tether_addr_copy(req-\u003emac, mac);\n+\t\treq-\u003epcifunc = pcifunc;\n+\t\treq-\u003eflags = FDB_ADD;\n+\n+\t\totx2_mbox_wait_for_zero(\u0026rvu-\u003eafvf_wq_info.mbox_up, vidx);\n+\t\totx2_mbox_msg_send_up(\u0026rvu-\u003eafvf_wq_info.mbox_up, vidx);\n+\t}\n+\n+\tmutex_unlock(\u0026rvu-\u003embox_lock);\n+\n+\treturn 0;\n+}\n+\n+static void rvu_sw_l2_fdb_refresh_wq_handler(struct work_struct *work)\n+{\n+\tstruct rvu_sw_l2_work *fdb_work;\n+\tstruct l2_entry *l2_entry;\n+\n+\tfdb_work = container_of(work, struct rvu_sw_l2_work, work);\n+\n+\twhile (1) {\n+\t\tmutex_lock(\u0026fdb_refresh_list_lock);\n+\t\tl2_entry = list_first_entry_or_null(\u0026fdb_refresh_lh,\n+\t\t\t\t\t\t    struct l2_entry, list);\n+\t\tif (!l2_entry) {\n+\t\t\tmutex_unlock(\u0026fdb_refresh_list_lock);\n+\t\t\treturn;\n+\t\t}\n+\n+\t\tlist_del_init(\u0026l2_entry-\u003elist);\n+\t\trvu_sw_l2_list_cnt_dec(fdb_work-\u003ervu-\u003edev, \u0026fdb_refresh_list_cnt,\n+\t\t\t\t       \"fdb refresh\");\n+\t\tmutex_unlock(\u0026fdb_refresh_list_lock);\n+\n+\t\trvu_sw_l2_fdb_refresh_send(fdb_work-\u003ervu, l2_entry-\u003eport_id,\n+\t\t\t\t\t   l2_entry-\u003emac);\n+\t\tkfree(l2_entry);\n+\t}\n+}\n+\n+static void rvu_sw_l2_offl_rule_wq_handler(struct work_struct *work)\n+{\n+\tstruct rvu_sw_l2_work *offl_work;\n+\tstruct l2_entry *l2_entry;\n+\tint budget = 16;\n+\tbool add_fdb;\n+\n+\toffl_work = container_of(work, struct rvu_sw_l2_work, work);\n+\n+\twhile (budget--) {\n+\t\tmutex_lock(\u0026l2_offl_list_lock);\n+\t\tl2_entry = list_first_entry_or_null(\u0026l2_offl_lh, struct l2_entry, list);\n+\t\tif (!l2_entry) {\n+\t\t\tmutex_unlock(\u0026l2_offl_list_lock);\n+\t\t\treturn;\n+\t\t}\n+\n+\t\tlist_del_init(\u0026l2_entry-\u003elist);\n+\t\trvu_sw_l2_list_cnt_dec(offl_work-\u003ervu-\u003edev, \u0026l2_offl_list_cnt,\n+\t\t\t\t       \"offload\");\n+\t\tmutex_unlock(\u0026l2_offl_list_lock);\n+\n+\t\tadd_fdb = !!(l2_entry-\u003eflags \u0026 FDB_ADD);\n+\n+\t\tif (add_fdb)\n+\t\t\trvu_sw_l2_offl_drop_pending_del(l2_entry-\u003emac);\n+\n+\t\tif (rvu_sw_l2_offl_rule_push(offl_work-\u003ervu, l2_entry))\n+\t\t\tdev_err(offl_work-\u003ervu-\u003edev,\n+\t\t\t\t\"%s: Error to push l2 rule\\n\",\n+\t\t\t\t__func__);\n+\t\tkfree(l2_entry);\n+\t}\n+\n+\tspin_lock_bh(\u0026rvu_sw_l2_state_lock);\n+\tif (rvu_sw_l2_offl_wq \u0026\u0026 atomic_read(\u0026l2_offl_list_cnt))\n+\t\tqueue_work(rvu_sw_l2_offl_wq, \u0026l2_offl_work.work);\n+\tspin_unlock_bh(\u0026rvu_sw_l2_state_lock);\n+}\n+\n+int rvu_sw_l2_init_offl_wq(struct rvu *rvu, u16 pcifunc, bool fw_up)\n+{\n+\tstruct rvu_switch *rswitch = \u0026rvu-\u003erswitch;\n+\n+\tif (!fw_up) {\n+\t\trvu_sw_l2_destroy_wqs(rvu);\n+\t\treturn 0;\n+\t}\n+\n+\tspin_lock_bh(\u0026rvu_sw_l2_state_lock);\n+\tif (fw_is_up \u0026\u0026 rvu_sw_l2_offl_wq \u0026\u0026 fdb_refresh_wq) {\n+\t\trswitch-\u003epcifunc = pcifunc;\n+\t\trswitch-\u003eflags |= RVU_SWITCH_FLAG_FW_READY;\n+\t\tspin_unlock_bh(\u0026rvu_sw_l2_state_lock);\n+\t\treturn 0;\n+\t}\n+\tspin_unlock_bh(\u0026rvu_sw_l2_state_lock);\n+\n+\tif (rvu_sw_l2_offl_wq || fdb_refresh_wq)\n+\t\trvu_sw_l2_destroy_wqs(rvu);\n+\n+\tl2_offl_work.rvu = rvu;\n+\tINIT_WORK(\u0026l2_offl_work.work, rvu_sw_l2_offl_rule_wq_handler);\n+\trvu_sw_l2_offl_wq = alloc_workqueue(\"swdev_rvu_sw_l2_offl_wq\", 0, 0);\n+\tif (!rvu_sw_l2_offl_wq) {\n+\t\tdev_err(rvu-\u003edev, \"L2 offl workqueue allocation failed\\n\");\n+\t\treturn -ENOMEM;\n+\t}\n+\n+\tfdb_refresh_work.rvu = rvu;\n+\tINIT_WORK(\u0026fdb_refresh_work.work, rvu_sw_l2_fdb_refresh_wq_handler);\n+\tfdb_refresh_wq = alloc_workqueue(\"swdev_fdb_refresh_wq\", 0, 0);\n+\tif (!fdb_refresh_wq) {\n+\t\tdev_err(rvu-\u003edev, \"fdb refresh workqueue allocation failed\\n\");\n+\t\tdestroy_workqueue(rvu_sw_l2_offl_wq);\n+\t\trvu_sw_l2_offl_wq = NULL;\n+\t\treturn -ENOMEM;\n+\t}\n+\n+\tspin_lock_bh(\u0026rvu_sw_l2_state_lock);\n+\tfw_is_up = true;\n+\trswitch-\u003epcifunc = pcifunc;\n+\trswitch-\u003eflags |= RVU_SWITCH_FLAG_FW_READY;\n+\tspin_unlock_bh(\u0026rvu_sw_l2_state_lock);\n+\n+\treturn 0;\n+}\n+\n+int rvu_sw_l2_fdb_list_entry_add(struct rvu *rvu, u16 pcifunc, u8 *mac)\n+{\n+\tstruct workqueue_struct *wq;\n+\tstruct l2_entry *l2_entry;\n+\n+\tif (!is_pf_func_valid(rvu, pcifunc))\n+\t\treturn -EINVAL;\n+\n+\tspin_lock_bh(\u0026rvu_sw_l2_state_lock);\n+\tif (!fdb_refresh_wq) {\n+\t\tspin_unlock_bh(\u0026rvu_sw_l2_state_lock);\n+\t\treturn -EINVAL;\n+\t}\n+\tspin_unlock_bh(\u0026rvu_sw_l2_state_lock);\n+\n+\tif (atomic_read(\u0026fdb_refresh_list_cnt) \u003e= RVU_SW_L2_LIST_MAX) {\n+\t\trvu_sw_l2_list_cnt_warn(rvu-\u003edev, \u0026fdb_refresh_list_cnt,\n+\t\t\t\t\t\"fdb refresh\");\n+\t\treturn -ENOMEM;\n+\t}\n+\n+\tl2_entry = kcalloc(1, sizeof(*l2_entry), GFP_KERNEL);\n+\tif (!l2_entry)\n+\t\treturn -ENOMEM;\n+\n+\tl2_entry-\u003eport_id = pcifunc;\n+\tether_addr_copy(l2_entry-\u003emac, mac);\n+\n+\tmutex_lock(\u0026fdb_refresh_list_lock);\n+\tif (atomic_read(\u0026fdb_refresh_list_cnt) \u003e= RVU_SW_L2_LIST_MAX) {\n+\t\trvu_sw_l2_list_cnt_warn(rvu-\u003edev, \u0026fdb_refresh_list_cnt,\n+\t\t\t\t\t\"fdb refresh\");\n+\t\tmutex_unlock(\u0026fdb_refresh_list_lock);\n+\t\tkfree(l2_entry);\n+\t\treturn -ENOMEM;\n+\t}\n+\tlist_add_tail(\u0026l2_entry-\u003elist, \u0026fdb_refresh_lh);\n+\trvu_sw_l2_list_cnt_inc(rvu-\u003edev, \u0026fdb_refresh_list_cnt, \"fdb refresh\");\n+\tmutex_unlock(\u0026fdb_refresh_list_lock);\n+\n+\tspin_lock_bh(\u0026rvu_sw_l2_state_lock);\n+\twq = fdb_refresh_wq;\n+\tif (wq)\n+\t\tqueue_work(wq, \u0026fdb_refresh_work.work);\n+\tspin_unlock_bh(\u0026rvu_sw_l2_state_lock);\n+\n+\tif (!wq) {\n+\t\tmutex_lock(\u0026fdb_refresh_list_lock);\n+\t\tlist_del_init(\u0026l2_entry-\u003elist);\n+\t\trvu_sw_l2_list_cnt_dec(rvu-\u003edev, \u0026fdb_refresh_list_cnt,\n+\t\t\t\t       \"fdb refresh\");\n+\t\tmutex_unlock(\u0026fdb_refresh_list_lock);\n+\t\tkfree(l2_entry);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int rvu_mbox_handler_fdb_notify(struct rvu *rvu,\n+\t\t\t\tstruct fdb_notify_req *req,\n+\t\t\t\tstruct msg_rsp *rsp)\n+{\n+\tstruct workqueue_struct *wq;\n+\tstruct l2_entry *l2_entry;\n+\n+\tspin_lock_bh(\u0026rvu_sw_l2_state_lock);\n+\tif (!(rvu-\u003erswitch.flags \u0026 RVU_SWITCH_FLAG_FW_READY) ||\n+\t    !rvu_sw_l2_offl_wq) {\n+\t\tspin_unlock_bh(\u0026rvu_sw_l2_state_lock);\n+\t\treturn 0;\n+\t}\n+\tspin_unlock_bh(\u0026rvu_sw_l2_state_lock);\n+\n+\tif (atomic_read(\u0026l2_offl_list_cnt) \u003e= RVU_SW_L2_LIST_MAX) {\n+\t\trvu_sw_l2_list_cnt_warn(rvu-\u003edev, \u0026l2_offl_list_cnt, \"offload\");\n+\t\treturn -ENOMEM;\n+\t}\n+\n+\tl2_entry = kcalloc(1, sizeof(*l2_entry), GFP_KERNEL);\n+\tif (!l2_entry)\n+\t\treturn -ENOMEM;\n+\n+\tl2_entry-\u003eport_id = rvu_sw_port_id(rvu, req-\u003ehdr.pcifunc);\n+\tether_addr_copy(l2_entry-\u003emac, req-\u003emac);\n+\tl2_entry-\u003eflags = req-\u003eflags;\n+\n+\tmutex_lock(\u0026l2_offl_list_lock);\n+\tif (atomic_read(\u0026l2_offl_list_cnt) \u003e= RVU_SW_L2_LIST_MAX) {\n+\t\trvu_sw_l2_list_cnt_warn(rvu-\u003edev, \u0026l2_offl_list_cnt, \"offload\");\n+\t\tmutex_unlock(\u0026l2_offl_list_lock);\n+\t\tkfree(l2_entry);\n+\t\treturn -ENOMEM;\n+\t}\n+\tlist_add_tail(\u0026l2_entry-\u003elist, \u0026l2_offl_lh);\n+\trvu_sw_l2_list_cnt_inc(rvu-\u003edev, \u0026l2_offl_list_cnt, \"offload\");\n+\tmutex_unlock(\u0026l2_offl_list_lock);\n+\n+\tspin_lock_bh(\u0026rvu_sw_l2_state_lock);\n+\twq = rvu_sw_l2_offl_wq;\n+\tif (wq)\n+\t\tqueue_work(wq, \u0026l2_offl_work.work);\n+\tspin_unlock_bh(\u0026rvu_sw_l2_state_lock);\n+\n+\tif (!wq) {\n+\t\tmutex_lock(\u0026l2_offl_list_lock);\n+\t\tlist_del_init(\u0026l2_entry-\u003elist);\n+\t\trvu_sw_l2_list_cnt_dec(rvu-\u003edev, \u0026l2_offl_list_cnt, \"offload\");\n+\t\tmutex_unlock(\u0026l2_offl_list_lock);\n+\t\tkfree(l2_entry);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+void rvu_sw_l2_shutdown(void)\n+{\n+\tif (!fdb_refresh_wq \u0026\u0026 !rvu_sw_l2_offl_wq)\n+\t\treturn;\n+\n+\trvu_sw_l2_destroy_wqs(l2_offl_work.rvu);\n+}\ndiff --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\nnew file mode 100644\nindex 00000000000000..6685431d60a236\n--- /dev/null\n+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l2.h\n@@ -0,0 +1,14 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/* Marvell RVU Admin Function driver\n+ *\n+ * Copyright (C) 2026 Marvell.\n+ *\n+ */\n+\n+#ifndef RVU_SW_L2_H\n+#define RVU_SW_L2_H\n+\n+int rvu_sw_l2_init_offl_wq(struct rvu *rvu, u16 pcifunc, bool fw_up);\n+int rvu_sw_l2_fdb_list_entry_add(struct rvu *rvu, u16 pcifunc, u8 *mac);\n+void rvu_sw_l2_shutdown(void);\n+#endif\ndiff --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\nnew file mode 100644\nindex 00000000000000..b9ba5ea1aaef89\n--- /dev/null\n+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l3.c\n@@ -0,0 +1,259 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/* Marvell RVU Admin Function driver\n+ *\n+ * Copyright (C) 2026 Marvell.\n+ *\n+ */\n+\n+#include \u003clinux/bitfield.h\u003e\n+#include \"rvu.h\"\n+#include \"rvu_sw.h\"\n+#include \"rvu_sw_l3.h\"\n+\n+#define M(_name, _id, _fn_name, _req_type, _rsp_type)\t\t\t\\\n+static struct _req_type __maybe_unused\t\t\t\t\t\\\n+*otx2_mbox_alloc_msg_ ## _fn_name(struct rvu *rvu, int devid)\t\t\\\n+{\t\t\t\t\t\t\t\t\t\\\n+\tstruct _req_type *req;\t\t\t\t\t\t\\\n+\t\t\t\t\t\t\t\t\t\\\n+\treq = (struct _req_type *)otx2_mbox_alloc_msg_rsp(\t\t\\\n+\t\t\u0026rvu-\u003eafpf_wq_info.mbox_up, devid, sizeof(struct _req_type), \\\n+\t\tsizeof(struct _rsp_type));\t\t\t\t\\\n+\tif (!req)\t\t\t\t\t\t\t\\\n+\t\treturn NULL;\t\t\t\t\t\t\\\n+\treq-\u003ehdr.sig = OTX2_MBOX_REQ_SIG;\t\t\t\t\\\n+\treq-\u003ehdr.id = _id;\t\t\t\t\t\t\\\n+\treturn req;\t\t\t\t\t\t\t\\\n+}\n+\n+MBOX_UP_AF2SWDEV_MESSAGES\n+#undef M\n+\n+#define RVU_SW_L3_BATCH_MAX\t\t\t\t\t\t\\\n+\t((int)(sizeof_field(struct af2swdev_notify_req, entry) /\t\\\n+\t       sizeof(struct fib_entry)))\n+\n+struct l3_entry {\n+\tstruct list_head list;\n+\t/* Always this AF driver's rvu; stored for clarity only (single RVU). */\n+\tstruct rvu *rvu;\n+\tu32 port_id;\n+\tint cnt;\n+\tstruct fib_entry entry[];\n+};\n+\n+static DEFINE_MUTEX(l3_offl_llock);\n+/*\n+ * Pending FIB updates from host PFs on this RVU. The list is per-AF-module\n+ * (one octeontx2-af instance / one RVU chip), not shared across RVU devices.\n+ */\n+static LIST_HEAD(l3_offl_lh);\n+\n+static struct workqueue_struct *sw_l3_offl_wq;\n+static void sw_l3_offl_work_handler(struct work_struct *work);\n+static DECLARE_DELAYED_WORK(l3_offl_work, sw_l3_offl_work_handler);\n+\n+/*\n+ * FIB offload to the switch ASIC: one octeontx2 AF driver instance, one\n+ * switch PF (switchdev), and one sw_l3_offl_wq per SoC.\n+ */\n+\n+static void rvu_sw_l3_drain_list(struct list_head *lh)\n+{\n+\tstruct l3_entry *entry;\n+\n+\twhile ((entry = list_first_entry_or_null(lh, struct l3_entry, list))) {\n+\t\tlist_del(\u0026entry-\u003elist);\n+\t\tkfree(entry);\n+\t}\n+}\n+\n+static void rvu_sw_l3_queue_work(void)\n+{\n+\tif (sw_l3_offl_wq)\n+\t\tqueue_delayed_work(sw_l3_offl_wq, \u0026l3_offl_work,\n+\t\t\t\t   msecs_to_jiffies(10));\n+}\n+\n+static int rvu_sw_l3_ensure_wq(void)\n+{\n+\tif (sw_l3_offl_wq)\n+\t\treturn 0;\n+\n+\tsw_l3_offl_wq = alloc_workqueue(\"sw_af_fib_wq\", 0, 0);\n+\tif (!sw_l3_offl_wq)\n+\t\treturn -ENOMEM;\n+\n+\treturn 0;\n+}\n+\n+static int rvu_sw_l3_offl_rule_push(struct list_head *lh)\n+{\n+\tstruct af2swdev_notify_req *req;\n+\tstruct fib_entry *entry, *dst;\n+\tstruct l3_entry *l3_entry;\n+\tstruct rvu *rvu;\n+\tint tot_cnt = 0;\n+\tint swdev_pf;\n+\tint sz, cnt, i;\n+\tbool rc;\n+\n+\tBUILD_BUG_ON(sizeof_field(struct af2swdev_notify_req, entry) !=\n+\t\t     sizeof(struct fib_entry) * RVU_SW_L3_BATCH_MAX);\n+\n+\tl3_entry = list_first_entry_or_null(lh, struct l3_entry, list);\n+\tif (!l3_entry)\n+\t\treturn 0;\n+\n+\t/*\n+\t * Octeontx2 has a single AF (one struct rvu) per RVU chip. All queued\n+\t * entries therefore share the same rvu and the same switch PF below.\n+\t * Host PF identity is carried per fib_entry (port_id), not by picking\n+\t * a different switch PF here.\n+\t */\n+\trvu = l3_entry-\u003ervu;\n+\tswdev_pf = rvu_get_pf(rvu-\u003epdev, rvu-\u003erswitch.pcifunc);\n+\n+\tmutex_lock(\u0026rvu-\u003embox_lock);\n+\treq = otx2_mbox_alloc_msg_af2swdev_notify(rvu, swdev_pf);\n+\tif (!req) {\n+\t\tmutex_unlock(\u0026rvu-\u003embox_lock);\n+\t\treturn -ENOMEM;\n+\t}\n+\n+\tdst = \u0026req-\u003eentry[0];\n+\t/*\n+\t * Batch fib_entry records from multiple host PF notifies into one\n+\t * af2swdev message. Safe on octeontx2: every l3_entry targets the\n+\t * same switch PF; egress port is encoded in each fib_entry.port_id.\n+\t */\n+\twhile ((l3_entry =\n+\t\tlist_first_entry_or_null(lh,\n+\t\t\t\t\t struct l3_entry, list)) != NULL) {\n+\t\tentry = l3_entry-\u003eentry;\n+\t\tcnt = l3_entry-\u003ecnt;\n+\n+\t\t/* af2swdev_notify_req.entry[] holds RVU_SW_L3_BATCH_MAX slots;\n+\t\t * stop before copying the next l3_entry when the mbox buffer\n+\t\t * would overflow. Leftovers stay on lh and are re-queued.\n+\t\t */\n+\t\tif (tot_cnt + cnt \u003e RVU_SW_L3_BATCH_MAX)\n+\t\t\tbreak;\n+\n+\t\tsz = sizeof(*entry) * cnt;\n+\n+\t\tmemcpy(dst, entry, sz);\n+\t\tfor (i = 0; i \u003c cnt; i++)\n+\t\t\tdst[i].port_id = l3_entry-\u003eport_id;\n+\t\ttot_cnt += cnt;\n+\t\tdst += cnt;\n+\n+\t\tlist_del_init(\u0026l3_entry-\u003elist);\n+\t\tkfree(l3_entry);\n+\t}\n+\tif (!tot_cnt) {\n+\t\tmutex_unlock(\u0026rvu-\u003embox_lock);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\treq-\u003eflags = FIB_CMD;\n+\treq-\u003ecnt = tot_cnt;\n+\n+\trc = otx2_mbox_wait_for_zero(\u0026rvu-\u003eafpf_wq_info.mbox_up, swdev_pf);\n+\tif (rc)\n+\t\totx2_mbox_msg_send_up(\u0026rvu-\u003eafpf_wq_info.mbox_up, swdev_pf);\n+\n+\tmutex_unlock(\u0026rvu-\u003embox_lock);\n+\treturn rc ? 0 : -EFAULT;\n+}\n+\n+static void sw_l3_offl_work_handler(struct work_struct *work)\n+{\n+\tstruct list_head l3lh;\n+\n+\tINIT_LIST_HEAD(\u0026l3lh);\n+\n+\tmutex_lock(\u0026l3_offl_llock);\n+\tif (list_empty(\u0026l3_offl_lh)) {\n+\t\tmutex_unlock(\u0026l3_offl_llock);\n+\t\treturn;\n+\t}\n+\tlist_splice_init(\u0026l3_offl_lh, \u0026l3lh);\n+\tmutex_unlock(\u0026l3_offl_llock);\n+\n+\tif (rvu_sw_l3_offl_rule_push(\u0026l3lh))\n+\t\tpr_err(\"%s: Error to push rules\\n\", __func__);\n+\n+\t/* rvu_sw_l3_offl_rule_push() may leave entries when a batch is full. */\n+\tif (!list_empty(\u0026l3lh)) {\n+\t\tmutex_lock(\u0026l3_offl_llock);\n+\t\tlist_splice_tail(\u0026l3lh, \u0026l3_offl_lh);\n+\t\tmutex_unlock(\u0026l3_offl_llock);\n+\t\tif (sw_l3_offl_wq)\n+\t\t\tqueue_delayed_work(sw_l3_offl_wq, \u0026l3_offl_work,\n+\t\t\t\t\t   msecs_to_jiffies(100));\n+\t\treturn;\n+\t}\n+\n+\tmutex_lock(\u0026l3_offl_llock);\n+\tif (!list_empty(\u0026l3_offl_lh))\n+\t\trvu_sw_l3_queue_work();\n+\tmutex_unlock(\u0026l3_offl_llock);\n+}\n+\n+int rvu_mbox_handler_fib_notify(struct rvu *rvu,\n+\t\t\t\tstruct fib_notify_req *req,\n+\t\t\t\tstruct msg_rsp *rsp)\n+{\n+\tstruct l3_entry *l3_entry;\n+\tint sz, rc;\n+\n+\tif (!(rvu-\u003erswitch.flags \u0026 RVU_SWITCH_FLAG_FW_READY))\n+\t\treturn -EAGAIN;\n+\n+\t/* Reject single notifies larger than af2swdev_notify_req.entry[]. */\n+\tif (!req-\u003ecnt || req-\u003ecnt \u003e RVU_SW_L3_BATCH_MAX)\n+\t\treturn -EINVAL;\n+\n+\tsz = req-\u003ecnt * sizeof(struct fib_entry);\n+\n+\tl3_entry = kcalloc(1, sizeof(*l3_entry) + sz, GFP_KERNEL);\n+\tif (!l3_entry)\n+\t\treturn -ENOMEM;\n+\n+\tl3_entry-\u003eport_id = rvu_sw_port_id(rvu, req-\u003ehdr.pcifunc);\n+\tl3_entry-\u003ervu = rvu;\n+\tl3_entry-\u003ecnt = req-\u003ecnt;\n+\tINIT_LIST_HEAD(\u0026l3_entry-\u003elist);\n+\tmemcpy(l3_entry-\u003eentry, req-\u003eentry, sz);\n+\n+\t/* Host PFs on this RVU share one AF and one switch PF offload path. */\n+\tmutex_lock(\u0026l3_offl_llock);\n+\trc = rvu_sw_l3_ensure_wq();\n+\tif (rc) {\n+\t\tmutex_unlock(\u0026l3_offl_llock);\n+\t\tkfree(l3_entry);\n+\t\treturn rc;\n+\t}\n+\n+\tlist_add_tail(\u0026l3_entry-\u003elist, \u0026l3_offl_lh);\n+\tif (sw_l3_offl_wq)\n+\t\trvu_sw_l3_queue_work();\n+\tmutex_unlock(\u0026l3_offl_llock);\n+\n+\treturn 0;\n+}\n+\n+void rvu_sw_l3_shutdown(void)\n+{\n+\tif (!sw_l3_offl_wq)\n+\t\treturn;\n+\n+\tcancel_delayed_work_sync(\u0026l3_offl_work);\n+\tdestroy_workqueue(sw_l3_offl_wq);\n+\tsw_l3_offl_wq = NULL;\n+\n+\tmutex_lock(\u0026l3_offl_llock);\n+\trvu_sw_l3_drain_list(\u0026l3_offl_lh);\n+\tmutex_unlock(\u0026l3_offl_llock);\n+}\ndiff --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\nnew file mode 100644\nindex 00000000000000..153f1415466deb\n--- /dev/null\n+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l3.h\n@@ -0,0 +1,12 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/* Marvell RVU Admin Function driver\n+ *\n+ * Copyright (C) 2026 Marvell.\n+ *\n+ */\n+\n+#ifndef RVU_SW_L3_H\n+#define RVU_SW_L3_H\n+\n+void rvu_sw_l3_shutdown(void);\n+#endif\ndiff --git a/drivers/net/ethernet/marvell/octeontx2/nic/Makefile b/drivers/net/ethernet/marvell/octeontx2/nic/Makefile\nindex 883e9f4d601c5c..e4deead6d1e00c 100644\n--- a/drivers/net/ethernet/marvell/octeontx2/nic/Makefile\n+++ b/drivers/net/ethernet/marvell/octeontx2/nic/Makefile\n@@ -9,7 +9,14 @@ obj-$(CONFIG_RVU_ESWITCH) += rvu_rep.o\n \n rvu_nicpf-y := otx2_pf.o otx2_common.o otx2_txrx.o otx2_ethtool.o \\\n                otx2_flows.o otx2_tc.o cn10k.o cn20k.o otx2_dmac_flt.o \\\n-               otx2_devlink.o qos_sq.o qos.o otx2_xsk.o\n+               otx2_devlink.o qos_sq.o qos.o otx2_xsk.o \\\n+\t       switch/sw_fdb.o switch/sw_fl.o switch/sw_trace.o\n+rvu_nicpf-$(CONFIG_OCTEONTX_SWITCH) += switch/sw_nb.o switch/sw_fib.o \\\n+\t\t\t\t       switch/sw_nb_v4.o\n+ifneq ($(CONFIG_IPV6),)\n+rvu_nicpf-$(CONFIG_OCTEONTX_SWITCH) += switch/sw_nb_v6.o\n+endif\n+\n rvu_nicvf-y := otx2_vf.o\n rvu_rep-y := rep.o\n \ndiff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c\nindex 2e33b33ec99341..0cd6049c637e1f 100644\n--- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c\n+++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c\n@@ -28,6 +28,7 @@\n #include \u003crvu_trace.h\u003e\n #include \"cn10k_ipsec.h\"\n #include \"otx2_xsk.h\"\n+#include \"switch/sw_nb.h\"\n \n #define DRV_NAME\t\"rvu_nicpf\"\n #define DRV_STRING\t\"Marvell RVU NIC Physical Function Driver\"\n@@ -993,6 +994,7 @@ static int otx2_process_mbox_msg_up(struct otx2_nic *pf,\n MBOX_UP_CGX_MESSAGES\n MBOX_UP_MCS_MESSAGES\n MBOX_UP_REP_MESSAGES\n+MBOX_UP_AF2PF_FDB_REFRESH_MESSAGES\n #undef M\n \t\tbreak;\n \tdefault:\ndiff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_txrx.h b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_txrx.h\nindex acf259d7200883..73a98b94426b04 100644\n--- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_txrx.h\n+++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_txrx.h\n@@ -78,6 +78,8 @@ struct otx2_rcv_queue {\n struct sg_list {\n \tu16\tnum_segs;\n \tu16\tflags;\n+\tu16\tcq_idx;\n+\tu16\tlen;\n \tu64\tskb;\n \tu64\tsize[OTX2_MAX_FRAGS_IN_SQE];\n \tu64\tdma_addr[OTX2_MAX_FRAGS_IN_SQE];\ndiff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c\nindex b022f52c684508..6f2fc4caf70ccd 100644\n--- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c\n+++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c\n@@ -9,6 +9,7 @@\n #include \u003clinux/module.h\u003e\n #include \u003clinux/pci.h\u003e\n #include \u003clinux/net_tstamp.h\u003e\n+#include \u003cnet/switchdev.h\u003e\n \n #include \"otx2_common.h\"\n #include \"otx2_reg.h\"\n@@ -114,6 +115,33 @@ static void otx2vf_vfaf_mbox_handler(struct work_struct *work)\n \t}\n }\n \n+#if IS_ENABLED(CONFIG_OCTEONTX_SWITCH)\n+static int otx2vf_mbox_af2pf_fdb_refresh(struct otx2_nic *vf,\n+\t\t\t\t\t struct af2pf_fdb_refresh_req *req,\n+\t\t\t\t\t struct msg_rsp *rsp)\n+{\n+\tstruct switchdev_notifier_fdb_info item = {0};\n+\n+\titem.addr = req-\u003emac;\n+\titem.info.dev = vf-\u003enetdev;\n+\tif (req-\u003eflags \u0026 FDB_DEL)\n+\t\tcall_switchdev_notifiers(SWITCHDEV_FDB_DEL_TO_BRIDGE,\n+\t\t\t\t\t item.info.dev, \u0026item.info, NULL);\n+\telse\n+\t\tcall_switchdev_notifiers(SWITCHDEV_FDB_ADD_TO_BRIDGE,\n+\t\t\t\t\t item.info.dev, \u0026item.info, NULL);\n+\n+\treturn 0;\n+}\n+#else\n+static int otx2vf_mbox_af2pf_fdb_refresh(struct otx2_nic *vf,\n+\t\t\t\t\t struct af2pf_fdb_refresh_req *req,\n+\t\t\t\t\t struct msg_rsp *rsp)\n+{\n+\treturn 0;\n+}\n+#endif\n+\n static int otx2vf_process_mbox_msg_up(struct otx2_nic *vf,\n \t\t\t\t      struct mbox_msghdr *req)\n {\n@@ -141,6 +169,22 @@ static int otx2vf_process_mbox_msg_up(struct otx2_nic *vf,\n \t\terr = otx2_mbox_up_handler_cgx_link_event(\n \t\t\t\tvf, (struct cgx_link_info_msg *)req, rsp);\n \t\treturn err;\n+\n+\tcase MBOX_MSG_AF2PF_FDB_REFRESH:\n+\t\trsp = (struct msg_rsp *)otx2_mbox_alloc_msg(\u0026vf-\u003embox.mbox_up, 0,\n+\t\t\t\t\t\t\t    sizeof(struct msg_rsp));\n+\t\tif (!rsp)\n+\t\t\treturn -ENOMEM;\n+\n+\t\trsp-\u003ehdr.id = MBOX_MSG_AF2PF_FDB_REFRESH;\n+\t\trsp-\u003ehdr.sig = OTX2_MBOX_RSP_SIG;\n+\t\trsp-\u003ehdr.pcifunc = req-\u003epcifunc;\n+\t\trsp-\u003ehdr.rc = 0;\n+\t\terr = otx2vf_mbox_af2pf_fdb_refresh(vf,\n+\t\t\t\t\t\t    (struct af2pf_fdb_refresh_req *)req,\n+\t\t\t\t\t\t    rsp);\n+\t\treturn err;\n+\n \tdefault:\n \t\totx2_reply_invalid_msg(\u0026vf-\u003embox.mbox_up, 0, 0, req-\u003eid);\n \t\treturn -ENODEV;\ndiff --git a/drivers/net/ethernet/marvell/octeontx2/nic/rep.c b/drivers/net/ethernet/marvell/octeontx2/nic/rep.c\nindex 0f5d5642d3f7a1..1900235fabc578 100644\n--- a/drivers/net/ethernet/marvell/octeontx2/nic/rep.c\n+++ b/drivers/net/ethernet/marvell/octeontx2/nic/rep.c\n@@ -15,6 +15,7 @@\n #include \"cn10k.h\"\n #include \"otx2_reg.h\"\n #include \"rep.h\"\n+#include \"switch/sw_nb.h\"\n \n #define DRV_NAME\t\"rvu_rep\"\n #define DRV_STRING\t\"Marvell RVU Representor Driver\"\n@@ -399,18 +400,55 @@ static void rvu_rep_get_stats64(struct net_device *dev,\n \n static int rvu_eswitch_config(struct otx2_nic *priv, u8 ena)\n {\n+#if IS_ENABLED(CONFIG_OCTEONTX_SWITCH)\n+\tstruct net_device *netdev = priv-\u003enetdev;\n+#endif\n+\tstruct devlink_port_attrs attrs = {};\n \tstruct esw_cfg_req *req;\n+\tint mbox_err;\n+#if IS_ENABLED(CONFIG_OCTEONTX_SWITCH)\n+\tint err;\n+#endif\n+\n+\trvu_rep_devlink_set_switch_id(priv, \u0026attrs.switch_id);\n+\n+#if IS_ENABLED(CONFIG_OCTEONTX_SWITCH)\n+\tif (ena) {\n+\t\terr = sw_nb_register(netdev);\n+\t\tif (err)\n+\t\t\treturn err;\n+\t}\n+#endif\n \n \tmutex_lock(\u0026priv-\u003embox.lock);\n \treq = otx2_mbox_alloc_msg_esw_cfg(\u0026priv-\u003embox);\n \tif (!req) {\n \t\tmutex_unlock(\u0026priv-\u003embox.lock);\n+#if IS_ENABLED(CONFIG_OCTEONTX_SWITCH)\n+\t\tif (ena)\n+\t\t\tsw_nb_unregister(netdev);\n+#endif\n \t\treturn -ENOMEM;\n \t}\n \treq-\u003eena = ena;\n-\totx2_sync_mbox_msg(\u0026priv-\u003embox);\n+\tmemcpy(req-\u003eswitch_id, attrs.switch_id.id, attrs.switch_id.id_len);\n+\tmbox_err = otx2_sync_mbox_msg(\u0026priv-\u003embox);\n \tmutex_unlock(\u0026priv-\u003embox.lock);\n-\treturn 0;\n+\n+#if IS_ENABLED(CONFIG_OCTEONTX_SWITCH)\n+\tif (ena \u0026\u0026 mbox_err) {\n+\t\tsw_nb_unregister(netdev);\n+\t\treturn mbox_err;\n+\t}\n+\n+\tif (!ena) {\n+\t\terr = sw_nb_unregister(netdev);\n+\t\tif (err \u0026\u0026 !mbox_err)\n+\t\t\treturn err;\n+\t}\n+#endif\n+\n+\treturn mbox_err;\n }\n \n static netdev_tx_t rvu_rep_xmit(struct sk_buff *skb, struct net_device *dev)\ndiff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.c b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.c\nnew file mode 100644\nindex 00000000000000..eb5e2ce44ca207\n--- /dev/null\n+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.c\n@@ -0,0 +1,234 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/* Marvell RVU switch driver\n+ *\n+ * Copyright (C) 2026 Marvell.\n+ *\n+ */\n+#include \u003clinux/kernel.h\u003e\n+#include \u003clinux/netdevice.h\u003e\n+#include \u003clinux/etherdevice.h\u003e\n+#include \u003cnet/switchdev.h\u003e\n+#include \u003cnet/netevent.h\u003e\n+#include \u003cnet/arp.h\u003e\n+\n+#include \"../otx2_reg.h\"\n+#include \"../otx2_common.h\"\n+#include \"../otx2_struct.h\"\n+#include \"../cn10k.h\"\n+#include \"sw_nb.h\"\n+#include \"sw_fdb.h\"\n+\n+#if !IS_ENABLED(CONFIG_OCTEONTX_SWITCH)\n+\n+int otx2_mbox_up_handler_af2pf_fdb_refresh(struct otx2_nic *pf,\n+\t\t\t\t\t   struct af2pf_fdb_refresh_req *req,\n+\t\t\t\t\t   struct msg_rsp *rsp)\n+{\n+\treturn 0;\n+}\n+\n+#else\n+\n+#define SW_FDB_LIST_MAX 4096\n+\n+static DEFINE_SPINLOCK(sw_fdb_llock);\n+static LIST_HEAD(sw_fdb_lh);\n+static atomic_t sw_fdb_list_cnt = ATOMIC_INIT(0);\n+\n+struct sw_fdb_list_entry {\n+\tstruct list_head list;\n+\tu64 flags;\n+\tstruct otx2_nic *pf;\n+\tnetdevice_tracker dev_tracker;\n+\tu8  mac[ETH_ALEN];\n+\tbool add_fdb;\n+};\n+\n+static struct workqueue_struct *sw_fdb_wq;\n+static struct work_struct sw_fdb_work;\n+\n+static void sw_fdb_list_cnt_warn(struct net_device *netdev)\n+{\n+\tint n = atomic_read(\u0026sw_fdb_list_cnt);\n+\n+\tif (n \u003c 0)\n+\t\tnetdev_warn(netdev, \"FDB list count underflow: %d\\n\", n);\n+\telse if (n \u003e SW_FDB_LIST_MAX)\n+\t\tnetdev_warn(netdev, \"FDB list count overflow: %d (max %d)\\n\",\n+\t\t\t    n, SW_FDB_LIST_MAX);\n+}\n+\n+static int sw_fdb_list_count(void)\n+{\n+\treturn atomic_read(\u0026sw_fdb_list_cnt);\n+}\n+\n+static void sw_fdb_list_cnt_inc(struct net_device *netdev)\n+{\n+\tatomic_inc(\u0026sw_fdb_list_cnt);\n+\tsw_fdb_list_cnt_warn(netdev);\n+}\n+\n+static void sw_fdb_list_cnt_dec(struct net_device *netdev)\n+{\n+\tatomic_dec(\u0026sw_fdb_list_cnt);\n+\tsw_fdb_list_cnt_warn(netdev);\n+}\n+\n+static int sw_fdb_add_or_del(struct otx2_nic *pf,\n+\t\t\t     const unsigned char *addr,\n+\t\t\t     bool add_fdb)\n+{\n+\tstruct fdb_notify_req *req;\n+\tint rc;\n+\n+\tmutex_lock(\u0026pf-\u003embox.lock);\n+\treq = otx2_mbox_alloc_msg_fdb_notify(\u0026pf-\u003embox);\n+\tif (!req) {\n+\t\trc = -ENOMEM;\n+\t\tgoto out;\n+\t}\n+\n+\tether_addr_copy(req-\u003emac, addr);\n+\treq-\u003eflags = add_fdb ? FDB_ADD : FDB_DEL;\n+\n+\trc = otx2_sync_mbox_msg(\u0026pf-\u003embox);\n+out:\n+\tmutex_unlock(\u0026pf-\u003embox.lock);\n+\treturn rc;\n+}\n+\n+static void sw_fdb_wq_handler(struct work_struct *work)\n+{\n+\tstruct sw_fdb_list_entry *entry;\n+\tstruct workqueue_struct *wq;\n+\tLIST_HEAD(tlist);\n+\n+\tspin_lock_bh(\u0026sw_fdb_llock);\n+\tlist_splice_init(\u0026sw_fdb_lh, \u0026tlist);\n+\tspin_unlock_bh(\u0026sw_fdb_llock);\n+\n+\twhile ((entry =\n+\t\tlist_first_entry_or_null(\u0026tlist,\n+\t\t\t\t\t struct sw_fdb_list_entry,\n+\t\t\t\t\t list)) != NULL) {\n+\t\tlist_del_init(\u0026entry-\u003elist);\n+\t\tsw_fdb_list_cnt_dec(entry-\u003epf-\u003enetdev);\n+\t\tif (sw_fdb_add_or_del(entry-\u003epf, entry-\u003emac, entry-\u003eadd_fdb))\n+\t\t\tnetdev_err(entry-\u003epf-\u003enetdev,\n+\t\t\t\t   \"Error to add/del fdb %pM entry\\n\",\n+\t\t\t\t   entry-\u003emac);\n+\t\tnetdev_put(entry-\u003epf-\u003enetdev, \u0026entry-\u003edev_tracker);\n+\t\tkfree(entry);\n+\t}\n+\n+\tspin_lock_bh(\u0026sw_fdb_llock);\n+\twq = sw_fdb_wq;\n+\tif (wq \u0026\u0026 !list_empty(\u0026sw_fdb_lh))\n+\t\tqueue_work(wq, \u0026sw_fdb_work);\n+\tspin_unlock_bh(\u0026sw_fdb_llock);\n+}\n+\n+int sw_fdb_add_to_list(struct net_device *dev, u8 *mac, bool add_fdb)\n+{\n+\tstruct otx2_nic *pf = netdev_priv(dev);\n+\tstruct sw_fdb_list_entry *entry;\n+\tstruct workqueue_struct *wq;\n+\n+\tspin_lock_bh(\u0026sw_fdb_llock);\n+\tif (!sw_fdb_wq) {\n+\t\tspin_unlock_bh(\u0026sw_fdb_llock);\n+\t\treturn -EINVAL;\n+\t}\n+\tspin_unlock_bh(\u0026sw_fdb_llock);\n+\n+\tif (sw_fdb_list_count() \u003e= SW_FDB_LIST_MAX)\n+\t\treturn -ENOMEM;\n+\n+\tentry = kcalloc(1, sizeof(*entry), GFP_ATOMIC);\n+\tif (!entry)\n+\t\treturn -ENOMEM;\n+\n+\tether_addr_copy(entry-\u003emac, mac);\n+\tentry-\u003eadd_fdb = add_fdb;\n+\tentry-\u003epf = pf;\n+\tnetdev_hold(dev, \u0026entry-\u003edev_tracker, GFP_ATOMIC);\n+\n+\tspin_lock_bh(\u0026sw_fdb_llock);\n+\twq = sw_fdb_wq;\n+\tif (wq) {\n+\t\tlist_add_tail(\u0026entry-\u003elist, \u0026sw_fdb_lh);\n+\t\tsw_fdb_list_cnt_inc(dev);\n+\t\tqueue_work(wq, \u0026sw_fdb_work);\n+\t}\n+\tspin_unlock_bh(\u0026sw_fdb_llock);\n+\n+\tif (!wq) {\n+\t\tnetdev_put(dev, \u0026entry-\u003edev_tracker);\n+\t\tkfree(entry);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int sw_fdb_init(void)\n+{\n+\tINIT_WORK(\u0026sw_fdb_work, sw_fdb_wq_handler);\n+\tsw_fdb_wq = alloc_workqueue(\"sw_fdb_wq\", 0, 0);\n+\tif (!sw_fdb_wq)\n+\t\treturn -ENOMEM;\n+\n+\treturn 0;\n+}\n+\n+void sw_fdb_deinit(void)\n+{\n+\tstruct sw_fdb_list_entry *entry;\n+\tstruct workqueue_struct *wq;\n+\tLIST_HEAD(tlist);\n+\n+\tspin_lock_bh(\u0026sw_fdb_llock);\n+\twq = sw_fdb_wq;\n+\tsw_fdb_wq = NULL;\n+\tspin_unlock_bh(\u0026sw_fdb_llock);\n+\n+\tif (!wq)\n+\t\treturn;\n+\n+\tcancel_work_sync(\u0026sw_fdb_work);\n+\tdestroy_workqueue(wq);\n+\n+\tspin_lock_bh(\u0026sw_fdb_llock);\n+\tlist_splice_init(\u0026sw_fdb_lh, \u0026tlist);\n+\tspin_unlock_bh(\u0026sw_fdb_llock);\n+\n+\twhile ((entry =\n+\t\tlist_first_entry_or_null(\u0026tlist,\n+\t\t\t\t\t struct sw_fdb_list_entry,\n+\t\t\t\t\t list)) != NULL) {\n+\t\tlist_del_init(\u0026entry-\u003elist);\n+\t\tsw_fdb_list_cnt_dec(entry-\u003epf-\u003enetdev);\n+\t\tnetdev_put(entry-\u003epf-\u003enetdev, \u0026entry-\u003edev_tracker);\n+\t\tkfree(entry);\n+\t}\n+}\n+\n+int otx2_mbox_up_handler_af2pf_fdb_refresh(struct otx2_nic *pf,\n+\t\t\t\t\t   struct af2pf_fdb_refresh_req *req,\n+\t\t\t\t\t   struct msg_rsp *rsp)\n+{\n+\tstruct switchdev_notifier_fdb_info item = {0};\n+\n+\titem.addr = req-\u003emac;\n+\titem.info.dev = pf-\u003enetdev;\n+\tif (req-\u003eflags \u0026 FDB_DEL)\n+\t\tcall_switchdev_notifiers(SWITCHDEV_FDB_DEL_TO_BRIDGE,\n+\t\t\t\t\t item.info.dev, \u0026item.info, NULL);\n+\telse\n+\t\tcall_switchdev_notifiers(SWITCHDEV_FDB_ADD_TO_BRIDGE,\n+\t\t\t\t\t item.info.dev, \u0026item.info, NULL);\n+\n+\treturn 0;\n+}\n+#endif\ndiff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.h b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.h\nnew file mode 100644\nindex 00000000000000..3b06a77e6b56ae\n--- /dev/null\n+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.h\n@@ -0,0 +1,14 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/* Marvell switch driver\n+ *\n+ * Copyright (C) 2026 Marvell.\n+ *\n+ */\n+#ifndef SW_FDB_H_\n+#define SW_FDB_H_\n+\n+int sw_fdb_add_to_list(struct net_device *dev, u8 *mac, bool add_fdb);\n+void sw_fdb_deinit(void);\n+int sw_fdb_init(void);\n+\n+#endif // SW_FDB_H\ndiff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fib.c b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fib.c\nnew file mode 100644\nindex 00000000000000..1c1f23d5d1364d\n--- /dev/null\n+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fib.c\n@@ -0,0 +1,205 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/* Marvell RVU switch driver\n+ *\n+ * Copyright (C) 2026 Marvell.\n+ *\n+ */\n+#include \u003clinux/kernel.h\u003e\n+#include \u003clinux/netdevice.h\u003e\n+#include \u003clinux/etherdevice.h\u003e\n+#include \u003cnet/switchdev.h\u003e\n+#include \u003cnet/netevent.h\u003e\n+#include \u003cnet/arp.h\u003e\n+#include \u003cnet/route.h\u003e\n+\n+#include \"../otx2_reg.h\"\n+#include \"../otx2_common.h\"\n+#include \"../otx2_struct.h\"\n+#include \"../cn10k.h\"\n+#include \"sw_nb.h\"\n+#include \"sw_fib.h\"\n+\n+#if IS_ENABLED(CONFIG_OCTEONTX_SWITCH)\n+\n+#define SW_FIB_BATCH_MAX 16\n+\n+/*\n+ * One switch PF registers notifiers via sw_nb_register(); a second call\n+ * returns -EBUSY. A single sw_fib_wq therefore serves the one switchdev\n+ * instance on octeontx2, matching the FDB offload path.\n+ */\n+static DEFINE_SPINLOCK(sw_fib_llock);\n+static LIST_HEAD(sw_fib_lh);\n+\n+static struct workqueue_struct *sw_fib_wq;\n+static void sw_fib_work_handler(struct work_struct *work);\n+static DECLARE_DELAYED_WORK(sw_fib_work, sw_fib_work_handler);\n+\n+struct sw_fib_list_entry {\n+\tstruct list_head lh;\n+\tstruct otx2_nic *pf;\n+\tnetdevice_tracker dev_tracker;\n+\tint cnt;\n+\tstruct fib_entry *entry;\n+};\n+\n+static int sw_fib_notify(struct otx2_nic *pf,\n+\t\t\t int cnt,\n+\t\t\t struct fib_entry *entry)\n+{\n+\tstruct fib_notify_req *req;\n+\tint rc;\n+\n+\tif (cnt \u003e SW_FIB_BATCH_MAX)\n+\t\treturn -EINVAL;\n+\n+\tmutex_lock(\u0026pf-\u003embox.lock);\n+\treq = otx2_mbox_alloc_msg_fib_notify(\u0026pf-\u003embox);\n+\tif (!req) {\n+\t\trc = -ENOMEM;\n+\t\tgoto out;\n+\t}\n+\n+\treq-\u003ecnt = cnt;\n+\tmemcpy(req-\u003eentry, entry, sizeof(*entry) * cnt);\n+\n+\trc = otx2_sync_mbox_msg(\u0026pf-\u003embox);\n+out:\n+\tmutex_unlock(\u0026pf-\u003embox.lock);\n+\treturn rc;\n+}\n+\n+static void sw_fib_work_handler(struct work_struct *work)\n+{\n+\tstruct sw_fib_list_entry *lentry;\n+\tLIST_HEAD(tlist);\n+\n+\tspin_lock_bh(\u0026sw_fib_llock);\n+\tlist_splice_init(\u0026sw_fib_lh, \u0026tlist);\n+\tspin_unlock_bh(\u0026sw_fib_llock);\n+\n+\twhile ((lentry =\n+\t\tlist_first_entry_or_null(\u0026tlist,\n+\t\t\t\t\t struct sw_fib_list_entry, lh)) != NULL) {\n+\t\tlist_del_init(\u0026lentry-\u003elh);\n+\t\tif (sw_fib_notify(lentry-\u003epf, lentry-\u003ecnt, lentry-\u003eentry)) {\n+\t\t\tnetdev_err(lentry-\u003epf-\u003enetdev,\n+\t\t\t\t   \"Failed to notify FIB update to AF, will retry\\n\");\n+\t\t\tspin_lock_bh(\u0026sw_fib_llock);\n+\t\t\tif (sw_fib_wq) {\n+\t\t\t\tlist_add_tail(\u0026lentry-\u003elh, \u0026sw_fib_lh);\n+\t\t\t\tqueue_delayed_work(sw_fib_wq, \u0026sw_fib_work,\n+\t\t\t\t\t\t   msecs_to_jiffies(100));\n+\t\t\t\tspin_unlock_bh(\u0026sw_fib_llock);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\tspin_unlock_bh(\u0026sw_fib_llock);\n+\t\t\tnetdev_put(lentry-\u003epf-\u003enetdev, \u0026lentry-\u003edev_tracker);\n+\t\t\tkfree(lentry-\u003eentry);\n+\t\t\tkfree(lentry);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tnetdev_put(lentry-\u003epf-\u003enetdev, \u0026lentry-\u003edev_tracker);\n+\t\tkfree(lentry-\u003eentry);\n+\t\tkfree(lentry);\n+\t}\n+\n+\tspin_lock_bh(\u0026sw_fib_llock);\n+\tif (!list_empty(\u0026sw_fib_lh) \u0026\u0026 sw_fib_wq)\n+\t\tqueue_delayed_work(sw_fib_wq, \u0026sw_fib_work,\n+\t\t\t\t   msecs_to_jiffies(10));\n+\tspin_unlock_bh(\u0026sw_fib_llock);\n+}\n+\n+int sw_fib_add_to_list(struct net_device *dev,\n+\t\t       struct fib_entry *entry, int cnt)\n+{\n+\tstruct otx2_nic *pf = netdev_priv(dev);\n+\tstruct sw_fib_list_entry *lentry;\n+\tstruct workqueue_struct *wq;\n+\n+\tif (cnt \u003c= 0 || cnt \u003e SW_FIB_BATCH_MAX) {\n+\t\tkfree(entry);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tspin_lock_bh(\u0026sw_fib_llock);\n+\tif (!sw_fib_wq) {\n+\t\tspin_unlock_bh(\u0026sw_fib_llock);\n+\t\tkfree(entry);\n+\t\treturn -EINVAL;\n+\t}\n+\tspin_unlock_bh(\u0026sw_fib_llock);\n+\n+\tlentry = kcalloc(1, sizeof(*lentry), GFP_ATOMIC);\n+\tif (!lentry) {\n+\t\tkfree(entry);\n+\t\treturn -ENOMEM;\n+\t}\n+\n+\tlentry-\u003epf = pf;\n+\tlentry-\u003ecnt = cnt;\n+\tlentry-\u003eentry = entry;\n+\tINIT_LIST_HEAD(\u0026lentry-\u003elh);\n+\tnetdev_hold(dev, \u0026lentry-\u003edev_tracker, GFP_ATOMIC);\n+\n+\tspin_lock_bh(\u0026sw_fib_llock);\n+\twq = sw_fib_wq;\n+\tif (wq) {\n+\t\tlist_add_tail(\u0026lentry-\u003elh, \u0026sw_fib_lh);\n+\t\tqueue_delayed_work(wq, \u0026sw_fib_work,\n+\t\t\t\t   msecs_to_jiffies(10));\n+\t}\n+\tspin_unlock_bh(\u0026sw_fib_llock);\n+\n+\tif (!wq) {\n+\t\tnetdev_put(dev, \u0026lentry-\u003edev_tracker);\n+\t\tkfree(lentry);\n+\t\tkfree(entry);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int sw_fib_init(void)\n+{\n+\tsw_fib_wq = alloc_workqueue(\"sw_pf_fib_wq\", 0, 0);\n+\tif (!sw_fib_wq)\n+\t\treturn -ENOMEM;\n+\n+\treturn 0;\n+}\n+\n+void sw_fib_deinit(void)\n+{\n+\tstruct sw_fib_list_entry *lentry;\n+\tstruct workqueue_struct *wq;\n+\tLIST_HEAD(tlist);\n+\n+\tspin_lock_bh(\u0026sw_fib_llock);\n+\twq = sw_fib_wq;\n+\tsw_fib_wq = NULL;\n+\tspin_unlock_bh(\u0026sw_fib_llock);\n+\n+\tif (!wq)\n+\t\treturn;\n+\n+\tcancel_delayed_work_sync(\u0026sw_fib_work);\n+\tdestroy_workqueue(wq);\n+\n+\tspin_lock_bh(\u0026sw_fib_llock);\n+\tlist_splice_init(\u0026sw_fib_lh, \u0026tlist);\n+\tspin_unlock_bh(\u0026sw_fib_llock);\n+\n+\twhile ((lentry =\n+\t\tlist_first_entry_or_null(\u0026tlist,\n+\t\t\t\t\t struct sw_fib_list_entry, lh)) != NULL) {\n+\t\tlist_del_init(\u0026lentry-\u003elh);\n+\t\tnetdev_put(lentry-\u003epf-\u003enetdev, \u0026lentry-\u003edev_tracker);\n+\t\tkfree(lentry-\u003eentry);\n+\t\tkfree(lentry);\n+\t}\n+}\n+\n+#endif /* CONFIG_OCTEONTX_SWITCH */\ndiff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fib.h b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fib.h\nnew file mode 100644\nindex 00000000000000..2e0e7a80c40ce5\n--- /dev/null\n+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fib.h\n@@ -0,0 +1,30 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/* Marvell switch driver\n+ *\n+ * Copyright (C) 2026 Marvell.\n+ *\n+ */\n+#ifndef SW_FIB_H_\n+#define SW_FIB_H_\n+\n+#include \u003clinux/kconfig.h\u003e\n+\n+struct fib_entry;\n+struct net_device;\n+\n+#if IS_ENABLED(CONFIG_OCTEONTX_SWITCH)\n+int sw_fib_add_to_list(struct net_device *dev,\n+\t\t       struct fib_entry *entry, int cnt);\n+void sw_fib_deinit(void);\n+int sw_fib_init(void);\n+#else\n+static inline int sw_fib_add_to_list(struct net_device *dev,\n+\t\t\t\t     struct fib_entry *entry, int cnt)\n+{\n+\treturn 0;\n+}\n+static inline void sw_fib_deinit(void) {}\n+static inline int sw_fib_init(void) { return 0; }\n+#endif\n+\n+#endif /* SW_FIB_H_ */\ndiff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.c b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.c\nnew file mode 100644\nindex 00000000000000..f4366cb9735328\n--- /dev/null\n+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.c\n@@ -0,0 +1,766 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/* Marvell RVU switch driver\n+ *\n+ * Copyright (C) 2026 Marvell.\n+ *\n+ */\n+#include \u003clinux/kernel.h\u003e\n+#include \u003clinux/netdevice.h\u003e\n+#include \u003clinux/etherdevice.h\u003e\n+#include \u003clinux/mutex.h\u003e\n+#include \u003clinux/refcount.h\u003e\n+#include \u003cnet/switchdev.h\u003e\n+#include \u003cnet/netevent.h\u003e\n+#include \u003cnet/arp.h\u003e\n+#include \u003cnet/nexthop.h\u003e\n+#include \u003cnet/netfilter/nf_flow_table.h\u003e\n+\n+#include \"../otx2_reg.h\"\n+#include \"../otx2_common.h\"\n+#include \"../otx2_struct.h\"\n+#include \"../cn10k.h\"\n+#include \"sw_nb.h\"\n+#include \"sw_trace.h\"\n+#include \"sw_fl.h\"\n+\n+#if !IS_ENABLED(CONFIG_OCTEONTX_SWITCH)\n+int sw_fl_setup_ft_block_ingress_cb(enum tc_setup_type type,\n+\t\t\t\t    void *type_data, void *cb_priv)\n+{\n+\treturn -EOPNOTSUPP;\n+}\n+\n+#else\n+\n+static DEFINE_SPINLOCK(sw_fl_lock);\n+static LIST_HEAD(sw_fl_lh);\n+\n+struct sw_fl_list_entry {\n+\tstruct list_head list;\n+\tu64 flags;\n+\tunsigned long cookie;\n+\tstruct otx2_nic *pf;\n+\tnetdevice_tracker dev_tracker;\n+\tstruct fl_tuple tuple;\n+};\n+\n+static struct workqueue_struct *sw_fl_wq;\n+static struct work_struct sw_fl_work;\n+\n+struct sw_fl_ct_cb {\n+\tstruct list_head list;\n+\tstruct nf_flowtable *ft;\n+\tstruct otx2_nic *nic;\n+\trefcount_t ref;\n+};\n+\n+struct sw_fl_ct_cookie {\n+\tstruct list_head list;\n+\tunsigned long cookie;\n+\tstruct nf_flowtable *ft;\n+\tstruct otx2_nic *nic;\n+};\n+\n+static LIST_HEAD(sw_fl_ct_cb_list);\n+static DEFINE_MUTEX(sw_fl_ct_cb_lock);\n+static LIST_HEAD(sw_fl_ct_cookie_list);\n+static DEFINE_MUTEX(sw_fl_ct_cookie_lock);\n+\n+static struct sw_fl_ct_cb *sw_fl_ct_cb_find(struct nf_flowtable *ft,\n+\t\t\t\t\t    struct otx2_nic *nic)\n+{\n+\tstruct sw_fl_ct_cb *entry;\n+\n+\tlist_for_each_entry(entry, \u0026sw_fl_ct_cb_list, list) {\n+\t\tif (entry-\u003eft == ft \u0026\u0026 entry-\u003enic == nic)\n+\t\t\treturn entry;\n+\t}\n+\n+\treturn NULL;\n+}\n+\n+static int sw_fl_ct_cb_get(struct nf_flowtable *ft, struct otx2_nic *nic)\n+{\n+\tstruct sw_fl_ct_cb *entry;\n+\tint err;\n+\n+\tmutex_lock(\u0026sw_fl_ct_cb_lock);\n+\tentry = sw_fl_ct_cb_find(ft, nic);\n+\tif (entry) {\n+\t\trefcount_inc(\u0026entry-\u003eref);\n+\t\tmutex_unlock(\u0026sw_fl_ct_cb_lock);\n+\t\treturn 0;\n+\t}\n+\n+\terr = nf_flow_table_offload_add_cb(ft, sw_fl_setup_ft_block_ingress_cb, nic);\n+\tif (err \u0026\u0026 err != -EEXIST) {\n+\t\tmutex_unlock(\u0026sw_fl_ct_cb_lock);\n+\t\treturn err;\n+\t}\n+\n+\tentry = kzalloc(sizeof(*entry), GFP_KERNEL);\n+\tif (!entry) {\n+\t\tif (!err)\n+\t\t\tnf_flow_table_offload_del_cb(ft,\n+\t\t\t\t\t\t     sw_fl_setup_ft_block_ingress_cb,\n+\t\t\t\t\t\t     nic);\n+\t\tmutex_unlock(\u0026sw_fl_ct_cb_lock);\n+\t\treturn -ENOMEM;\n+\t}\n+\n+\tentry-\u003eft = ft;\n+\tentry-\u003enic = nic;\n+\trefcount_set(\u0026entry-\u003eref, 1);\n+\tlist_add_tail(\u0026entry-\u003elist, \u0026sw_fl_ct_cb_list);\n+\tmutex_unlock(\u0026sw_fl_ct_cb_lock);\n+\n+\treturn 0;\n+}\n+\n+static void sw_fl_ct_cb_put(struct nf_flowtable *ft, struct otx2_nic *nic)\n+{\n+\tstruct sw_fl_ct_cb *entry;\n+\n+\tmutex_lock(\u0026sw_fl_ct_cb_lock);\n+\tentry = sw_fl_ct_cb_find(ft, nic);\n+\tif (!entry || !refcount_dec_and_test(\u0026entry-\u003eref)) {\n+\t\tmutex_unlock(\u0026sw_fl_ct_cb_lock);\n+\t\treturn;\n+\t}\n+\n+\tlist_del(\u0026entry-\u003elist);\n+\tmutex_unlock(\u0026sw_fl_ct_cb_lock);\n+\n+\tnf_flow_table_offload_del_cb(ft, sw_fl_setup_ft_block_ingress_cb, nic);\n+\tkfree(entry);\n+}\n+\n+static void sw_fl_ct_cookie_add(unsigned long cookie, struct nf_flowtable *ft,\n+\t\t\t\tstruct otx2_nic *nic)\n+{\n+\tstruct sw_fl_ct_cookie *entry;\n+\n+\tentry = kzalloc(sizeof(*entry), GFP_KERNEL);\n+\tif (!entry)\n+\t\treturn;\n+\n+\tentry-\u003ecookie = cookie;\n+\tentry-\u003eft = ft;\n+\tentry-\u003enic = nic;\n+\n+\tmutex_lock(\u0026sw_fl_ct_cookie_lock);\n+\tlist_add_tail(\u0026entry-\u003elist, \u0026sw_fl_ct_cookie_list);\n+\tmutex_unlock(\u0026sw_fl_ct_cookie_lock);\n+}\n+\n+static void sw_fl_ct_cookie_put(unsigned long cookie)\n+{\n+\tstruct sw_fl_ct_cookie *entry, *tmp;\n+\n+\tmutex_lock(\u0026sw_fl_ct_cookie_lock);\n+\tlist_for_each_entry_safe(entry, tmp, \u0026sw_fl_ct_cookie_list, list) {\n+\t\tif (entry-\u003ecookie != cookie)\n+\t\t\tcontinue;\n+\n+\t\tlist_del(\u0026entry-\u003elist);\n+\t\tmutex_unlock(\u0026sw_fl_ct_cookie_lock);\n+\t\tsw_fl_ct_cb_put(entry-\u003eft, entry-\u003enic);\n+\t\tkfree(entry);\n+\t\treturn;\n+\t}\n+\tmutex_unlock(\u0026sw_fl_ct_cookie_lock);\n+}\n+\n+static void sw_fl_ct_cb_flush(void)\n+{\n+\tstruct sw_fl_ct_cookie *cookie, *ctmp;\n+\tstruct sw_fl_ct_cb *entry, *etmp;\n+\n+\tmutex_lock(\u0026sw_fl_ct_cookie_lock);\n+\tlist_for_each_entry_safe(cookie, ctmp, \u0026sw_fl_ct_cookie_list, list) {\n+\t\tlist_del(\u0026cookie-\u003elist);\n+\t\tkfree(cookie);\n+\t}\n+\tmutex_unlock(\u0026sw_fl_ct_cookie_lock);\n+\n+\tmutex_lock(\u0026sw_fl_ct_cb_lock);\n+\tlist_for_each_entry_safe(entry, etmp, \u0026sw_fl_ct_cb_list, list) {\n+\t\tlist_del(\u0026entry-\u003elist);\n+\t\tnf_flow_table_offload_del_cb(entry-\u003eft,\n+\t\t\t\t\t     sw_fl_setup_ft_block_ingress_cb,\n+\t\t\t\t\t     entry-\u003enic);\n+\t\tkfree(entry);\n+\t}\n+\tmutex_unlock(\u0026sw_fl_ct_cb_lock);\n+}\n+\n+static int sw_fl_msg_send(struct otx2_nic *pf,\n+\t\t\t  struct fl_tuple *tuple,\n+\t\t\t  u64 flags,\n+\t\t\t  unsigned long cookie)\n+{\n+\tstruct fl_notify_req *req;\n+\tint rc;\n+\n+\tmutex_lock(\u0026pf-\u003embox.lock);\n+\treq = otx2_mbox_alloc_msg_fl_notify(\u0026pf-\u003embox);\n+\tif (!req) {\n+\t\trc = -ENOMEM;\n+\t\tgoto out;\n+\t}\n+\n+\treq-\u003etuple = *tuple;\n+\treq-\u003eflags = flags;\n+\treq-\u003ecookie = cookie;\n+\n+\trc = otx2_sync_mbox_msg(\u0026pf-\u003embox);\n+out:\n+\tmutex_unlock(\u0026pf-\u003embox.lock);\n+\treturn rc;\n+}\n+\n+static void sw_fl_wq_handler(struct work_struct *work)\n+{\n+\tstruct sw_fl_list_entry *entry;\n+\tLIST_HEAD(tlist);\n+\n+\tspin_lock_bh(\u0026sw_fl_lock);\n+\tlist_splice_init(\u0026sw_fl_lh, \u0026tlist);\n+\tspin_unlock_bh(\u0026sw_fl_lock);\n+\n+\twhile ((entry =\n+\t\tlist_first_entry_or_null(\u0026tlist,\n+\t\t\t\t\t struct sw_fl_list_entry,\n+\t\t\t\t\t list)) != NULL) {\n+\t\tlist_del_init(\u0026entry-\u003elist);\n+\t\tif (sw_fl_msg_send(entry-\u003epf, \u0026entry-\u003etuple,\n+\t\t\t\t   entry-\u003eflags, entry-\u003ecookie)) {\n+\t\t\tnetdev_err(entry-\u003epf-\u003enetdev,\n+\t\t\t\t   \"Failed to notify flow update to AF, will retry\\n\");\n+\t\t\tspin_lock_bh(\u0026sw_fl_lock);\n+\t\t\tif (sw_fl_wq) {\n+\t\t\t\tlist_add_tail(\u0026entry-\u003elist, \u0026sw_fl_lh);\n+\t\t\t\tqueue_work(sw_fl_wq, \u0026sw_fl_work);\n+\t\t\t\tspin_unlock_bh(\u0026sw_fl_lock);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\tspin_unlock_bh(\u0026sw_fl_lock);\n+\t\t\tnetdev_put(entry-\u003epf-\u003enetdev, \u0026entry-\u003edev_tracker);\n+\t\t\tkfree(entry);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tnetdev_put(entry-\u003epf-\u003enetdev, \u0026entry-\u003edev_tracker);\n+\t\tkfree(entry);\n+\t}\n+\n+\tspin_lock_bh(\u0026sw_fl_lock);\n+\tif (!list_empty(\u0026sw_fl_lh) \u0026\u0026 sw_fl_wq)\n+\t\tqueue_work(sw_fl_wq, \u0026sw_fl_work);\n+\tspin_unlock_bh(\u0026sw_fl_lock);\n+}\n+\n+static int\n+sw_fl_add_to_list(struct otx2_nic *pf, struct fl_tuple *tuple,\n+\t\t  unsigned long cookie, bool add_fl)\n+{\n+\tstruct sw_fl_list_entry *entry;\n+\n+\tentry = kcalloc(1, sizeof(*entry), GFP_ATOMIC);\n+\tif (!entry)\n+\t\treturn -ENOMEM;\n+\n+\tentry-\u003epf = pf;\n+\tentry-\u003eflags = add_fl ? FL_ADD : FL_DEL;\n+\tif (add_fl)\n+\t\tentry-\u003etuple = *tuple;\n+\tentry-\u003ecookie = cookie;\n+\tentry-\u003etuple.uni_di = netif_is_ovs_port(pf-\u003enetdev);\n+\n+\tspin_lock_bh(\u0026sw_fl_lock);\n+\tif (!sw_fl_wq) {\n+\t\tspin_unlock_bh(\u0026sw_fl_lock);\n+\t\tkfree(entry);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tnetdev_hold(pf-\u003enetdev, \u0026entry-\u003edev_tracker, GFP_ATOMIC);\n+\tlist_add_tail(\u0026entry-\u003elist, \u0026sw_fl_lh);\n+\tqueue_work(sw_fl_wq, \u0026sw_fl_work);\n+\tspin_unlock_bh(\u0026sw_fl_lock);\n+\n+\treturn 0;\n+}\n+\n+static int sw_fl_parse_actions(struct otx2_nic *nic,\n+\t\t\t       struct flow_action *flow_action,\n+\t\t\t       struct flow_cls_offload *f,\n+\t\t\t       struct fl_tuple *tuple, u64 *op,\n+\t\t\t       struct nf_flowtable **ct_ft)\n+{\n+\tstruct flow_action_entry *act;\n+\tstruct otx2_nic *out_nic;\n+\tint used = 0;\n+\tint err;\n+\tint i;\n+\n+\tif (!flow_action_has_entries(flow_action))\n+\t\treturn -EINVAL;\n+\n+\tflow_action_for_each(i, act, flow_action) {\n+\t\tif (used \u003e= MANGLE_ARR_SZ) {\n+\t\t\tnetdev_err(nic-\u003enetdev,\n+\t\t\t\t   \"%s: More entries than supported %u\\n\",\n+\t\t\t\t   __func__, used);\n+\t\t\treturn -ENOMEM;\n+\t\t}\n+\n+\t\tswitch (act-\u003eid) {\n+\t\tcase FLOW_ACTION_REDIRECT:\n+\t\t\tif (!act-\u003edev || !sw_nb_is_valid_dev(act-\u003edev))\n+\t\t\t\treturn -EOPNOTSUPP;\n+\t\t\ttrace_sw_act_dump(__func__, \"redirect to egress port\", act-\u003eid);\n+\t\t\ttuple-\u003ein_pf = nic-\u003epcifunc;\n+\t\t\tout_nic = netdev_priv(act-\u003edev);\n+\t\t\ttuple-\u003exmit_pf = out_nic-\u003epcifunc;\n+\t\t\t*op |= BIT_ULL(FLOW_ACTION_REDIRECT);\n+\t\t\tbreak;\n+\n+\t\tcase FLOW_ACTION_CT:\n+\t\t\ttrace_sw_act_dump(__func__, \"register conntrack offload callback\", act-\u003eid);\n+\t\t\terr = sw_fl_ct_cb_get(act-\u003ect.flow_table, nic);\n+\t\t\tif (err) {\n+\t\t\t\tnetdev_err(nic-\u003enetdev,\n+\t\t\t\t\t   \"%s: Error to offload flow, err=%d\\n\",\n+\t\t\t\t\t   __func__, err);\n+\t\t\t\treturn err;\n+\t\t\t}\n+\n+\t\t\tif (ct_ft)\n+\t\t\t\t*ct_ft = act-\u003ect.flow_table;\n+\t\t\t*op |= BIT_ULL(FLOW_ACTION_CT);\n+\t\t\tbreak;\n+\n+\t\tcase FLOW_ACTION_MANGLE:\n+\t\t\ttrace_sw_act_dump(__func__, \"header mangle action\", act-\u003eid);\n+\t\t\ttuple-\u003emangle[used].type = act-\u003emangle.htype;\n+\t\t\ttuple-\u003emangle[used].val = act-\u003emangle.val;\n+\t\t\ttuple-\u003emangle[used].mask = act-\u003emangle.mask;\n+\t\t\ttuple-\u003emangle[used].offset = act-\u003emangle.offset;\n+\t\t\ttuple-\u003emangle_map[act-\u003emangle.htype] |= BIT(used);\n+\t\t\tused++;\n+\t\t\tbreak;\n+\n+\t\tdefault:\n+\t\t\ttrace_sw_act_dump(__func__, \"unsupported flow action\", act-\u003eid);\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\ttuple-\u003emangle_cnt = used;\n+\n+\tif (!*op \u0026\u0026 !used) {\n+\t\tnetdev_dbg(nic-\u003enetdev, \"%s: Op is not valid\\n\", __func__);\n+\t\treturn -EOPNOTSUPP;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int sw_fl_get_route(struct net *net, struct fib_result *res, __be32 addr)\n+{\n+\tstruct flowi4 fl4;\n+\n+\tmemset(\u0026fl4, 0, sizeof(fl4));\n+\tfl4.daddr = addr;\n+\treturn fib_lookup(net, \u0026fl4, res, 0);\n+}\n+\n+static int sw_fl_get_pcifunc(struct otx2_nic *pf, __be32 dst, u16 *pcifunc,\n+\t\t\t     struct fl_tuple *ftuple, bool is_in_dev)\n+{\n+\tstruct fib_nh_common *fib_nhc;\n+\tstruct net_device *dev, *br;\n+\tstruct fib_result res;\n+\tstruct list_head *lh;\n+\tstruct otx2_nic *nic;\n+\tint err;\n+\n+\trcu_read_lock();\n+\n+\terr = sw_fl_get_route(dev_net(pf-\u003enetdev), \u0026res, dst);\n+\tif (err) {\n+\t\tnetdev_err(pf-\u003enetdev,\n+\t\t\t   \"%s: Failed to find route to dst %pI4\\n\",\n+\t\t\t   __func__, \u0026dst);\n+\t\tgoto done;\n+\t}\n+\n+\tif (res.fi-\u003efib_type != RTN_UNICAST) {\n+\t\tnetdev_err(pf-\u003enetdev,\n+\t\t\t   \"%s: Not unicast  route to dst %pi4\\n\",\n+\t\t\t   __func__, \u0026dst);\n+\t\terr = -EFAULT;\n+\t\tgoto done;\n+\t}\n+\n+\tfib_nhc = fib_info_nhc(res.fi, 0);\n+\tif (!fib_nhc) {\n+\t\terr = -EINVAL;\n+\t\tnetdev_err(pf-\u003enetdev,\n+\t\t\t   \"%s: Could not get fib_nhc for %pI4\\n\",\n+\t\t\t   __func__, \u0026dst);\n+\t\tgoto done;\n+\t}\n+\n+\tif (unlikely(netif_is_bridge_master(fib_nhc-\u003enhc_dev))) {\n+\t\tbr = fib_nhc-\u003enhc_dev;\n+\n+\t\tif (is_in_dev)\n+\t\t\tftuple-\u003eis_indev_br = 1;\n+\t\telse\n+\t\t\tftuple-\u003eis_xdev_br = 1;\n+\n+\t\tlh = \u0026br-\u003eadj_list.lower;\n+\t\tif (list_empty(lh)) {\n+\t\t\tnetdev_err(pf-\u003enetdev,\n+\t\t\t\t   \"%s: Unable to find any slave device\\n\",\n+\t\t\t\t   __func__);\n+\t\t\terr = -EINVAL;\n+\t\t\tgoto done;\n+\t\t}\n+\t\tdev = netdev_next_lower_dev_rcu(br, \u0026lh);\n+\n+\t} else {\n+\t\tdev = fib_nhc-\u003enhc_dev;\n+\t}\n+\n+\tif (!dev || !sw_nb_is_valid_dev(dev)) {\n+\t\tnetdev_err(pf-\u003enetdev,\n+\t\t\t   \"%s: flow acceleration support is only for cavium devices\\n\",\n+\t\t\t   __func__);\n+\t\terr = -EOPNOTSUPP;\n+\t\tgoto done;\n+\t}\n+\n+\tnic = netdev_priv(dev);\n+\t*pcifunc = nic-\u003epcifunc;\n+\n+done:\n+\trcu_read_unlock();\n+\treturn err;\n+}\n+\n+static int sw_fl_parse_flow(struct otx2_nic *nic, struct flow_cls_offload *f,\n+\t\t\t    struct fl_tuple *tuple, u64 *features)\n+{\n+\tstruct flow_rule *rule;\n+\tu8 ip_proto = 0;\n+\n+\t*features = 0;\n+\n+\trule = flow_cls_offload_flow_rule(f);\n+\n+\tif (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_BASIC)) {\n+\t\tstruct flow_match_basic match;\n+\n+\t\tflow_rule_match_basic(rule, \u0026match);\n+\n+\t\t/* All EtherTypes can be matched, no hw limitation */\n+\n+\t\tif (match.mask-\u003en_proto) {\n+\t\t\ttuple-\u003eeth_type = match.key-\u003en_proto;\n+\t\t\ttuple-\u003em_eth_type = match.mask-\u003en_proto;\n+\t\t\t*features |= BIT_ULL(NPC_ETYPE);\n+\t\t}\n+\n+\t\tif (match.mask-\u003eip_proto) {\n+\t\t\tif (match.key-\u003eip_proto != IPPROTO_TCP \u0026\u0026\n+\t\t\t    match.key-\u003eip_proto != IPPROTO_UDP)\n+\t\t\t\treturn -EOPNOTSUPP;\n+\n+\t\t\tip_proto = match.key-\u003eip_proto;\n+\t\t\tif (ip_proto == IPPROTO_UDP)\n+\t\t\t\t*features |= BIT_ULL(NPC_IPPROTO_UDP);\n+\t\t\telse\n+\t\t\t\t*features |= BIT_ULL(NPC_IPPROTO_TCP);\n+\t\t}\n+\n+\t\ttuple-\u003eproto = ip_proto;\n+\t}\n+\n+\tif (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_ETH_ADDRS)) {\n+\t\tstruct flow_match_eth_addrs match;\n+\n+\t\tflow_rule_match_eth_addrs(rule, \u0026match);\n+\n+\t\tif (!is_zero_ether_addr(match.key-\u003edst) \u0026\u0026\n+\t\t    is_unicast_ether_addr(match.key-\u003edst)) {\n+\t\t\tether_addr_copy(tuple-\u003edmac,\n+\t\t\t\t\tmatch.key-\u003edst);\n+\n+\t\t\tether_addr_copy(tuple-\u003em_dmac,\n+\t\t\t\t\tmatch.mask-\u003edst);\n+\n+\t\t\t*features |= BIT_ULL(NPC_DMAC);\n+\t\t}\n+\n+\t\tif (!is_zero_ether_addr(match.key-\u003esrc) \u0026\u0026\n+\t\t    is_unicast_ether_addr(match.key-\u003esrc)) {\n+\t\t\tether_addr_copy(tuple-\u003esmac,\n+\t\t\t\t\tmatch.key-\u003esrc);\n+\t\t\tether_addr_copy(tuple-\u003em_smac,\n+\t\t\t\t\tmatch.mask-\u003esrc);\n+\t\t\t*features |= BIT_ULL(NPC_SMAC);\n+\t\t}\n+\t}\n+\n+\tif (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IPV4_ADDRS)) {\n+\t\tstruct flow_match_ipv4_addrs match;\n+\n+\t\tflow_rule_match_ipv4_addrs(rule, \u0026match);\n+\n+\t\tif (match.mask-\u003edst) {\n+\t\t\ttuple-\u003eip4dst = match.key-\u003edst;\n+\t\t\ttuple-\u003em_ip4dst = match.mask-\u003edst;\n+\t\t\t*features |= BIT_ULL(NPC_DIP_IPV4);\n+\t\t}\n+\n+\t\tif (match.mask-\u003esrc) {\n+\t\t\ttuple-\u003eip4src = match.key-\u003esrc;\n+\t\t\ttuple-\u003em_ip4src = match.mask-\u003esrc;\n+\t\t\t*features |= BIT_ULL(NPC_SIP_IPV4);\n+\t\t}\n+\t}\n+\n+\tif (!(*features \u0026 BIT_ULL(NPC_DMAC))) {\n+\t\tif (!tuple-\u003em_ip4src || !tuple-\u003em_ip4dst) {\n+\t\t\tnetdev_err(nic-\u003enetdev,\n+\t\t\t\t   \"%s: Invalid src=%pI4 and dst=%pI4 addresses\\n\",\n+\t\t\t\t   __func__, \u0026tuple-\u003eip4src, \u0026tuple-\u003eip4dst);\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\n+\t\tif ((tuple-\u003eip4src \u0026 tuple-\u003em_ip4src) == (tuple-\u003eip4dst \u0026 tuple-\u003em_ip4dst)) {\n+\t\t\tnetdev_err(nic-\u003enetdev,\n+\t\t\t\t   \"%s: Masked values are same; Invalid src=%pI4 and dst=%pI4 addresses\\n\",\n+\t\t\t\t   __func__, \u0026tuple-\u003eip4src, \u0026tuple-\u003eip4dst);\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\t}\n+\n+\tif (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_PORTS)) {\n+\t\tstruct flow_match_ports match;\n+\n+\t\tflow_rule_match_ports(rule, \u0026match);\n+\n+\t\tif (ip_proto == IPPROTO_UDP) {\n+\t\t\tif (match.mask-\u003edst)\n+\t\t\t\t*features |= BIT_ULL(NPC_DPORT_UDP);\n+\n+\t\t\tif (match.mask-\u003esrc)\n+\t\t\t\t*features |= BIT_ULL(NPC_SPORT_UDP);\n+\t\t} else if (ip_proto == IPPROTO_TCP) {\n+\t\t\tif (match.mask-\u003edst)\n+\t\t\t\t*features |= BIT_ULL(NPC_DPORT_TCP);\n+\n+\t\t\tif (match.mask-\u003esrc)\n+\t\t\t\t*features |= BIT_ULL(NPC_SPORT_TCP);\n+\t\t}\n+\n+\t\tif (match.mask-\u003esrc) {\n+\t\t\ttuple-\u003esport = match.key-\u003esrc;\n+\t\t\ttuple-\u003em_sport = match.mask-\u003esrc;\n+\t\t}\n+\n+\t\tif (match.mask-\u003edst) {\n+\t\t\ttuple-\u003edport = match.key-\u003edst;\n+\t\t\ttuple-\u003em_dport = match.mask-\u003edst;\n+\t\t}\n+\t}\n+\n+\tif (!(*features \u0026 (BIT_ULL(NPC_DMAC) |\n+\t\t\t   BIT_ULL(NPC_SMAC) |\n+\t\t\t   BIT_ULL(NPC_DIP_IPV4) |\n+\t\t\t   BIT_ULL(NPC_SIP_IPV4) |\n+\t\t\t   BIT_ULL(NPC_DIP_IPV6) |\n+\t\t\t   BIT_ULL(NPC_SIP_IPV6) |\n+\t\t\t   BIT_ULL(NPC_DPORT_UDP) |\n+\t\t\t   BIT_ULL(NPC_SPORT_UDP) |\n+\t\t\t   BIT_ULL(NPC_DPORT_TCP) |\n+\t\t\t   BIT_ULL(NPC_SPORT_TCP)))) {\n+\t\treturn -EINVAL;\n+\t}\n+\n+\ttuple-\u003efeatures = *features;\n+\n+\treturn 0;\n+}\n+\n+static int sw_fl_add(struct otx2_nic *nic, struct flow_cls_offload *f)\n+{\n+\tstruct nf_flowtable *ct_ft = NULL;\n+\tstruct fl_tuple tuple = { 0 };\n+\tstruct flow_rule *rule;\n+\tu64 features = 0;\n+\tu64 op = 0;\n+\tint rc;\n+\n+\trule = flow_cls_offload_flow_rule(f);\n+\n+\trc = sw_fl_parse_actions(nic, \u0026rule-\u003eaction, f, \u0026tuple, \u0026op, \u0026ct_ft);\n+\tif (rc)\n+\t\treturn rc;\n+\n+\tif (op \u0026 BIT_ULL(FLOW_ACTION_CT)) {\n+\t\tif (ct_ft)\n+\t\t\tsw_fl_ct_cookie_add(f-\u003ecookie, ct_ft, nic);\n+\t\treturn 0;\n+\t}\n+\n+\trc  = sw_fl_parse_flow(nic, f, \u0026tuple, \u0026features);\n+\tif (rc) {\n+\t\ttrace_sw_fl_dump(__func__, \"flow key parse failed\", \u0026tuple);\n+\t\treturn -EFAULT;\n+\t}\n+\n+\tif (!netif_is_ovs_port(nic-\u003enetdev)) {\n+\t\trc = sw_fl_get_pcifunc(nic, tuple.ip4src, \u0026tuple.in_pf,\n+\t\t\t\t       \u0026tuple, true);\n+\t\tif (rc) {\n+\t\t\ttrace_sw_fl_dump(__func__, \"ingress pcifunc lookup failed\", \u0026tuple);\n+\t\t\treturn rc;\n+\t\t}\n+\n+\t\trc = sw_fl_get_pcifunc(nic, tuple.ip4dst,\n+\t\t\t\t       \u0026tuple.xmit_pf, \u0026tuple, false);\n+\t\tif (rc) {\n+\t\t\ttrace_sw_fl_dump(__func__, \"egress pcifunc lookup failed\", \u0026tuple);\n+\t\t\treturn rc;\n+\t\t}\n+\t}\n+\n+\ttrace_sw_fl_dump(__func__, \"offload flow add queued\", \u0026tuple);\n+\treturn sw_fl_add_to_list(nic, \u0026tuple, f-\u003ecookie, true);\n+}\n+\n+static int sw_fl_del(struct otx2_nic *nic, struct flow_cls_offload *f)\n+{\n+\tsw_fl_ct_cookie_put(f-\u003ecookie);\n+\tsw_fl_add_to_list(nic, NULL, f-\u003ecookie, false);\n+\treturn 0;\n+}\n+\n+static int sw_fl_stats(struct otx2_nic *nic, struct flow_cls_offload *f)\n+{\n+\tstruct fl_get_stats_req *req;\n+\tstruct fl_get_stats_rsp *rsp;\n+\tu64 pkts_diff;\n+\tint rc = 0;\n+\n+\tmutex_lock(\u0026nic-\u003embox.lock);\n+\n+\treq = otx2_mbox_alloc_msg_fl_get_stats(\u0026nic-\u003embox);\n+\tif (!req) {\n+\t\tnetdev_err(nic-\u003enetdev,\n+\t\t\t   \"%s: Error happened while mcam alloc req\\n\",\n+\t\t\t   __func__);\n+\t\trc = -ENOMEM;\n+\t\tgoto fail;\n+\t}\n+\treq-\u003ecookie = f-\u003ecookie;\n+\n+\trc = otx2_sync_mbox_msg(\u0026nic-\u003embox);\n+\tif (rc)\n+\t\tgoto fail;\n+\n+\trsp = (struct fl_get_stats_rsp *)otx2_mbox_get_rsp\n+\t\t(\u0026nic-\u003embox.mbox, 0, \u0026req-\u003ehdr);\n+\tif (IS_ERR(rsp)) {\n+\t\trc = PTR_ERR(rsp);\n+\t\tgoto fail;\n+\t}\n+\tpkts_diff = rsp-\u003epkts_diff;\n+\tmutex_unlock(\u0026nic-\u003embox.lock);\n+\n+\tif (pkts_diff) {\n+\t\tflow_stats_update(\u0026f-\u003estats, 0x0, pkts_diff,\n+\t\t\t\t  0x0, jiffies,\n+\t\t\t\t  FLOW_ACTION_HW_STATS_IMMEDIATE);\n+\t}\n+\treturn 0;\n+fail:\n+\tmutex_unlock(\u0026nic-\u003embox.lock);\n+\treturn rc;\n+}\n+\n+static bool init_done;\n+\n+int sw_fl_setup_ft_block_ingress_cb(enum tc_setup_type type,\n+\t\t\t\t    void *type_data, void *cb_priv)\n+{\n+\tstruct flow_cls_offload *cls = type_data;\n+\tstruct otx2_nic *nic = cb_priv;\n+\n+\tif (!smp_load_acquire(\u0026init_done))\n+\t\treturn 0;\n+\n+\tswitch (cls-\u003ecommand) {\n+\tcase FLOW_CLS_REPLACE:\n+\t\treturn sw_fl_add(nic, cls);\n+\tcase FLOW_CLS_DESTROY:\n+\t\treturn sw_fl_del(nic, cls);\n+\tcase FLOW_CLS_STATS:\n+\t\treturn sw_fl_stats(nic, cls);\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\treturn -EOPNOTSUPP;\n+}\n+\n+int sw_fl_init(void)\n+{\n+\tINIT_WORK(\u0026sw_fl_work, sw_fl_wq_handler);\n+\tsw_fl_wq = alloc_workqueue(\"sw_fl_wq\", 0, 0);\n+\tif (!sw_fl_wq)\n+\t\treturn -ENOMEM;\n+\n+\tsmp_store_release(\u0026init_done, true);\n+\treturn 0;\n+}\n+\n+void sw_fl_deinit(void)\n+{\n+\tstruct sw_fl_list_entry *entry;\n+\tstruct workqueue_struct *wq;\n+\tLIST_HEAD(tlist);\n+\n+\tsmp_store_release(\u0026init_done, false);\n+\n+\tspin_lock_bh(\u0026sw_fl_lock);\n+\twq = sw_fl_wq;\n+\tsw_fl_wq = NULL;\n+\tspin_unlock_bh(\u0026sw_fl_lock);\n+\n+\tif (!wq)\n+\t\treturn;\n+\n+\tcancel_work_sync(\u0026sw_fl_work);\n+\tdestroy_workqueue(wq);\n+\n+\tspin_lock_bh(\u0026sw_fl_lock);\n+\tlist_splice_init(\u0026sw_fl_lh, \u0026tlist);\n+\tspin_unlock_bh(\u0026sw_fl_lock);\n+\n+\twhile ((entry =\n+\t\tlist_first_entry_or_null(\u0026tlist,\n+\t\t\t\t\t struct sw_fl_list_entry,\n+\t\t\t\t\t list)) != NULL) {\n+\t\tlist_del_init(\u0026entry-\u003elist);\n+\t\tnetdev_put(entry-\u003epf-\u003enetdev, \u0026entry-\u003edev_tracker);\n+\t\tkfree(entry);\n+\t}\n+\n+\tsw_fl_ct_cb_flush();\n+}\n+#endif\ndiff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.h b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.h\nnew file mode 100644\nindex 00000000000000..8dd816eb17d2c9\n--- /dev/null\n+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.h\n@@ -0,0 +1,15 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/* Marvell switch driver\n+ *\n+ * Copyright (C) 2026 Marvell.\n+ *\n+ */\n+#ifndef SW_FL_H_\n+#define SW_FL_H_\n+\n+void sw_fl_deinit(void);\n+int sw_fl_init(void);\n+int sw_fl_setup_ft_block_ingress_cb(enum tc_setup_type type,\n+\t\t\t\t    void *type_data, void *cb_priv);\n+\n+#endif // SW_FL_H\ndiff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c\nnew file mode 100644\nindex 00000000000000..971af268ae9310\n--- /dev/null\n+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c\n@@ -0,0 +1,501 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/* Marvell RVU switch driver\n+ *\n+ * Copyright (C) 2026 Marvell.\n+ *\n+ */\n+#include \u003clinux/kernel.h\u003e\n+#include \u003clinux/netdevice.h\u003e\n+#include \u003clinux/etherdevice.h\u003e\n+#include \u003cnet/switchdev.h\u003e\n+#include \u003cnet/netevent.h\u003e\n+#include \u003cnet/arp.h\u003e\n+#include \u003cnet/route.h\u003e\n+#include \u003clinux/inetdevice.h\u003e\n+#include \u003cnet/addrconf.h\u003e\n+\n+#include \"../otx2_reg.h\"\n+#include \"../otx2_common.h\"\n+#include \"../otx2_struct.h\"\n+#include \"../cn10k.h\"\n+#include \"sw_nb.h\"\n+#include \"sw_fdb.h\"\n+#include \"sw_fib.h\"\n+#include \"sw_fl.h\"\n+#include \"sw_nb_v4.h\"\n+#include \"sw_nb_v6.h\"\n+\n+/* PF netdev for netdev_* logging when notifier info has no device */\n+static struct net_device *sw_nb_pf_netdev;\n+static bool sw_nb_registered;\n+\n+static const char *sw_nb_cmd2str[OTX2_CMD_MAX] = {\n+\t[OTX2_DEV_UP]  = \"OTX2_DEV_UP\",\n+\t[OTX2_DEV_DOWN] = \"OTX2_DEV_DOWN\",\n+\t[OTX2_DEV_CHANGE] = \"OTX2_DEV_CHANGE\",\n+\t[OTX2_NEIGH_UPDATE] = \"OTX2_NEIGH_UPDATE\",\n+\t[OTX2_FIB_ENTRY_REPLACE] = \"OTX2_FIB_ENTRY_REPLACE\",\n+\t[OTX2_FIB_ENTRY_ADD] = \"OTX2_FIB_ENTRY_ADD\",\n+\t[OTX2_FIB_ENTRY_DEL] = \"OTX2_FIB_ENTRY_DEL\",\n+\t[OTX2_FIB_ENTRY_APPEND] = \"OTX2_FIB_ENTRY_APPEND\",\n+};\n+\n+const char *sw_nb_get_cmd2str(int cmd)\n+{\n+\treturn sw_nb_cmd2str[cmd];\n+}\n+EXPORT_SYMBOL(sw_nb_get_cmd2str);\n+\n+bool sw_nb_is_cavium_dev(struct net_device *netdev)\n+{\n+\tstruct pci_dev *pdev;\n+\tstruct device *dev;\n+\n+\tdev = netdev-\u003edev.parent;\n+\tif (!dev || dev-\u003ebus != \u0026pci_bus_type)\n+\t\treturn false;\n+\n+\tpdev = to_pci_dev(dev);\n+\tif (pdev-\u003evendor != PCI_VENDOR_ID_CAVIUM)\n+\t\treturn false;\n+\n+\treturn true;\n+}\n+\n+struct net_device *sw_nb_resolve_pf_dev(struct net_device *dev)\n+{\n+\tstruct net_device *lower, *pf_dev = dev;\n+\tstruct list_head *iter;\n+\n+\tif (netif_is_bridge_master(dev)) {\n+\t\tnetdev_for_each_lower_dev(dev, lower, iter) {\n+\t\t\tpf_dev = lower;\n+\t\t\tbreak;\n+\t\t}\n+\t} else if (is_vlan_dev(dev)) {\n+\t\tpf_dev = vlan_dev_real_dev(dev);\n+\t}\n+\n+\tif (!sw_nb_is_cavium_dev(pf_dev))\n+\t\treturn NULL;\n+\n+\treturn pf_dev;\n+}\n+\n+static int sw_nb_check_slaves(struct net_device *dev,\n+\t\t\t      struct netdev_nested_priv *priv)\n+{\n+\tint *cnt;\n+\n+\tif (!priv-\u003eflags)\n+\t\treturn 0;\n+\n+\tpriv-\u003eflags \u0026= sw_nb_is_cavium_dev(dev);\n+\tif (priv-\u003eflags) {\n+\t\tcnt = priv-\u003edata;\n+\t\t(*cnt)++;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+bool sw_nb_is_valid_dev(struct net_device *netdev)\n+{\n+\tstruct netdev_nested_priv priv;\n+\tstruct net_device *br;\n+\tint cnt = 0;\n+\tbool valid;\n+\n+\tpriv.flags = true;\n+\tpriv.data = \u0026cnt;\n+\n+\trcu_read_lock();\n+\n+\tif (netif_is_bridge_master(netdev) || is_vlan_dev(netdev)) {\n+\t\tnetdev_walk_all_lower_dev_rcu(netdev, sw_nb_check_slaves, \u0026priv);\n+\t\tvalid = priv.flags \u0026\u0026 cnt;\n+\t\trcu_read_unlock();\n+\t\treturn valid;\n+\t}\n+\n+\tif (netif_is_bridge_port(netdev)) {\n+\t\tbr = netdev_master_upper_dev_get_rcu(netdev);\n+\t\tif (!br) {\n+\t\t\trcu_read_unlock();\n+\t\t\treturn false;\n+\t\t}\n+\t\tnetdev_walk_all_lower_dev_rcu(br, sw_nb_check_slaves, \u0026priv);\n+\t\tvalid = priv.flags \u0026\u0026 cnt;\n+\t\trcu_read_unlock();\n+\t\treturn valid;\n+\t}\n+\n+\trcu_read_unlock();\n+\n+\treturn sw_nb_is_cavium_dev(netdev);\n+}\n+\n+static int sw_nb_fdb_event(struct notifier_block *unused,\n+\t\t\t   unsigned long event, void *ptr)\n+{\n+\tstruct net_device *dev = switchdev_notifier_info_to_dev(ptr);\n+\tstruct switchdev_notifier_fdb_info *fdb_info = ptr;\n+\tint rc = 0;\n+\n+\tif (!sw_nb_is_valid_dev(dev))\n+\t\treturn NOTIFY_DONE;\n+\n+\tswitch (event) {\n+\tcase SWITCHDEV_FDB_ADD_TO_DEVICE:\n+\t\tif (fdb_info-\u003eis_local)\n+\t\t\tbreak;\n+\t\trc = sw_fdb_add_to_list(dev, (u8 *)fdb_info-\u003eaddr, true);\n+\t\tbreak;\n+\n+\tcase SWITCHDEV_FDB_DEL_TO_DEVICE:\n+\t\tif (fdb_info-\u003eis_local)\n+\t\t\tbreak;\n+\t\trc = sw_fdb_add_to_list(dev, (u8 *)fdb_info-\u003eaddr, false);\n+\t\tbreak;\n+\n+\tdefault:\n+\t\treturn NOTIFY_DONE;\n+\t}\n+\n+\tif (rc)\n+\t\tnetdev_err(dev, \"%s: Error to add to list\\n\", __func__);\n+\n+\treturn NOTIFY_DONE;\n+}\n+\n+static struct notifier_block sw_nb_fdb = {\n+\t.notifier_call = sw_nb_fdb_event,\n+};\n+\n+static void __maybe_unused\n+sw_nb_fib_event_dump(unsigned long event, void *ptr)\n+{\n+\tstruct fib_entry_notifier_info *fen_info = ptr;\n+\tstruct net_device *log_dev;\n+\tstruct fib_nh *fib_nh;\n+\tstruct fib_info *fi;\n+\tint i;\n+\n+\tfi = fen_info-\u003efi;\n+\tlog_dev = (fi \u0026\u0026 fi-\u003efib_nhs) ? fi-\u003efib_nh-\u003efib_nh_dev : sw_nb_pf_netdev;\n+\tif (log_dev)\n+\t\tnetdev_info(log_dev, \"%s: FIB event=%lu dst=%pI4 dstlen=%u type=%u\\n\",\n+\t\t\t    __func__, event, (const __be32 *)\u0026fen_info-\u003edst,\n+\t\t\t    fen_info-\u003edst_len, fen_info-\u003etype);\n+\n+\tif (!fi)\n+\t\treturn;\n+\n+\tfib_nh = fi-\u003efib_nh;\n+\tfor (i = 0; i \u003c fi-\u003efib_nhs; i++, fib_nh++) {\n+\t\tif (!fib_nh-\u003efib_nh_dev)\n+\t\t\tcontinue;\n+\t\tnetdev_info(fib_nh-\u003efib_nh_dev,\n+\t\t\t    \"%s: dev=%s saddr=%pI4 gw=%pI4\\n\",\n+\t\t\t    __func__, fib_nh-\u003efib_nh_dev-\u003ename,\n+\t\t\t    \u0026fib_nh-\u003enh_saddr, \u0026fib_nh-\u003efib_nh_gw4);\n+\t}\n+}\n+\n+#define SWITCH_NB_FIB_EVENT_DUMP(...) \\\n+\tsw_nb_fib_event_dump(__VA_ARGS__)\n+\n+int sw_nb_fib_event_to_otx2_event(int event, struct net_device *netdev)\n+{\n+\tswitch (event) {\n+\tcase FIB_EVENT_ENTRY_REPLACE:\n+\t\treturn OTX2_FIB_ENTRY_REPLACE;\n+\tcase FIB_EVENT_ENTRY_ADD:\n+\t\treturn OTX2_FIB_ENTRY_ADD;\n+\tcase FIB_EVENT_ENTRY_DEL:\n+\t\treturn OTX2_FIB_ENTRY_DEL;\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\tnetdev_err(netdev, \"Wrong FIB event %d\\n\", event);\n+\treturn -1;\n+}\n+\n+static int sw_nb_fib_event(struct notifier_block *nb,\n+\t\t\t   unsigned long event, void *ptr)\n+{\n+\tstruct fib_notifier_info *info = ptr;\n+\n+\tswitch (event) {\n+\tcase FIB_EVENT_ENTRY_REPLACE:\n+\tcase FIB_EVENT_ENTRY_ADD:\n+\tcase FIB_EVENT_ENTRY_DEL:\n+\t\tbreak;\n+\tdefault:\n+\t\tif (sw_nb_pf_netdev)\n+\t\t\tnetdev_dbg(sw_nb_pf_netdev,\n+\t\t\t\t   \"%s: Won't process FIB event %lu\\n\",\n+\t\t\t\t   __func__, event);\n+\t\treturn NOTIFY_DONE;\n+\t}\n+\n+\tswitch (info-\u003efamily) {\n+\tcase AF_INET:\n+\t\treturn sw_nb_v4_fib_event(nb, event, ptr);\n+#if IS_ENABLED(CONFIG_IPV6)\n+\tcase AF_INET6:\n+\t\treturn sw_nb_v6_fib_event(nb, event, ptr);\n+#endif\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\treturn NOTIFY_DONE;\n+}\n+\n+static struct notifier_block sw_nb_fib = {\n+\t.notifier_call = sw_nb_fib_event,\n+};\n+\n+static int sw_nb_net_event(struct notifier_block *nb,\n+\t\t\t   unsigned long event, void *ptr)\n+{\n+\tstruct neighbour *n = ptr;\n+\n+\tif (!sw_nb_is_valid_dev(n-\u003edev))\n+\t\treturn NOTIFY_DONE;\n+\n+\tif (event != NETEVENT_NEIGH_UPDATE)\n+\t\treturn NOTIFY_DONE;\n+\n+\tswitch (n-\u003etbl-\u003efamily) {\n+\tcase AF_INET:\n+\t\treturn sw_nb_net_v4_neigh_update(nb, event, ptr);\n+#if IS_ENABLED(CONFIG_IPV6)\n+\tcase AF_INET6:\n+\t\treturn sw_nb_net_v6_neigh_update(nb, event, ptr);\n+#endif\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\treturn NOTIFY_DONE;\n+}\n+\n+static struct notifier_block sw_nb_netevent = {\n+\t.notifier_call = sw_nb_net_event,\n+\n+};\n+\n+int sw_nb_inetaddr_event_to_otx2_event(int event, struct net_device *netdev)\n+{\n+\tswitch (event) {\n+\tcase NETDEV_CHANGE:\n+\t\treturn OTX2_DEV_CHANGE;\n+\tcase NETDEV_UP:\n+\t\treturn OTX2_DEV_UP;\n+\tcase NETDEV_DOWN:\n+\t\treturn OTX2_DEV_DOWN;\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\tnetdev_dbg(netdev, \"%s: Wrong interaddr event %d\\n\",\n+\t\t   __func__, event);\n+\treturn -1;\n+}\n+\n+static struct notifier_block sw_nb_v4_inetaddr = {\n+\t.notifier_call = sw_nb_v4_inetaddr_event,\n+};\n+\n+#if IS_ENABLED(CONFIG_IPV6)\n+static struct notifier_block sw_nb_v6_inetaddr = {\n+\t.notifier_call = sw_nb_v6_inetaddr_event,\n+};\n+#endif\n+\n+static int sw_nb_netdev_event(struct notifier_block *unused,\n+\t\t\t      unsigned long event, void *ptr)\n+{\n+\tstruct net_device *dev = netdev_notifier_info_to_dev(ptr);\n+\tstruct in_device *idev;\n+\tstruct inet6_dev *i6dev;\n+\n+\tif (event != NETDEV_CHANGE \u0026\u0026\n+\t    event != NETDEV_UP \u0026\u0026\n+\t    event != NETDEV_DOWN) {\n+\t\treturn NOTIFY_DONE;\n+\t}\n+\n+\tif (!sw_nb_is_valid_dev(dev))\n+\t\treturn NOTIFY_DONE;\n+\n+\tidev = __in_dev_get_rtnl(dev);\n+\tif (idev)\n+\t\tsw_nb_v4_netdev_event(unused, event, ptr);\n+\n+\ti6dev = __in6_dev_get(dev);\n+#if IS_ENABLED(CONFIG_IPV6)\n+\tif (i6dev)\n+\t\tsw_nb_v6_netdev_event(unused, event, ptr);\n+#endif\n+\n+\treturn NOTIFY_DONE;\n+}\n+\n+static struct notifier_block sw_nb_netdev = {\n+\t.notifier_call = sw_nb_netdev_event,\n+};\n+\n+int sw_nb_unregister(struct net_device *netdev)\n+{\n+\tint err, ret = 0;\n+\n+\tif (!sw_nb_registered)\n+\t\treturn 0;\n+\n+\terr = unregister_switchdev_notifier(\u0026sw_nb_fdb);\n+\tif (err) {\n+\t\tnetdev_err(netdev, \"Failed to unregister switchdev nb\\n\");\n+\t\tret = err;\n+\t}\n+\n+\terr = unregister_fib_notifier(\u0026init_net, \u0026sw_nb_fib);\n+\tif (err) {\n+\t\tnetdev_err(netdev, \"Failed to unregister fib nb\\n\");\n+\t\tif (!ret)\n+\t\t\tret = err;\n+\t}\n+\n+\terr = unregister_netevent_notifier(\u0026sw_nb_netevent);\n+\tif (err) {\n+\t\tnetdev_err(netdev, \"Failed to unregister netevent\\n\");\n+\t\tif (!ret)\n+\t\t\tret = err;\n+\t}\n+\n+\terr = unregister_inetaddr_notifier(\u0026sw_nb_v4_inetaddr);\n+\tif (err) {\n+\t\tnetdev_err(netdev, \"Failed to unregister addr event\\n\");\n+\t\tif (!ret)\n+\t\t\tret = err;\n+\t}\n+\n+#if IS_ENABLED(CONFIG_IPV6)\n+\terr = unregister_inet6addr_notifier(\u0026sw_nb_v6_inetaddr);\n+\tif (err) {\n+\t\tnetdev_err(netdev, \"Failed to unregister addr event\\n\");\n+\t\tif (!ret)\n+\t\t\tret = err;\n+\t}\n+#endif\n+\n+\terr = unregister_netdevice_notifier(\u0026sw_nb_netdev);\n+\tif (err) {\n+\t\tnetdev_err(netdev, \"Failed to unregister netdev notifier\\n\");\n+\t\tif (!ret)\n+\t\t\tret = err;\n+\t}\n+\n+\tsw_fl_deinit();\n+\tsw_fib_deinit();\n+\tsw_fdb_deinit();\n+\n+\tsw_nb_pf_netdev = NULL;\n+\tsw_nb_registered = false;\n+\n+\treturn ret;\n+}\n+EXPORT_SYMBOL(sw_nb_unregister);\n+\n+int sw_nb_register(struct net_device *netdev)\n+{\n+\tint err;\n+\n+\t/* One switch PF / switchdev instance registers system-wide notifiers. */\n+\tif (sw_nb_registered)\n+\t\treturn -EBUSY;\n+\n+\tsw_nb_pf_netdev = netdev;\n+\n+\terr = sw_fdb_init();\n+\tif (err)\n+\t\tgoto err_clear;\n+\n+\terr = sw_fib_init();\n+\tif (err)\n+\t\tgoto err_fdb;\n+\n+\terr = sw_fl_init();\n+\tif (err)\n+\t\tgoto err_fib;\n+\n+\terr = register_switchdev_notifier(\u0026sw_nb_fdb);\n+\tif (err) {\n+\t\tnetdev_err(netdev, \"Failed to register switchdev nb\\n\");\n+\t\tgoto err_helpers;\n+\t}\n+\n+\terr = register_fib_notifier(\u0026init_net, \u0026sw_nb_fib, NULL, NULL);\n+\tif (err) {\n+\t\tnetdev_err(netdev, \"Failed to register fb notifier block\\n\");\n+\t\tgoto err1;\n+\t}\n+\n+\terr = register_netevent_notifier(\u0026sw_nb_netevent);\n+\tif (err) {\n+\t\tnetdev_err(netdev, \"Failed to register netevent\\n\");\n+\t\tgoto err2;\n+\t}\n+\n+#if IS_ENABLED(CONFIG_IPV6)\n+\terr = register_inet6addr_notifier(\u0026sw_nb_v6_inetaddr);\n+\tif (err) {\n+\t\tnetdev_err(netdev, \"Failed to register addr event\\n\");\n+\t\tgoto err3;\n+\t}\n+#endif\n+\n+\terr = register_inetaddr_notifier(\u0026sw_nb_v4_inetaddr);\n+\tif (err) {\n+\t\tnetdev_err(netdev, \"Failed to register addr event\\n\");\n+\t\tgoto err4;\n+\t}\n+\n+\terr = register_netdevice_notifier(\u0026sw_nb_netdev);\n+\tif (err) {\n+\t\tnetdev_err(netdev, \"Failed to register netdevice nb\\n\");\n+\t\tgoto err5;\n+\t}\n+\n+\tsw_nb_registered = true;\n+\n+\treturn 0;\n+\n+err5:\n+\tunregister_inetaddr_notifier(\u0026sw_nb_v4_inetaddr);\n+\n+err4:\n+#if IS_ENABLED(CONFIG_IPV6)\n+\tunregister_inet6addr_notifier(\u0026sw_nb_v6_inetaddr);\n+\n+err3:\n+#endif\n+\tunregister_netevent_notifier(\u0026sw_nb_netevent);\n+\n+err2:\n+\tunregister_fib_notifier(\u0026init_net, \u0026sw_nb_fib);\n+\n+err1:\n+\tunregister_switchdev_notifier(\u0026sw_nb_fdb);\n+\n+err_helpers:\n+\tsw_fl_deinit();\n+err_fib:\n+\tsw_fib_deinit();\n+err_fdb:\n+\tsw_fdb_deinit();\n+err_clear:\n+\tsw_nb_pf_netdev = NULL;\n+\treturn err;\n+}\n+EXPORT_SYMBOL(sw_nb_register);\ndiff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.h b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.h\nnew file mode 100644\nindex 00000000000000..a701574de1e46b\n--- /dev/null\n+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.h\n@@ -0,0 +1,49 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/* Marvell switch driver\n+ *\n+ * Copyright (C) 2026 Marvell.\n+ *\n+ */\n+#ifndef SW_NB_H_\n+#define SW_NB_H_\n+\n+#include \u003clinux/kconfig.h\u003e\n+\n+struct net_device;\n+struct otx2_nic;\n+struct af2pf_fdb_refresh_req;\n+struct msg_rsp;\n+\n+int otx2_mbox_up_handler_af2pf_fdb_refresh(struct otx2_nic *pf,\n+\t\t\t\t\t   struct af2pf_fdb_refresh_req *req,\n+\t\t\t\t\t   struct msg_rsp *rsp);\n+\n+#if IS_ENABLED(CONFIG_OCTEONTX_SWITCH)\n+enum {\n+\tOTX2_DEV_UP = 1,\n+\tOTX2_DEV_DOWN,\n+\tOTX2_DEV_CHANGE,\n+\tOTX2_NEIGH_UPDATE,\n+\tOTX2_FIB_ENTRY_REPLACE,\n+\tOTX2_FIB_ENTRY_ADD,\n+\tOTX2_FIB_ENTRY_DEL,\n+\tOTX2_FIB_ENTRY_APPEND,\n+\tOTX2_CMD_MAX,\n+};\n+\n+int sw_nb_register(struct net_device *netdev);\n+int sw_nb_unregister(struct net_device *netdev);\n+bool sw_nb_is_valid_dev(struct net_device *netdev);\n+struct net_device *sw_nb_resolve_pf_dev(struct net_device *dev);\n+\n+bool sw_nb_is_cavium_dev(struct net_device *netdev);\n+int sw_nb_fib_event_to_otx2_event(int event, struct net_device *netdev);\n+int sw_nb_inetaddr_event_to_otx2_event(int event, struct net_device *netdev);\n+\n+const char *sw_nb_get_cmd2str(int cmd);\n+#else\n+static inline int sw_nb_register(struct net_device *netdev) { return 0; }\n+static inline int sw_nb_unregister(struct net_device *netdev) { return 0; }\n+#endif\n+\n+#endif /* SW_NB_H_ */\ndiff --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\nnew file mode 100644\nindex 00000000000000..c1e50bb0fbd3ad\n--- /dev/null\n+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.c\n@@ -0,0 +1,343 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/* Marvell RVU switch driver\n+ *\n+ * Copyright (C) 2026 Marvell.\n+ *\n+ */\n+#include \u003clinux/kernel.h\u003e\n+#include \u003clinux/netdevice.h\u003e\n+#include \u003clinux/etherdevice.h\u003e\n+#include \u003cnet/switchdev.h\u003e\n+#include \u003cnet/netevent.h\u003e\n+#include \u003cnet/arp.h\u003e\n+#include \u003cnet/route.h\u003e\n+#include \u003clinux/inetdevice.h\u003e\n+\n+#include \"../otx2_reg.h\"\n+#include \"../otx2_common.h\"\n+#include \"../otx2_struct.h\"\n+#include \"../cn10k.h\"\n+#include \"sw_nb.h\"\n+#include \"sw_fdb.h\"\n+#include \"sw_fib.h\"\n+#include \"sw_fl.h\"\n+#include \"sw_nb_v4.h\"\n+\n+int sw_nb_v4_netdev_event(struct notifier_block *unused,\n+\t\t\t  unsigned long event, void *ptr)\n+{\n+\tstruct net_device *dev = netdev_notifier_info_to_dev(ptr);\n+\tstruct netdev_hw_addr *dev_addr;\n+\tstruct net_device *pf_dev;\n+\tstruct in_device *idev;\n+\tstruct in_ifaddr *ifa;\n+\tstruct fib_entry *entry;\n+\tstruct otx2_nic *pf;\n+\n+\tidev = __in_dev_get_rtnl(dev);\n+\tif (!idev || !idev-\u003eifa_list)\n+\t\treturn NOTIFY_DONE;\n+\n+\tif (!sw_nb_is_valid_dev(dev))\n+\t\treturn NOTIFY_DONE;\n+\n+\tifa = rtnl_dereference(idev-\u003eifa_list);\n+\n+\tentry = kcalloc(1, sizeof(*entry), GFP_KERNEL);\n+\tif (!entry)\n+\t\treturn NOTIFY_DONE;\n+\n+\tentry-\u003ecmd = sw_nb_inetaddr_event_to_otx2_event(event, dev);\n+\tentry-\u003edst = ifa-\u003eifa_address;\n+\tentry-\u003edst_len = 32;\n+\tentry-\u003emac_valid = 1;\n+\tentry-\u003ehost = 1;\n+\n+\tpf_dev = sw_nb_resolve_pf_dev(dev);\n+\tif (!pf_dev) {\n+\t\tkfree(entry);\n+\t\treturn NOTIFY_DONE;\n+\t}\n+\n+\tif (netif_is_bridge_master(dev)) {\n+\t\tentry-\u003ebridge = 1;\n+\t} else if (is_vlan_dev(dev)) {\n+\t\tentry-\u003evlan_valid = 1;\n+\t\tentry-\u003evlan_tag = cpu_to_be16(vlan_dev_vlan_id(dev));\n+\t}\n+\n+\tpf = netdev_priv(pf_dev);\n+\tentry-\u003eport_id = pf-\u003epcifunc;\n+\n+\tfor_each_dev_addr(dev, dev_addr) {\n+\t\tether_addr_copy(entry-\u003emac, dev_addr-\u003eaddr);\n+\t\tbreak;\n+\t}\n+\n+\tnetdev_dbg(dev, \"%s: pushing netdev event from HOST interface address %pI4, %pM, dev=%s\\n\",\n+\t\t   __func__, \u0026entry-\u003edst, entry-\u003emac, dev-\u003ename);\n+\tsw_fib_add_to_list(pf_dev, entry, 1);\n+\n+\treturn NOTIFY_DONE;\n+}\n+\n+int sw_nb_v4_inetaddr_event(struct notifier_block *nb,\n+\t\t\t    unsigned long event, void *ptr)\n+{\n+\tstruct in_ifaddr *ifa = (struct in_ifaddr *)ptr;\n+\tstruct net_device *dev = ifa-\u003eifa_dev-\u003edev;\n+\tstruct netdev_hw_addr *dev_addr;\n+\tstruct net_device *pf_dev;\n+\tstruct in_device *idev;\n+\tstruct fib_entry *entry;\n+\tstruct otx2_nic *pf;\n+\n+\tif (event != NETDEV_CHANGE \u0026\u0026\n+\t    event != NETDEV_UP \u0026\u0026\n+\t    event != NETDEV_DOWN) {\n+\t\treturn NOTIFY_DONE;\n+\t}\n+\n+\tif (!sw_nb_is_valid_dev(dev))\n+\t\treturn NOTIFY_DONE;\n+\n+\tidev = __in_dev_get_rtnl(dev);\n+\tif (!idev || !idev-\u003eifa_list)\n+\t\treturn NOTIFY_DONE;\n+\n+\tentry = kcalloc(1, sizeof(*entry), GFP_ATOMIC);\n+\tif (!entry)\n+\t\treturn NOTIFY_DONE;\n+\n+\tentry-\u003ecmd = sw_nb_inetaddr_event_to_otx2_event(event, dev);\n+\tentry-\u003edst = ifa-\u003eifa_address;\n+\tentry-\u003edst_len = 32;\n+\tentry-\u003emac_valid = 1;\n+\tentry-\u003ehost = 1;\n+\n+\tpf_dev = sw_nb_resolve_pf_dev(dev);\n+\tif (!pf_dev) {\n+\t\tkfree(entry);\n+\t\treturn NOTIFY_DONE;\n+\t}\n+\n+\tif (netif_is_bridge_master(dev)) {\n+\t\tentry-\u003ebridge = 1;\n+\t} else if (is_vlan_dev(dev)) {\n+\t\tentry-\u003evlan_valid = 1;\n+\t\tentry-\u003evlan_tag = cpu_to_be16(vlan_dev_vlan_id(dev));\n+\t}\n+\n+\tpf = netdev_priv(pf_dev);\n+\tentry-\u003eport_id = pf-\u003epcifunc;\n+\n+\tfor_each_dev_addr(dev, dev_addr) {\n+\t\tether_addr_copy(entry-\u003emac, dev_addr-\u003eaddr);\n+\t\tbreak;\n+\t}\n+\n+\tnetdev_dbg(dev, \"%s: pushing inetaddr event from HOST interface address %pI4, %pM, %s\\n\",\n+\t\t   __func__, \u0026entry-\u003edst, entry-\u003emac, dev-\u003ename);\n+\n+\tsw_fib_add_to_list(pf_dev, entry, 1);\n+\treturn NOTIFY_DONE;\n+}\n+\n+int sw_nb_v4_fib_event(struct notifier_block *nb,\n+\t\t       unsigned long event, void *ptr)\n+{\n+\tstruct fib_entry_notifier_info *fen_info = ptr;\n+\tstruct net_device *host_pf_dev = NULL;\n+\tstruct netdev_hw_addr *dev_addr;\n+\tstruct net_device *nh_pf_dev;\n+\tstruct neighbour *neigh;\n+\tstruct fib_entry *entry;\n+\tstruct net_device *dev;\n+\tstruct fib_nh *fib_nh;\n+\tstruct fib_info *fi;\n+\tstruct otx2_nic *pf;\n+\t__be32 *haddr;\n+\tint hcnt = 0;\n+\tint i, cnt;\n+\n+\t/* Process only UNICAST routes add or del */\n+\tif (fen_info-\u003etype != RTN_UNICAST)\n+\t\treturn NOTIFY_DONE;\n+\n+\tfi = fen_info-\u003efi;\n+\tif (!fi)\n+\t\treturn NOTIFY_DONE;\n+\n+\tif (fi-\u003efib_nh_is_v6) {\n+\t\tstruct net_device *log_dev = (fi-\u003efib_nhs \u003e 0) ?\n+\t\t\tfi-\u003efib_nh-\u003efib_nh_dev : NULL;\n+\n+\t\tif (log_dev)\n+\t\t\tnetdev_dbg(log_dev, \"%s: Received v6 notification\\n\",\n+\t\t\t\t   __func__);\n+\t\treturn NOTIFY_DONE;\n+\t}\n+\n+\thaddr = kcalloc(fi-\u003efib_nhs, sizeof(*haddr), GFP_ATOMIC);\n+\tif (!haddr)\n+\t\treturn NOTIFY_DONE;\n+\n+\tfib_nh = fi-\u003efib_nh;\n+\tfor (i = 0; i \u003c fi-\u003efib_nhs; i++, fib_nh++) {\n+\t\tdev = fib_nh-\u003efib_nh_dev;\n+\n+\t\tif (!dev)\n+\t\t\tcontinue;\n+\n+\t\tif (dev-\u003etype != ARPHRD_ETHER)\n+\t\t\tcontinue;\n+\n+\t\tif (!sw_nb_is_valid_dev(dev))\n+\t\t\tcontinue;\n+\n+\t\tnh_pf_dev = sw_nb_resolve_pf_dev(dev);\n+\t\tif (!nh_pf_dev)\n+\t\t\tcontinue;\n+\n+\t\tentry = kcalloc(1, sizeof(*entry), GFP_ATOMIC);\n+\t\tif (!entry)\n+\t\t\tbreak;\n+\n+\t\tentry-\u003ecmd = sw_nb_fib_event_to_otx2_event(event, dev);\n+\t\tentry-\u003edst = (__force __be32)fen_info-\u003edst;\n+\t\tentry-\u003edst_len = fen_info-\u003edst_len;\n+\t\tentry-\u003egw = fib_nh-\u003efib_nh_gw4;\n+\n+\t\tif (netif_is_bridge_master(dev)) {\n+\t\t\tentry-\u003ebridge = 1;\n+\t\t} else if (is_vlan_dev(dev)) {\n+\t\t\tentry-\u003evlan_valid = 1;\n+\t\t\tentry-\u003evlan_tag = cpu_to_be16(vlan_dev_vlan_id(dev));\n+\t\t}\n+\n+\t\tpf = netdev_priv(nh_pf_dev);\n+\t\tentry-\u003eport_id = pf-\u003epcifunc;\n+\n+\t\tif (!fib_nh-\u003efib_nh_gw4) {\n+\t\t\tif (!entry-\u003edst \u0026\u0026 !entry-\u003edst_len) {\n+\t\t\t\tkfree(entry);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\tsw_fib_add_to_list(nh_pf_dev, entry, 1);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tentry-\u003egw_valid = 1;\n+\n+\t\tif (fib_nh-\u003enh_saddr)\n+\t\t\thaddr[hcnt++] = fib_nh-\u003enh_saddr;\n+\n+\t\trcu_read_lock();\n+\t\tneigh = ip_neigh_gw4(fib_nh-\u003efib_nh_dev, fib_nh-\u003efib_nh_gw4);\n+\t\tif (!neigh) {\n+\t\t\trcu_read_unlock();\n+\t\t\tkfree(entry);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (is_valid_ether_addr(neigh-\u003eha)) {\n+\t\t\tentry-\u003emac_valid = 1;\n+\t\t\tneigh_ha_snapshot(entry-\u003emac, neigh, fib_nh-\u003efib_nh_dev);\n+\t\t}\n+\t\trcu_read_unlock();\n+\n+\t\tnetdev_dbg(dev, \"%s: FIB route Rule cmd=%llu dst=%pI4 dst_len=%u gw=%pI4\\n\",\n+\t\t\t   __func__, entry-\u003ecmd, \u0026entry-\u003edst, entry-\u003edst_len,\n+\t\t\t   \u0026entry-\u003egw);\n+\t\tsw_fib_add_to_list(nh_pf_dev, entry, 1);\n+\t}\n+\n+\tif (!hcnt) {\n+\t\tkfree(haddr);\n+\t\treturn NOTIFY_DONE;\n+\t}\n+\n+\tfor (i = 0; i \u003c hcnt; i++) {\n+\t\tfib_nh = fi-\u003efib_nh;\n+\t\tfor (cnt = 0; cnt \u003c fi-\u003efib_nhs; cnt++, fib_nh++) {\n+\t\t\tif (fib_nh-\u003enh_saddr == haddr[i]) {\n+\t\t\t\thost_pf_dev = sw_nb_resolve_pf_dev(fib_nh-\u003efib_nh_dev);\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (!host_pf_dev)\n+\t\t\tcontinue;\n+\n+\t\tentry = kcalloc(1, sizeof(*entry), GFP_ATOMIC);\n+\t\tif (!entry)\n+\t\t\tbreak;\n+\n+\t\tpf = netdev_priv(host_pf_dev);\n+\t\tentry-\u003ecmd = sw_nb_fib_event_to_otx2_event(event, host_pf_dev);\n+\t\tentry-\u003edst = haddr[i];\n+\t\tentry-\u003edst_len = 32;\n+\t\tentry-\u003emac_valid = 1;\n+\t\tentry-\u003ehost = 1;\n+\t\tentry-\u003eport_id = pf-\u003epcifunc;\n+\n+\t\tfor_each_dev_addr(host_pf_dev, dev_addr) {\n+\t\t\tether_addr_copy(entry-\u003emac, dev_addr-\u003eaddr);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tnetdev_dbg(host_pf_dev,\n+\t\t\t   \"%s: FIB host Rule cmd=%llu dst=%pI4 dst_len=%u gw=%pI4 %s\\n\",\n+\t\t\t   __func__, entry-\u003ecmd, \u0026entry-\u003edst, entry-\u003edst_len,\n+\t\t\t   \u0026entry-\u003egw, host_pf_dev-\u003ename);\n+\t\tsw_fib_add_to_list(host_pf_dev, entry, 1);\n+\t}\n+\n+\tkfree(haddr);\n+\treturn NOTIFY_DONE;\n+}\n+\n+int sw_nb_net_v4_neigh_update(struct notifier_block *nb,\n+\t\t\t      unsigned long event, void *ptr)\n+{\n+\tstruct net_device *pf_dev;\n+\tstruct neighbour *n = ptr;\n+\tstruct fib_entry *entry;\n+\tstruct otx2_nic *pf;\n+\n+\tif (n-\u003etbl != \u0026arp_tbl)\n+\t\treturn NOTIFY_DONE;\n+\n+\tif (!sw_nb_is_valid_dev(n-\u003edev))\n+\t\treturn NOTIFY_DONE;\n+\n+\tentry = kcalloc(1, sizeof(*entry), GFP_ATOMIC);\n+\tif (!entry)\n+\t\treturn NOTIFY_DONE;\n+\n+\tentry-\u003ecmd = OTX2_NEIGH_UPDATE;\n+\tentry-\u003edst = *(__be32 *)n-\u003eprimary_key;\n+\tentry-\u003edst_len = n-\u003etbl-\u003ekey_len * 8;\n+\tentry-\u003emac_valid = 1;\n+\tentry-\u003enud_state = n-\u003enud_state;\n+\tneigh_ha_snapshot(entry-\u003emac, n, n-\u003edev);\n+\n+\tpf_dev = sw_nb_resolve_pf_dev(n-\u003edev);\n+\tif (!pf_dev) {\n+\t\tkfree(entry);\n+\t\treturn NOTIFY_DONE;\n+\t}\n+\n+\tif (netif_is_bridge_master(n-\u003edev)) {\n+\t\tentry-\u003ebridge = 1;\n+\t} else if (is_vlan_dev(n-\u003edev)) {\n+\t\tentry-\u003evlan_valid = 1;\n+\t\tentry-\u003evlan_tag = cpu_to_be16(vlan_dev_vlan_id(n-\u003edev));\n+\t}\n+\n+\tpf = netdev_priv(pf_dev);\n+\tentry-\u003eport_id = pf-\u003epcifunc;\n+\n+\tsw_fib_add_to_list(pf_dev, entry, 1);\n+\treturn NOTIFY_DONE;\n+}\ndiff --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\nnew file mode 100644\nindex 00000000000000..c6dbf4b93a9a2e\n--- /dev/null\n+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.h\n@@ -0,0 +1,21 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/* Marvell switch driver\n+ *\n+ * Copyright (C) 2026 Marvell.\n+ *\n+ */\n+#ifndef SW_NB_V4_H_\n+#define SW_NB_V4_H_\n+\n+int sw_nb_v4_fib_event(struct notifier_block *nb,\n+\t\t       unsigned long event, void *ptr);\n+\n+int sw_nb_net_v4_neigh_update(struct notifier_block *nb,\n+\t\t\t      unsigned long event, void *ptr);\n+\n+int sw_nb_v4_inetaddr_event(struct notifier_block *nb,\n+\t\t\t    unsigned long event, void *ptr);\n+\n+int sw_nb_v4_netdev_event(struct notifier_block *unused,\n+\t\t\t  unsigned long event, void *ptr);\n+#endif // SW_NB_V4_H__\ndiff --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\nnew file mode 100644\nindex 00000000000000..0d94c33c53a66b\n--- /dev/null\n+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v6.c\n@@ -0,0 +1,288 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/* Marvell RVU switch driver\n+ *\n+ * Copyright (C) 2026 Marvell.\n+ *\n+ */\n+#include \u003clinux/kernel.h\u003e\n+#include \u003clinux/netdevice.h\u003e\n+#include \u003clinux/etherdevice.h\u003e\n+#include \u003cnet/switchdev.h\u003e\n+#include \u003cnet/netevent.h\u003e\n+#include \u003cnet/arp.h\u003e\n+#include \u003cnet/route.h\u003e\n+#include \u003clinux/inetdevice.h\u003e\n+#include \u003cnet/addrconf.h\u003e\n+#include \u003cnet/ip6_fib.h\u003e\n+#include \u003cnet/nexthop.h\u003e\n+\n+#include \"../otx2_reg.h\"\n+#include \"../otx2_common.h\"\n+#include \"../otx2_struct.h\"\n+#include \"../cn10k.h\"\n+#include \"sw_nb.h\"\n+#include \"sw_fdb.h\"\n+#include \"sw_fib.h\"\n+#include \"sw_fl.h\"\n+#include \"sw_nb_v6.h\"\n+\n+#if IS_ENABLED(CONFIG_IPV6)\n+\n+int sw_nb_v6_netdev_event(struct notifier_block *unused,\n+\t\t\t  unsigned long event, void *ptr)\n+{\n+\tstruct net_device *dev = netdev_notifier_info_to_dev(ptr);\n+\tstruct netdev_hw_addr *dev_addr;\n+\tstruct net_device *pf_dev;\n+\tstruct inet6_ifaddr *ifp;\n+\tstruct inet6_dev *i6dev;\n+\tstruct fib_entry *entry;\n+\tstruct in6_addr addr;\n+\tstruct otx2_nic *pf;\n+\tu32 prefix_len;\n+\n+\ti6dev = __in6_dev_get(dev);\n+\tif (!i6dev)\n+\t\treturn NOTIFY_DONE;\n+\n+\tif (!sw_nb_is_valid_dev(dev))\n+\t\treturn NOTIFY_DONE;\n+\n+\trcu_read_lock();\n+\tifp = list_first_entry_or_null(\u0026i6dev-\u003eaddr_list,\n+\t\t\t\t       struct inet6_ifaddr, if_list);\n+\tif (!ifp) {\n+\t\trcu_read_unlock();\n+\t\treturn NOTIFY_DONE;\n+\t}\n+\n+\tif (ipv6_addr_type(\u0026ifp-\u003eaddr) \u0026 IPV6_ADDR_LINKLOCAL) {\n+\t\trcu_read_unlock();\n+\t\treturn NOTIFY_DONE;\n+\t}\n+\n+\taddr = ifp-\u003eaddr;\n+\tprefix_len = ifp-\u003eprefix_len;\n+\trcu_read_unlock();\n+\n+\tentry = kcalloc(1, sizeof(*entry), GFP_KERNEL);\n+\tif (!entry)\n+\t\treturn NOTIFY_DONE;\n+\n+\tpf_dev = sw_nb_resolve_pf_dev(dev);\n+\tif (!pf_dev) {\n+\t\tkfree(entry);\n+\t\treturn NOTIFY_DONE;\n+\t}\n+\n+\tentry-\u003ecmd = sw_nb_inetaddr_event_to_otx2_event(event, dev);\n+\tmemcpy(entry-\u003edst6, \u0026addr, sizeof(entry-\u003edst6));\n+\tentry-\u003edst6_plen = prefix_len;\n+\tentry-\u003ehost = 1;\n+\tentry-\u003eipv6 = 1;\n+\n+\tpf = netdev_priv(pf_dev);\n+\tentry-\u003eport_id = pf-\u003epcifunc;\n+\n+\tfor_each_dev_addr(dev, dev_addr) {\n+\t\tentry-\u003emac_valid = 1;\n+\t\tether_addr_copy(entry-\u003emac, dev_addr-\u003eaddr);\n+\t\tbreak;\n+\t}\n+\n+\tnetdev_dbg(dev, \"netdev event addr=%pI6c plen=%u mac=%pM\\n\",\n+\t\t   \u0026addr, prefix_len, entry-\u003emac);\n+\tsw_fib_add_to_list(pf_dev, entry, 1);\n+\treturn NOTIFY_DONE;\n+}\n+\n+int sw_nb_v6_fib_event(struct notifier_block *nb,\n+\t\t       unsigned long event, void *ptr)\n+{\n+\tstruct fib_notifier_info *info = ptr;\n+\tstruct fib6_entry_notifier_info *f6_eni;\n+\tstruct net_device *fib_dev, *pf_dev;\n+\tstruct fib_entry *entry;\n+\tstruct fib6_info *f6i;\n+\tstruct neighbour *neigh;\n+\tstruct fib6_nh *nh6;\n+\tstruct rt6key *key;\n+\tstruct otx2_nic *pf;\n+\n+\tf6_eni = container_of(info, struct fib6_entry_notifier_info, info);\n+\tf6i = f6_eni-\u003ert;\n+\n+\tfib_dev = fib6_info_nh_dev(f6i);\n+\n+\tif (!fib_dev)\n+\t\treturn NOTIFY_DONE;\n+\n+\tif (fib_dev-\u003etype != ARPHRD_ETHER)\n+\t\treturn NOTIFY_DONE;\n+\n+\tif (!sw_nb_is_valid_dev(fib_dev))\n+\t\treturn NOTIFY_DONE;\n+\n+\tif (f6i-\u003efib6_type != RTN_UNICAST)\n+\t\treturn NOTIFY_DONE;\n+\n+\tkey = \u0026f6i-\u003efib6_dst;\n+\t/* TODO: vlan and bridge support */\n+\tif (ipv6_addr_type(\u0026key-\u003eaddr) \u0026 IPV6_ADDR_LINKLOCAL)\n+\t\treturn NOTIFY_DONE;\n+\n+\tnetdev_dbg(fib_dev, \"fib6dst rt6key.addr=%pI6c len=%u\\n\", \u0026key-\u003eaddr,\n+\t\t   key-\u003eplen);\n+\n+\tnetdev_dbg(fib_dev, \"fib6flags=%#x proto=%u type=%u\\n\",\n+\t\t   f6i-\u003efib6_flags, f6i-\u003efib6_protocol, f6i-\u003efib6_type);\n+\n+\tnh6 = f6i-\u003enh ? nexthop_fib6_nh(f6i-\u003enh) : f6i-\u003efib6_nh;\n+\tnetdev_dbg(nh6-\u003efib_nh_dev ? nh6-\u003efib_nh_dev : fib_dev,\n+\t\t   \"nh family=%u dev=%s  gw=%pI6c gwfamily=%u\\n\",\n+\t\t   nh6-\u003efib_nh_family,\n+\t\t   nh6-\u003efib_nh_dev ? nh6-\u003efib_nh_dev-\u003ename : \"No dev\",\n+\t\t   \u0026nh6-\u003efib_nh_gw6, nh6-\u003efib_nh_gw_family);\n+\n+\tpf_dev = sw_nb_resolve_pf_dev(fib_dev);\n+\tif (!pf_dev)\n+\t\treturn NOTIFY_DONE;\n+\n+\tpf = netdev_priv(pf_dev);\n+\n+\tentry = kcalloc(1, sizeof(*entry), GFP_ATOMIC);\n+\tif (!entry)\n+\t\treturn NOTIFY_DONE;\n+\n+\tentry-\u003ecmd = sw_nb_fib_event_to_otx2_event(event, fib_dev);\n+\tentry-\u003eipv6 = 1;\n+\tentry-\u003eport_id = pf-\u003epcifunc;\n+\tmemcpy(entry-\u003edst6, \u0026key-\u003eaddr, sizeof(entry-\u003edst6));\n+\tentry-\u003edst6_plen = key-\u003eplen;\n+\n+\tmemcpy(entry-\u003egw6, \u0026nh6-\u003efib_nh_gw6, sizeof(nh6-\u003efib_nh_gw6));\n+\tentry-\u003egw_valid = !!(ipv6_addr_type(\u0026nh6-\u003efib_nh_gw6) \u0026 IPV6_ADDR_UNICAST);\n+\n+\trcu_read_lock();\n+\tneigh = ip_neigh_gw6(fib_dev, \u0026nh6-\u003efib_nh_gw6);\n+\tif (!neigh) {\n+\t\trcu_read_unlock();\n+\t\tkfree(entry);\n+\t\treturn NOTIFY_DONE;\n+\t}\n+\n+\tif (is_valid_ether_addr(neigh-\u003eha)) {\n+\t\tentry-\u003emac_valid = 1;\n+\t\tneigh_ha_snapshot(entry-\u003emac, neigh, fib_dev);\n+\t\tnetdev_dbg(fib_dev, \"fib found MAC=%pM\\n\", entry-\u003emac);\n+\t}\n+\n+\tsw_fib_add_to_list(pf_dev, entry, 1);\n+\trcu_read_unlock();\n+\n+\treturn NOTIFY_DONE;\n+}\n+\n+int sw_nb_net_v6_neigh_update(struct notifier_block *nb,\n+\t\t\t      unsigned long event, void *ptr)\n+{\n+\tstruct net_device *pf_dev;\n+\tstruct neighbour *n = ptr;\n+\tstruct fib_entry *entry;\n+\tstruct otx2_nic *pf;\n+\n+\tif (n-\u003etbl != \u0026nd_tbl)\n+\t\treturn NOTIFY_DONE;\n+\n+\tif (ipv6_addr_type((struct in6_addr *)n-\u003eprimary_key) \u0026 IPV6_ADDR_LINKLOCAL)\n+\t\treturn NOTIFY_DONE;\n+\n+\tentry = kcalloc(1, sizeof(*entry), GFP_ATOMIC);\n+\tif (!entry)\n+\t\treturn NOTIFY_DONE;\n+\n+\tpf_dev = sw_nb_resolve_pf_dev(n-\u003edev);\n+\tif (!pf_dev) {\n+\t\tkfree(entry);\n+\t\treturn NOTIFY_DONE;\n+\t}\n+\n+\tpf = netdev_priv(pf_dev);\n+\n+\tentry-\u003ecmd = OTX2_NEIGH_UPDATE;\n+\tentry-\u003edst6_plen = n-\u003etbl-\u003ekey_len * 8;\n+\tmemcpy(entry-\u003edst6, (struct in6_addr *)n-\u003eprimary_key,\n+\t       sizeof(entry-\u003edst6));\n+\tentry-\u003eipv6 = 1;\n+\tentry-\u003enud_state = n-\u003enud_state;\n+\tneigh_ha_snapshot(entry-\u003emac, n, n-\u003edev);\n+\tentry-\u003emac_valid = 1;\n+\tentry-\u003eport_id = pf-\u003epcifunc;\n+\n+\tsw_fib_add_to_list(pf_dev, entry, 1);\n+\n+\tnetdev_dbg(n-\u003edev, \"v6 neigh update %pI6c mac=%pM plen=%u\\n\",\n+\t\t   n-\u003eprimary_key, entry-\u003emac, n-\u003etbl-\u003ekey_len * 8);\n+\n+\treturn NOTIFY_DONE;\n+}\n+\n+int sw_nb_v6_inetaddr_event(struct notifier_block *nb,\n+\t\t\t    unsigned long event, void *ptr)\n+{\n+\tstruct inet6_ifaddr *ifa6 = (struct inet6_ifaddr *)ptr;\n+\tstruct net_device *dev = ifa6-\u003eidev-\u003edev;\n+\tstruct netdev_hw_addr *dev_addr;\n+\tstruct net_device *pf_dev;\n+\tstruct fib_entry *entry;\n+\tstruct otx2_nic *pf;\n+\n+\tif (event != NETDEV_CHANGE \u0026\u0026\n+\t    event != NETDEV_UP \u0026\u0026\n+\t    event != NETDEV_DOWN) {\n+\t\treturn NOTIFY_DONE;\n+\t}\n+\n+\tif (dev-\u003etype != ARPHRD_ETHER)\n+\t\treturn NOTIFY_DONE;\n+\n+\tif (!sw_nb_is_valid_dev(dev))\n+\t\treturn NOTIFY_DONE;\n+\n+\tif (ipv6_addr_type(\u0026ifa6-\u003eaddr) \u0026 IPV6_ADDR_LINKLOCAL)\n+\t\treturn NOTIFY_DONE;\n+\n+\tentry = kcalloc(1, sizeof(*entry), GFP_ATOMIC);\n+\tif (!entry)\n+\t\treturn NOTIFY_DONE;\n+\n+\tpf_dev = sw_nb_resolve_pf_dev(dev);\n+\tif (!pf_dev) {\n+\t\tkfree(entry);\n+\t\treturn NOTIFY_DONE;\n+\t}\n+\n+\tpf = netdev_priv(pf_dev);\n+\n+\tentry-\u003ecmd = sw_nb_inetaddr_event_to_otx2_event(event, dev);\n+\tmemcpy(entry-\u003edst6, \u0026ifa6-\u003eaddr, sizeof(entry-\u003edst6));\n+\tentry-\u003edst6_plen = ifa6-\u003eprefix_len;\n+\tentry-\u003emac_valid = 1;\n+\tentry-\u003ehost = 1;\n+\tentry-\u003eipv6 = 1;\n+\tentry-\u003eport_id = pf-\u003epcifunc;\n+\n+\tfor_each_dev_addr(dev, dev_addr) {\n+\t\tether_addr_copy(entry-\u003emac, dev_addr-\u003eaddr);\n+\t\tentry-\u003emac_valid = 1;\n+\t\tbreak;\n+\t}\n+\n+\tsw_fib_add_to_list(pf_dev, entry, 1);\n+\n+\tnetdev_dbg(dev, \"inetaddr addr=%pI6c len=%u %pM\\n\",\n+\t\t   \u0026ifa6-\u003eaddr, ifa6-\u003eprefix_len, entry-\u003emac);\n+\n+\treturn NOTIFY_DONE;\n+}\n+#endif\ndiff --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\nnew file mode 100644\nindex 00000000000000..78c0df5eb880a7\n--- /dev/null\n+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v6.h\n@@ -0,0 +1,50 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/* Marvell switch driver\n+ *\n+ * Copyright (C) 2026 Marvell.\n+ *\n+ */\n+#ifndef SW_NB_V6_H_\n+#define SW_NB_V6_H_\n+\n+#include \u003clinux/kconfig.h\u003e\n+\n+#if IS_ENABLED(CONFIG_IPV6)\n+int sw_nb_v6_fib_event(struct notifier_block *nb,\n+\t\t       unsigned long event, void *ptr);\n+\n+int sw_nb_net_v6_neigh_update(struct notifier_block *nb,\n+\t\t\t      unsigned long event, void *ptr);\n+\n+int sw_nb_v6_inetaddr_event(struct notifier_block *nb,\n+\t\t\t    unsigned long event, void *ptr);\n+\n+int sw_nb_v6_netdev_event(struct notifier_block *unused,\n+\t\t\t  unsigned long event, void *ptr);\n+#else\n+static inline int sw_nb_v6_fib_event(struct notifier_block *nb,\n+\t\t\t\t     unsigned long event, void *ptr)\n+{\n+\treturn NOTIFY_DONE;\n+}\n+\n+static inline int sw_nb_net_v6_neigh_update(struct notifier_block *nb,\n+\t\t\t\t\t    unsigned long event, void *ptr)\n+{\n+\treturn NOTIFY_DONE;\n+}\n+\n+static inline int sw_nb_v6_inetaddr_event(struct notifier_block *nb,\n+\t\t\t\t\t  unsigned long event, void *ptr)\n+{\n+\treturn NOTIFY_DONE;\n+}\n+\n+static inline int sw_nb_v6_netdev_event(struct notifier_block *unused,\n+\t\t\t\t\tunsigned long event, void *ptr)\n+{\n+\treturn NOTIFY_DONE;\n+}\n+#endif\n+\n+#endif /* SW_NB_V6_H_ */\ndiff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_trace.c b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_trace.c\nnew file mode 100644\nindex 00000000000000..672f3405de8519\n--- /dev/null\n+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_trace.c\n@@ -0,0 +1,11 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/* Marvell RVU Admin Function driver\n+ *\n+ * Copyright (C) 2026 Marvell.\n+ *\n+ */\n+\n+#define CREATE_TRACE_POINTS\n+#if IS_ENABLED(CONFIG_OCTEONTX_SWITCH)\n+#include \"sw_trace.h\"\n+#endif\ndiff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_trace.h b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_trace.h\nnew file mode 100644\nindex 00000000000000..3c94f2d0200187\n--- /dev/null\n+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_trace.h\n@@ -0,0 +1,84 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/* Marvell RVU Admin Function driver\n+ *\n+ * Copyright (C) 2026 Marvell.\n+ *\n+ */\n+\n+#undef TRACE_SYSTEM\n+#define TRACE_SYSTEM rvu_sw\n+\n+#if !defined(SW_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)\n+#define SW_TRACE_H\n+\n+#include \u003clinux/types.h\u003e\n+#include \u003clinux/tracepoint.h\u003e\n+#include \u003clinux/byteorder/generic.h\u003e\n+\n+#include \"mbox.h\"\n+\n+TRACE_EVENT(sw_fl_dump,\n+\t    TP_PROTO(const char *fname, const char *info, struct fl_tuple *ftuple),\n+\t    TP_ARGS(fname, info, ftuple),\n+\t    TP_STRUCT__entry(__string(f, fname)\n+\t\t\t     __string(info, info)\n+\t\t\t     __array(u8, smac, ETH_ALEN)\n+\t\t\t     __array(u8, dmac, ETH_ALEN)\n+\t\t\t     __field_struct(__be16, eth_type)\n+\t\t\t     __field_struct(__be32, sip)\n+\t\t\t     __field_struct(__be32, dip)\n+\t\t\t     __field(u8, ip_proto)\n+\t\t\t     __field_struct(__be16, sport)\n+\t\t\t     __field_struct(__be16, dport)\n+\t\t\t     __field(u8, uni_di)\n+\t\t\t     __field(u16, in_pf)\n+\t\t\t     __field(u16, out_pf)\n+\t    ),\n+\t    TP_fast_assign(__assign_str(f);\n+\t\t\t   __assign_str(info);\n+\t\t\t   memcpy(__entry-\u003esmac, ftuple-\u003esmac, ETH_ALEN);\n+\t\t\t   memcpy(__entry-\u003edmac, ftuple-\u003edmac, ETH_ALEN);\n+\t\t\t   __entry-\u003esip = ftuple-\u003eip4src;\n+\t\t\t   __entry-\u003edip = ftuple-\u003eip4dst;\n+\t\t\t   __entry-\u003eeth_type = ftuple-\u003eeth_type;\n+\t\t\t   __entry-\u003eip_proto = ftuple-\u003eproto;\n+\t\t\t   __entry-\u003esport = ftuple-\u003esport;\n+\t\t\t   __entry-\u003edport = ftuple-\u003edport;\n+\t\t\t   __entry-\u003euni_di = ftuple-\u003euni_di;\n+\t\t\t   __entry-\u003ein_pf = ftuple-\u003ein_pf;\n+\t\t\t   __entry-\u003eout_pf = ftuple-\u003exmit_pf;\n+\t    ),\n+\t    TP_printk(\"[%s] %s: %pM %pI4:%u to %pM %pI4:%u eth_type=%#x proto=%u uni=%u in=%#x out=%#x\",\n+\t\t      __get_str(f), __get_str(info),\n+\t\t      __entry-\u003esmac, \u0026__entry-\u003esip, __entry-\u003esport,\n+\t\t      __entry-\u003edmac, \u0026__entry-\u003edip, __entry-\u003edport,\n+\t\t      __entry-\u003eeth_type, __entry-\u003eip_proto, __entry-\u003euni_di,\n+\t\t      __entry-\u003ein_pf, __entry-\u003eout_pf)\n+);\n+\n+TRACE_EVENT(sw_act_dump,\n+\t    TP_PROTO(const char *fname, const char *info, u32 act),\n+\t    TP_ARGS(fname, info, act),\n+\t    TP_STRUCT__entry(__string(fname, fname)\n+\t\t\t     __string(info, info)\n+\t\t\t     __field(u32, act)\n+\t    ),\n+\n+\t    TP_fast_assign(__assign_str(fname);\n+\t\t\t   __assign_str(info);\n+\t\t\t   __entry-\u003eact = act;\n+\t    ),\n+\n+\t    TP_printk(\"[%s] %s: act=%u\",\n+\t\t      __get_str(fname), __get_str(info), __entry-\u003eact)\n+);\n+\n+#endif\n+\n+#undef TRACE_INCLUDE_PATH\n+#define TRACE_INCLUDE_PATH ../../drivers/net/ethernet/marvell/octeontx2/nic/switch/\n+\n+#undef TRACE_INCLUDE_FILE\n+#define TRACE_INCLUDE_FILE sw_trace\n+\n+#include \u003ctrace/define_trace.h\u003e\n"
}

2/1 2026/07/14 05:54 agent patch-evaluator
2m Model:
gemini-3.1-pro-preview

Args:
null

Results:
{
  "EnableConfigs": [
    "OCTEONTX_SWITCH",
    "OCTEONTX2_PF",
    "OCTEONTX2_AF"
  ],
  "FocusSymbols": [
    "rvu_mbox_handler_iface_get_info",
    "rvu_mbox_handler_npc_flow_del_n_free",
    "rvu_mbox_handler_npc_mcam_mul_stats",
    "rvu_mbox_handler_npc_mcam_get_features",
    "rvu_mbox_handler_swdev2af_notify",
    "rvu_mbox_handler_fl_get_stats",
    "rvu_mbox_handler_fl_notify",
    "rvu_mbox_handler_fdb_notify",
    "rvu_mbox_handler_fib_notify",
    "otx2vf_mbox_af2pf_fdb_refresh",
    "otx2_mbox_up_handler_af2pf_fdb_refresh",
    "sw_nb_fdb_event",
    "sw_nb_fib_event",
    "sw_nb_net_event",
    "sw_nb_netdev_event",
    "sw_fl_setup_ft_block_ingress_cb",
    "sw_nb_v4_fib_event",
    "sw_nb_net_v4_neigh_update",
    "sw_nb_v4_inetaddr_event",
    "sw_nb_v4_netdev_event",
    "sw_nb_v6_fib_event",
    "sw_nb_net_v6_neigh_update",
    "sw_nb_v6_inetaddr_event",
    "sw_nb_v6_netdev_event"
  ],
  "Reasoning": "The patch adds support for Marvell OcteonTX2 switch driver, introducing new mailbox messages, switchdev offloading, FDB, FIB, and flow offloading. This is a functional change that adds new reachable code paths.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Do not list too generic configs (we already have them enabled). Only list those that
   specifically cover the modified code. List them in the EnableConfigs output array,
   and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit d7ce965bece40aa4bbe94f1b4b8aeb38abe2e44b
Author: syz-cluster <triage@syzkaller.com>
Date:   Tue Jul 14 05:54:25 2026 +0000

    syz-cluster: applied patch under review

diff --git a/drivers/net/ethernet/marvell/octeontx2/Kconfig b/drivers/net/ethernet/marvell/octeontx2/Kconfig
index 47e549c581f0d2..e2fb6dd71078b2 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/af/Makefile b/drivers/net/ethernet/marvell/octeontx2/af/Makefile
index 91b7d6e96a6148..73f20a44f1a0fa 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.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 f87cdf1b971db1..e8cc7b68ad75e2 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/mbox.h
+++ b/drivers/net/ethernet/marvell/octeontx2/af/mbox.h
@@ -164,6 +164,18 @@ 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)	\
+M(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)		\
@@ -309,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)	\
@@ -438,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
@@ -445,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
 };
 
@@ -1130,6 +1158,13 @@ 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 */
+	/* Set only by the single switchdev PF (rvu->rswitch.pcifunc). This is
+	 * not the eswitch representor (rvu->rep_pcifunc). That PF requests two
+	 * aggregate-level TL2 queues on the PAN link, one for CGX and one for
+	 * SDP steering. No other PF or VF sets this flag.
+	 */
+#define NIX_TXSCH_ALLOC_FLAG_PAN BIT_ULL(0)
+	u64 flags;
 };
 
 struct nix_txsch_alloc_rsp {
@@ -1148,6 +1183,10 @@ struct nix_txsch_alloc_rsp {
 struct nix_txsch_free_req {
 	struct mbox_msghdr hdr;
 #define TXSCHQ_FREE_ALL BIT_ULL(0)
+	/* Frees PAN TL2 queues allocated with NIX_TXSCH_ALLOC_FLAG_PAN. Used
+	 * only by the switchdev PF (rvu->rswitch.pcifunc), not by other PFs/VFs.
+	 */
+#define TXSCHQ_FREE_PAN_TL1 BIT_ULL(1)
 	u16 flags;
 	/* Scheduler queue level to be freed */
 	u16 schq_lvl;
@@ -1581,6 +1620,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;
+	u16 cnt;
+	u16 entry[256]; /* mcam entry */
+};
+
+struct npc_mcam_get_mul_stats_rsp {
+	struct mbox_msghdr hdr;
+	u16 cnt;
+	u64 stat[256];  /* counter stats */
+};
+
 struct npc_mcam_free_entry_req {
 	struct mbox_msghdr hdr;
 	u16 entry; /* Entry index to be freed */
@@ -1782,6 +1845,7 @@ struct get_rep_cnt_rsp {
 struct esw_cfg_req {
 	struct mbox_msghdr hdr;
 	u8 ena;
+	unsigned char switch_id[MAX_PHYS_ITEM_ID_LEN];
 	u64 rsvd;
 };
 
@@ -1807,6 +1871,189 @@ 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;
+	__be16 vlan_tag;
+	u32 dst_len;
+	u8 dst6_plen;
+	u8 gw6_plen;
+	union {
+		__be32 dst;
+		__be32 dst6[4];
+	};
+	union {
+		__be32 gw;
+		__be32 gw6[4];
+	};
+	u16 port_id;
+	u8 nud_state;
+	u8 mac[ETH_ALEN];
+};
+
+struct fib_notify_req {
+	struct  mbox_msghdr hdr;
+	u16 cnt;
+	u16 rsvd[3]; /* explicit padding for entry[] 8-byte alignment */
+	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;
+	u16 rsvd;
+	u64 features;
+	struct {				/* FLOW_ACTION_MANGLE */
+		u8		offset;
+		u8		type;
+		u16		rsvd;
+		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;
+	u64 cookie;
+	u64 flags;
+	u64 features;
+	struct fl_tuple tuple;
+};
+
+struct fl_get_stats_req {
+	struct  mbox_msghdr hdr;
+	u64 cookie;
+};
+
+struct fl_get_stats_rsp {
+	struct  mbox_msghdr hdr;
+	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 {
+			u64 cookie;
+			u64 features;
+			struct fl_tuple tuple;
+		};
+	};
+};
+
+struct af2pf_fdb_refresh_req {
+	struct mbox_msghdr hdr;
+	u16 pcifunc;
+	u8 mac[6];
+	u64 flags;
+};
+
+struct iface_info {
+	u8 is_vf : 1;
+	u8 is_sdp : 1;
+	u8 rsvd : 6;
+	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;
+};
+
+/* Max supported */
+#define IFACE_MAX (256 + 32) /* 32 PFs + 256 VFs */
+
+struct iface_get_info_rsp {
+	struct  mbox_msghdr hdr;
+	u16 cnt;
+	u8 truncated;
+	u8 rsvd[5];
+	struct iface_info info[IFACE_MAX];
+};
+
+struct fl_info {
+	u64 cookie;
+	u16 mcam_idx[2];
+	u8 dis : 1;
+	u8 uni_di : 1;
+};
+
+struct swdev2af_notify_req {
+	struct  mbox_msghdr hdr;
+	u64 msg_type;
+/* Mutually exclusive message selectors (not a combinable bitmask). */
+#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
+			u8 cnt;
+			u8 rsvd[7];
+			struct fl_info fl[64];
+		};
+	};
+};
+
 struct flow_msg {
 	unsigned char dmac[6];
 	unsigned char smac[6];
@@ -1881,6 +2128,10 @@ struct npc_install_flow_req {
 	u8 hw_prio;
 	u8  req_kw_type; /* Key type to be written */
 	u8 alloc_entry;	/* only for cn20k */
+	/* When set, rvu_mbox_handler_npc_install_flow() keeps caller chan_mask
+	 * for switchdev-installed flows instead of the default CPT override.
+	 */
+	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.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu.c
index ffba56ee8a60cd..217ce85033ac43 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"
@@ -1990,6 +1991,153 @@ int rvu_mbox_handler_msix_offset(struct rvu *rvu, struct msg_req *req,
 	return 0;
 }
 
+static bool rvu_iface_get_info_permitted(struct rvu *rvu, u16 pcifunc)
+{
+	if (rvu->rswitch.mode == DEVLINK_ESWITCH_MODE_SWITCHDEV)
+		return false;
+
+	return true;
+}
+
+int rvu_mbox_handler_iface_get_info(struct rvu *rvu, struct msg_req *req,
+				    struct iface_get_info_rsp *rsp)
+{
+	struct iface_info *info;
+	bool truncated = false;
+	struct rvu_pfvf *pfvf;
+	int pf, vf, numvfs;
+	u16 pcifunc;
+	int tot = 0;
+	u64 cfg;
+
+	if (!rvu_iface_get_info_permitted(rvu, req->hdr.pcifunc))
+		return -EPERM;
+
+	memset(rsp, 0, sizeof(*rsp));
+	info = rsp->info;
+	for (pf = 0; pf < rvu->hw->total_pfs; pf++) {
+		if (tot >= IFACE_MAX) {
+			truncated = true;
+			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;
+
+		mutex_lock(&rvu->rsrc_lock);
+		if (pfvf->sq_bmap) {
+			int sq_bmap_bits = rvu_is_switch_pcifunc(rvu, pcifunc) ?
+					   NIX_SQ_BMAP_BITS : pfvf->sq_ctx->qsize;
+
+			info->sq_cnt = bitmap_weight(pfvf->sq_bmap, sq_bmap_bits);
+		}
+
+		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);
+		mutex_unlock(&rvu->rsrc_lock);
+
+		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) {
+				truncated = true;
+				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;
+
+			mutex_lock(&rvu->rsrc_lock);
+			if (pfvf->sq_bmap) {
+				int sq_bmap_bits = rvu_is_switch_pcifunc(rvu, pcifunc) ?
+						   NIX_SQ_BMAP_BITS : pfvf->sq_ctx->qsize;
+
+				info->sq_cnt = bitmap_weight(pfvf->sq_bmap, sq_bmap_bits);
+			}
+
+			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);
+
+			mutex_unlock(&rvu->rsrc_lock);
+
+			if (pfvf->nix_blkaddr == BLKADDR_NIX0)
+				info->nix = 0;
+			else
+				info->nix = 1;
+
+			info++;
+
+			tot++;
+		}
+	}
+done:
+	rsp->cnt = tot;
+	rsp->truncated = truncated;
+
+	return 0;
+}
+
 int rvu_mbox_handler_free_rsrc_cnt(struct rvu *rvu, struct msg_req *req,
 				   struct free_rsrcs_rsp *rsp)
 {
@@ -3712,6 +3860,7 @@ static void rvu_remove(struct pci_dev *pdev)
 	rvu_cgx_exit(rvu);
 	rvu_fwdata_exit(rvu);
 	rvu_mcs_exit(rvu);
+	rvu_sw_shutdown();
 	rvu_mbox_destroy(&rvu->afpf_wq_info);
 	rvu_disable_sriov(rvu);
 	rvu_reset_all_blocks(rvu);
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu.h b/drivers/net/ethernet/marvell/octeontx2/af/rvu.h
index c5610f24268795..0662cc6134b066 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu.h
+++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu.h
@@ -335,6 +335,7 @@ struct nix_txsch {
 	u8   lvl;
 #define NIX_TXSCHQ_FREE		      BIT_ULL(1)
 #define NIX_TXSCHQ_CFG_DONE	      BIT_ULL(0)
+#define NIX_SQ_BMAP_BITS	      (BITS_PER_LONG * 16)
 #define TXSCH_MAP_FUNC(__pfvf_map)    ((__pfvf_map) & 0xFFFF)
 #define TXSCH_MAP_FLAGS(__pfvf_map)   ((__pfvf_map) >> 16)
 #define TXSCH_MAP(__func, __flags)    (((__func) & 0xFFFF) | ((__flags) << 16))
@@ -576,6 +577,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 {
@@ -900,6 +905,11 @@ static inline bool is_pffunc_af(u16 pcifunc)
 	return !pcifunc;
 }
 
+static inline bool rvu_is_switch_pcifunc(struct rvu *rvu, u16 pcifunc)
+{
+	return rvu->rswitch.pcifunc && pcifunc == rvu->rswitch.pcifunc;
+}
+
 static inline bool is_rvu_fwdata_valid(struct rvu *rvu)
 {
 	return (rvu->fwdata->header_magic == RVU_FWDATA_HEADER_MAGIC) &&
@@ -1158,6 +1168,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);
@@ -1196,4 +1207,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_nix.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c
index 78667a0977c013..1a0ba148478e52 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,
@@ -906,6 +905,8 @@ static void nix_setup_lso(struct rvu *rvu, struct nix_hw *nix_hw, int blkaddr)
 
 static void nix_ctx_free(struct rvu *rvu, struct rvu_pfvf *pfvf)
 {
+	mutex_lock(&rvu->rsrc_lock);
+
 	kfree(pfvf->rq_bmap);
 	kfree(pfvf->sq_bmap);
 	kfree(pfvf->cq_bmap);
@@ -931,6 +932,8 @@ static void nix_ctx_free(struct rvu *rvu, struct rvu_pfvf *pfvf)
 	pfvf->rss_ctx = NULL;
 	pfvf->nix_qints_ctx = NULL;
 	pfvf->cq_ints_ctx = NULL;
+
+	mutex_unlock(&rvu->rsrc_lock);
 }
 
 static int nixlf_rss_ctx_init(struct rvu *rvu, int blkaddr,
@@ -1597,7 +1600,11 @@ 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);
+	if (rvu_is_switch_pcifunc(rvu, pcifunc))
+		pfvf->sq_bmap = kcalloc(BITS_TO_LONGS(NIX_SQ_BMAP_BITS),
+					sizeof(long), GFP_KERNEL);
+	else
+		pfvf->sq_bmap = kcalloc(req->sq_cnt, sizeof(long), GFP_KERNEL);
 	if (!pfvf->sq_bmap) {
 		rc = -ENOMEM;
 		goto free_mem;
@@ -2087,7 +2094,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);
@@ -2124,6 +2131,25 @@ static void nix_get_txschq_range(struct rvu *rvu, u16 pcifunc,
 	}
 }
 
+static int nix_get_pan_tx_link(struct rvu *rvu)
+{
+	struct rvu_hwinfo *hw = rvu->hw;
+
+	return hw->cgx_links + hw->lbk_links + 1;
+}
+
+static bool nix_txsch_is_pan_schq(struct rvu *rvu, int schq)
+{
+	int pan_link = nix_get_pan_tx_link(rvu);
+
+	return schq >= pan_link && schq <= pan_link + 1;
+}
+
+static bool nix_txsch_pan_allowed(struct rvu *rvu, u16 pcifunc)
+{
+	return rvu_is_switch_pcifunc(rvu, pcifunc);
+}
+
 static int nix_check_txschq_alloc_req(struct rvu *rvu, int lvl, u16 pcifunc,
 				      struct nix_hw *nix_hw,
 				      struct nix_txsch_alloc_req *req)
@@ -2139,12 +2165,27 @@ 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) {
+		if (!nix_txsch_pan_allowed(rvu, pcifunc))
+			return NIX_AF_ERR_TLX_ALLOC_FAIL;
+		link = nix_get_pan_tx_link(rvu);
+	} 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;
+		if (req->schq[lvl] > MAX_TXSCHQ_PER_FUNC ||
+		    req->schq_contig[lvl] > MAX_TXSCHQ_PER_FUNC)
+			return NIX_AF_ERR_TLX_ALLOC_FAIL;
+		if (req->flags & NIX_TXSCH_ALLOC_FLAG_PAN) {
+			if (link >= txsch->schq.max || link + 1 >= txsch->schq.max)
+				return NIX_AF_ERR_TLX_ALLOC_FAIL;
+			if (req_schq > 2)
+				return NIX_AF_ERR_TLX_ALLOC_FAIL;
+		}
 		return 0;
 	}
 
@@ -2173,9 +2214,9 @@ static int nix_check_txschq_alloc_req(struct rvu *rvu, int lvl, u16 pcifunc,
 	return 0;
 }
 
-static void nix_txsch_alloc(struct rvu *rvu, struct nix_txsch *txsch,
-			    struct nix_txsch_alloc_rsp *rsp,
-			    int lvl, int start, int end)
+static int nix_txsch_alloc(struct rvu *rvu, struct nix_txsch *txsch,
+			   struct nix_txsch_alloc_rsp *rsp,
+			   int lvl, int start, int end)
 {
 	struct rvu_hwinfo *hw = rvu->hw;
 	u16 pcifunc = rsp->hdr.pcifunc;
@@ -2185,6 +2226,46 @@ static void nix_txsch_alloc(struct rvu *rvu, struct nix_txsch *txsch,
 	 * on transmit link to which PF_FUNC is mapped to.
 	 */
 	if (lvl >= hw->cap.nix_tx_aggr_lvl) {
+		if (start != end) {
+			int want_contig = rsp->schq_contig[lvl];
+			int got_contig = 0, got = 0;
+			int want = rsp->schq[lvl];
+
+			for (schq = start; schq <= end; schq++) {
+				if (test_bit(schq, txsch->schq.bmap))
+					continue;
+
+				if (got_contig < want_contig) {
+					set_bit(schq, txsch->schq.bmap);
+					rsp->schq_contig_list[lvl][got_contig++] = schq;
+					continue;
+				}
+
+				if (got < want) {
+					set_bit(schq, txsch->schq.bmap);
+					rsp->schq_list[lvl][got++] = schq;
+				}
+			}
+
+			rsp->schq_contig[lvl] = got_contig;
+			rsp->schq[lvl] = got;
+
+			if (got_contig < want_contig || got < want) {
+				for (idx = 0; idx < got_contig; idx++)
+					clear_bit(rsp->schq_contig_list[lvl][idx],
+						  txsch->schq.bmap);
+				for (idx = 0; idx < got; idx++)
+					clear_bit(rsp->schq_list[lvl][idx],
+						  txsch->schq.bmap);
+				rsp->schq_contig[lvl] = 0;
+				rsp->schq[lvl] = 0;
+				dev_err(rvu->dev,
+					"Could not allocate schq at lvl=%u start=%u end=%u\n",
+					lvl, start, end);
+				return -ENOMEM;
+			}
+			return 0;
+		}
 		/* A single TL queue is allocated */
 		if (rsp->schq_contig[lvl]) {
 			rsp->schq_contig[lvl] = 1;
@@ -2199,7 +2280,7 @@ static void nix_txsch_alloc(struct rvu *rvu, struct nix_txsch *txsch,
 			rsp->schq[lvl] = 1;
 			rsp->schq_list[lvl][0] = start;
 		}
-		return;
+		return 0;
 	}
 
 	/* Adjust the queue request count if HW supports
@@ -2211,7 +2292,7 @@ static void nix_txsch_alloc(struct rvu *rvu, struct nix_txsch *txsch,
 		if (idx >= (end - start) || test_bit(schq, txsch->schq.bmap)) {
 			rsp->schq_contig[lvl] = 0;
 			rsp->schq[lvl] = 0;
-			return;
+			return 0;
 		}
 
 		if (rsp->schq_contig[lvl]) {
@@ -2224,7 +2305,7 @@ static void nix_txsch_alloc(struct rvu *rvu, struct nix_txsch *txsch,
 			set_bit(schq, txsch->schq.bmap);
 			rsp->schq_list[lvl][0] = schq;
 		}
-		return;
+		return 0;
 	}
 
 	/* Allocate contiguous queue indices requesty first */
@@ -2255,6 +2336,8 @@ static void nix_txsch_alloc(struct rvu *rvu, struct nix_txsch *txsch,
 		/* Update how many were allocated */
 		rsp->schq[lvl] = idx;
 	}
+
+	return 0;
 }
 
 int rvu_mbox_handler_nix_txsch_alloc(struct rvu *rvu,
@@ -2279,6 +2362,10 @@ int rvu_mbox_handler_nix_txsch_alloc(struct rvu *rvu,
 	if (!nix_hw)
 		return NIX_AF_ERR_INVALID_NIXBLK;
 
+	if ((req->flags & NIX_TXSCH_ALLOC_FLAG_PAN) &&
+	    !nix_txsch_pan_allowed(rvu, pcifunc))
+		return NIX_AF_ERR_TLX_ALLOC_FAIL;
+
 	mutex_lock(&rvu->rsrc_lock);
 
 	/* Check if request is valid as per HW capabilities
@@ -2301,11 +2388,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 = nix_get_pan_tx_link(rvu);
+		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 {
@@ -2313,10 +2403,11 @@ int rvu_mbox_handler_nix_txsch_alloc(struct rvu *rvu,
 			end = txsch->schq.max;
 		}
 
-		nix_txsch_alloc(rvu, txsch, rsp, lvl, start, end);
+		if (nix_txsch_alloc(rvu, txsch, rsp, lvl, start, end))
+			goto err;
 
 		/* Reset queue config */
-		for (idx = 0; idx < req->schq_contig[lvl]; idx++) {
+		for (idx = 0; idx < rsp->schq_contig[lvl]; idx++) {
 			schq = rsp->schq_contig_list[lvl][idx];
 			if (!(TXSCH_MAP_FLAGS(pfvf_map[schq]) &
 			    NIX_TXSCHQ_CFG_DONE))
@@ -2326,7 +2417,7 @@ int rvu_mbox_handler_nix_txsch_alloc(struct rvu *rvu,
 			nix_reset_tx_schedule(rvu, blkaddr, lvl, schq);
 		}
 
-		for (idx = 0; idx < req->schq[lvl]; idx++) {
+		for (idx = 0; idx < rsp->schq[lvl]; idx++) {
 			schq = rsp->schq_list[lvl][idx];
 			if (!(TXSCH_MAP_FLAGS(pfvf_map[schq]) &
 			    NIX_TXSCHQ_CFG_DONE))
@@ -2622,11 +2713,11 @@ static int nix_txschq_free(struct rvu *rvu, u16 pcifunc)
 		 /* TLs above aggregation level are shared across all PF
 		  * and it's VFs, hence skip freeing them.
 		  */
-		if (lvl >= hw->cap.nix_tx_aggr_lvl)
-			continue;
-
 		txsch = &nix_hw->txsch[lvl];
 		for (schq = 0; schq < txsch->schq.max; schq++) {
+			if (lvl >= hw->cap.nix_tx_aggr_lvl &&
+			    !nix_txsch_is_pan_schq(rvu, schq))
+				continue;
 			if (TXSCH_MAP_FUNC(txsch->pfvf_map[schq]) != pcifunc)
 				continue;
 			nix_reset_tx_schedule(rvu, blkaddr, lvl, schq);
@@ -2670,7 +2761,16 @@ 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 (req->flags & TXSCHQ_FREE_PAN_TL1) {
+		if (!nix_txsch_pan_allowed(rvu, pcifunc))
+			return NIX_AF_ERR_TLX_INVALID;
+		if (!nix_txsch_is_pan_schq(rvu, schq))
+			return NIX_AF_ERR_TLX_INVALID;
+	} else if (lvl >= hw->cap.nix_tx_aggr_lvl) {
+		return 0;
+	}
+
+	if (schq >= txsch->schq.max)
 		return 0;
 
 	pfvf_map = txsch->pfvf_map;
diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c
index 08b83de9beb463..b17da72250a367 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c
+++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c
@@ -3544,6 +3544,40 @@ 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=%u\n", mreq->cnt);
+		return -EINVAL;
+	}
+
+	for (i = 0; i < mreq->cnt; i++) {
+		dreq.entry = mreq->entry[i];
+		rvu_mbox_handler_npc_delete_flow(rvu, &dreq, &drsp);
+
+		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)
@@ -4398,6 +4432,72 @@ 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;
+	int blkaddr, cnt = 0, i;
+	u16 index, cntr, entry;
+	u64 regval;
+	u32 bank;
+
+	if (!req->cnt || req->cnt > 256) {
+		dev_err(rvu->dev, "%s invalid request cnt=%u\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 (i = 0; i < req->cnt; i++) {
+		entry = npc_cn20k_vidx2idx(req->entry[i]);
+
+		if (npc_mcam_verify_entry(mcam, pcifunc, entry)) {
+			mutex_unlock(&mcam->lock);
+			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);
+
+		if (is_cn20k(rvu->pdev)) {
+			regval = rvu_read64(rvu, blkaddr,
+					    NPC_AF_CN20K_MCAMEX_BANKX_STAT_EXT(index,
+									       bank));
+			rsp->stat[cnt] = regval;
+			cnt++;
+			continue;
+		}
+
+		/* read MCAM entry STAT_ACT register */
+		regval = rvu_read64(rvu, blkaddr, NPC_AF_MCAMEX_BANKX_STAT_ACT(index, bank));
+
+		if (!(regval & rvu->hw->npc_stat_ena)) {
+			rsp->stat[cnt] = 0;
+			cnt++;
+			continue;
+		}
+
+		cntr = regval & 0x1FF;
+
+		rsp->stat[cnt] = rvu_read64(rvu, blkaddr, NPC_AF_MATCH_STATX(cntr));
+		rsp->stat[cnt] &= BIT_ULL(48) - 1;
+		cnt++;
+	}
+
+	rsp->cnt = 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 91b5947dae0600..03bc3e321522fb 100644
--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc_fs.c
+++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc_fs.c
@@ -1828,8 +1828,11 @@ int rvu_mbox_handler_npc_install_flow(struct rvu *rvu,
 		target = req->hdr.pcifunc;
 	}
 
-	/* ignore chan_mask in case pf func is not AF, revisit later */
-	if (!is_pffunc_af(req->hdr.pcifunc))
+	/* Non-AF requesters normally get the CPT default chan_mask. set_chanmask
+	 * preserves caller-supplied chan_mask for switchdev-installed flows; see
+	 * npc_install_flow_req.set_chanmask.
+	 */
+	if (!is_pffunc_af(req->hdr.pcifunc) && !req->set_chanmask)
 		req->chan_mask = rvu_get_cpt_chan_mask(rvu);
 
 	err = npc_check_unsupported_flows(rvu, req->features, req->intf);
@@ -1926,6 +1929,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/rvu_rep.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_rep.c
index a2781e0f504e3e..6bb6064b2391b4 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
new file mode 100644
index 00000000000000..f233fe7b2983a9
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.c
@@ -0,0 +1,88 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Marvell RVU Admin Function driver
+ *
+ * Copyright (C) 2026 Marvell.
+ *
+ */
+
+#include <linux/bitfield.h>
+#include "rvu.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 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;
+}
+
+static bool rvu_sw_swdev2af_msg_valid(u64 msg_type)
+{
+	return msg_type == SWDEV2AF_MSG_TYPE_FW_STATUS ||
+	       msg_type == SWDEV2AF_MSG_TYPE_REFRESH_FDB ||
+	       msg_type == SWDEV2AF_MSG_TYPE_REFRESH_FL;
+}
+
+static int rvu_sw_swdev2af_sender_check(struct rvu *rvu,
+					struct swdev2af_notify_req *req,
+					u64 msg_type)
+{
+	u16 sender = req->hdr.pcifunc;
+
+	if (!rvu_sw_swdev2af_msg_valid(msg_type))
+		return -EINVAL;
+
+	if (!rvu_is_switch_pcifunc(rvu, sender))
+		return -EPERM;
+
+	return 0;
+}
+
+int rvu_mbox_handler_swdev2af_notify(struct rvu *rvu,
+				     struct swdev2af_notify_req *req,
+				     struct msg_rsp *rsp)
+{
+	int rc = 0;
+
+	rc = rvu_sw_swdev2af_sender_check(rvu, req, req->msg_type);
+	if (rc)
+		return rc;
+
+	switch (req->msg_type) {
+	case SWDEV2AF_MSG_TYPE_FW_STATUS:
+		rc = rvu_sw_l2_init_offl_wq(rvu, req->hdr.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;
+
+	case SWDEV2AF_MSG_TYPE_REFRESH_FL:
+		if (req->cnt <= 0 || req->cnt > ARRAY_SIZE(req->fl))
+			return -EINVAL;
+		rc = rvu_sw_fl_stats_sync2db(rvu, req->fl, req->cnt);
+		break;
+
+	default:
+		rc = -EOPNOTSUPP;
+		break;
+	}
+
+	return rc;
+}
+
+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.h b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.h
new file mode 100644
index 00000000000000..a0cb2a9ce7ab43
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Marvell RVU Admin Function driver
+ *
+ * Copyright (C) 2026 Marvell.
+ *
+ */
+
+#ifndef RVU_SWITCH_H
+#define RVU_SWITCH_H
+
+/* RVU Switch */
+#define RVU_SW_INVALID_PORT_ID	((u32)~0U)
+
+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_fl.c b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_fl.c
new file mode 100644
index 00000000000000..34e23d57e64938
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_fl.c
@@ -0,0 +1,352 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Marvell RVU Admin Function driver
+ *
+ * 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
+
+#define RVU_SW_FL_REFRESH_MAX						\
+	((int)ARRAY_SIZE(((struct swdev2af_notify_req *)0)->fl))
+
+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 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 void rvu_sw_fl_queue_work(void)
+{
+	if (sw_fl_offl_wq)
+		queue_delayed_work(sw_fl_offl_wq, &fl_offl_work,
+				   msecs_to_jiffies(10));
+}
+
+static int rvu_sw_fl_ensure_wq(void)
+{
+	if (sw_fl_offl_wq)
+		return 0;
+
+	sw_fl_offl_wq = alloc_workqueue("sw_af_fl_wq", 0, 0);
+	if (!sw_fl_offl_wq)
+		return -ENOMEM;
+
+	return 0;
+}
+
+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 i, idx;
+	int rc = 0;
+	u64 pkts;
+
+	if (cnt <= 0 || cnt > RVU_SW_FL_REFRESH_MAX)
+		return -EINVAL;
+
+	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;
+	}
+
+	idx = 0;
+	for (i = 0; i < cnt; i++) {
+		req->entry[idx++] = fl[i].mcam_idx[0];
+		if (!fl[i].uni_di)
+			req->entry[idx++] = fl[i].mcam_idx[1];
+	}
+	req->cnt = idx;
+
+	if (idx > 256) {
+		rc = -EINVAL;
+		goto fail;
+	}
+
+	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;
+	}
+
+	idx = 0;
+	for (i = 0; i < cnt; i++) {
+		pkts = rsp->stat[idx++];
+		if (!fl[i].uni_di)
+			pkts += rsp->stat[idx++];
+
+		rc |= rvu_sw_fl_stats_sync2db_one_entry(fl[i].cookie, fl[i].dis,
+							fl[i].mcam_idx,
+							fl[i].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;
+
+	if (!otx2_mbox_wait_for_zero(&rvu->afpf_wq_info.mbox_up, swdev_pf)) {
+		mutex_unlock(&rvu->mbox_lock);
+		return -EBUSY;
+	}
+
+	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);
+
+	if (rvu_sw_fl_offl_rule_push(fl_entry)) {
+		mutex_lock(&fl_offl_llock);
+		list_add_tail(&fl_entry->list, &fl_offl_lh);
+		mutex_unlock(&fl_offl_llock);
+		if (sw_fl_offl_wq)
+			queue_delayed_work(sw_fl_offl_wq, &fl_offl_work,
+					   msecs_to_jiffies(100));
+		return;
+	}
+
+	kfree(fl_entry);
+
+	mutex_lock(&fl_offl_llock);
+	if (!list_empty(&fl_offl_lh))
+		rvu_sw_fl_queue_work();
+	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;
+}
+
+int rvu_mbox_handler_fl_notify(struct rvu *rvu,
+			       struct fl_notify_req *req,
+			       struct msg_rsp *rsp)
+{
+	struct fl_entry *fl_entry;
+	int rc;
+
+	if (!(rvu->rswitch.flags & RVU_SWITCH_FLAG_FW_READY))
+		return -EAGAIN;
+
+	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);
+	rc = rvu_sw_fl_ensure_wq();
+	if (rc) {
+		mutex_unlock(&fl_offl_llock);
+		kfree(fl_entry);
+		return rc;
+	}
+
+	list_add_tail(&fl_entry->list, &fl_offl_lh);
+	rvu_sw_fl_queue_work();
+	mutex_unlock(&fl_offl_llock);
+
+	return 0;
+}
+
+void rvu_sw_fl_shutdown(void)
+{
+	struct sw_fl_stats_node *snode, *tmp;
+	struct workqueue_struct *wq;
+	struct fl_entry *entry;
+
+	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);
+
+	if (!sw_fl_offl_wq)
+		return;
+
+	cancel_delayed_work_sync(&fl_offl_work);
+	wq = sw_fl_offl_wq;
+	sw_fl_offl_wq = NULL;
+	destroy_workqueue(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);
+}
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 00000000000000..f117a96fc33e7b
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_fl.h
@@ -0,0 +1,13 @@
+/* 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
+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/af/switch/rvu_sw_l2.c b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l2.c
new file mode 100644
index 00000000000000..f61b2d15768b13
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l2.c
@@ -0,0 +1,500 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Marvell RVU Admin Function driver
+ *
+ * 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
+
+#define RVU_SW_L2_LIST_MAX 4096
+
+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 atomic_t l2_offl_list_cnt = ATOMIC_INIT(0);
+
+static DEFINE_MUTEX(fdb_refresh_list_lock);
+static LIST_HEAD(fdb_refresh_lh);
+static atomic_t fdb_refresh_list_cnt = ATOMIC_INIT(0);
+
+struct rvu_sw_l2_work {
+	struct rvu *rvu;
+	struct work_struct work;
+};
+
+/* Work queue for switchdev message handling. There is only
+ * one 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;
+
+static bool fw_is_up;
+static DEFINE_SPINLOCK(rvu_sw_l2_state_lock);
+
+static void rvu_sw_l2_list_cnt_warn(struct device *dev, atomic_t *cnt,
+				    const char *name)
+{
+	int n = atomic_read(cnt);
+
+	if (n < 0)
+		dev_warn(dev, "L2 %s list count underflow: %d\n", name, n);
+	else if (n > RVU_SW_L2_LIST_MAX)
+		dev_warn(dev, "L2 %s list count overflow: %d (max %d)\n",
+			 name, n, RVU_SW_L2_LIST_MAX);
+}
+
+static void rvu_sw_l2_list_cnt_inc(struct device *dev, atomic_t *cnt,
+				   const char *name)
+{
+	atomic_inc(cnt);
+	rvu_sw_l2_list_cnt_warn(dev, cnt, name);
+}
+
+static void rvu_sw_l2_list_cnt_dec(struct device *dev, atomic_t *cnt,
+				   const char *name)
+{
+	atomic_dec(cnt);
+	rvu_sw_l2_list_cnt_warn(dev, cnt, name);
+}
+
+static void rvu_sw_l2_destroy_wqs(struct rvu *rvu)
+{
+	struct workqueue_struct *offl_wq, *refresh_wq;
+	struct l2_entry *entry;
+
+	spin_lock_bh(&rvu_sw_l2_state_lock);
+	rvu->rswitch.flags &= ~RVU_SWITCH_FLAG_FW_READY;
+	rvu->rswitch.pcifunc = 0;
+	fw_is_up = false;
+	offl_wq = rvu_sw_l2_offl_wq;
+	refresh_wq = fdb_refresh_wq;
+	rvu_sw_l2_offl_wq = NULL;
+	fdb_refresh_wq = NULL;
+	spin_unlock_bh(&rvu_sw_l2_state_lock);
+
+	if (refresh_wq) {
+		cancel_work_sync(&fdb_refresh_work.work);
+		destroy_workqueue(refresh_wq);
+
+		mutex_lock(&fdb_refresh_list_lock);
+		rvu_sw_l2_list_cnt_warn(rvu->dev, &fdb_refresh_list_cnt,
+					"fdb refresh");
+		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);
+		}
+		atomic_set(&fdb_refresh_list_cnt, 0);
+		mutex_unlock(&fdb_refresh_list_lock);
+	}
+
+	if (offl_wq) {
+		cancel_work_sync(&l2_offl_work.work);
+		destroy_workqueue(offl_wq);
+
+		mutex_lock(&l2_offl_list_lock);
+		rvu_sw_l2_list_cnt_warn(rvu->dev, &l2_offl_list_cnt, "offload");
+		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);
+		}
+		atomic_set(&l2_offl_list_cnt, 0);
+		mutex_unlock(&l2_offl_list_lock);
+	}
+}
+
+/* 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.  When processing an ADD,
+ * drop a queued DELETE for the same MAC that has not yet been sent to
+ * hardware; the ADD reflects the desired final state and supersedes it.
+ */
+static void rvu_sw_l2_offl_drop_pending_del(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);
+		rvu_sw_l2_list_cnt_dec(l2_offl_work.rvu->dev, &l2_offl_list_cnt,
+				       "offload");
+		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_send(struct rvu *rvu, u16 pcifunc, u8 *mac)
+{
+	struct af2pf_fdb_refresh_req *req;
+	int pf, vidx;
+
+	if (!is_pf_func_valid(rvu, pcifunc))
+		return -EINVAL;
+
+	pf = rvu_get_pf(rvu->pdev, pcifunc);
+
+	mutex_lock(&rvu->mbox_lock);
+
+	if (pf) {
+		if (pf >= rvu->afpf_wq_info.mbox_up.ndevs) {
+			mutex_unlock(&rvu->mbox_lock);
+			return -EINVAL;
+		}
+
+		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;
+		req->flags = FDB_ADD;
+
+		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;
+
+		if (vidx < 0 || vidx >= rvu->afvf_wq_info.mbox_up.ndevs) {
+			mutex_unlock(&rvu->mbox_lock);
+			return -EINVAL;
+		}
+
+		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;
+		req->flags = FDB_ADD;
+
+		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);
+		rvu_sw_l2_list_cnt_dec(fdb_work->rvu->dev, &fdb_refresh_list_cnt,
+				       "fdb refresh");
+		mutex_unlock(&fdb_refresh_list_lock);
+
+		rvu_sw_l2_fdb_refresh_send(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);
+		rvu_sw_l2_list_cnt_dec(offl_work->rvu->dev, &l2_offl_list_cnt,
+				       "offload");
+		mutex_unlock(&l2_offl_list_lock);
+
+		add_fdb = !!(l2_entry->flags & FDB_ADD);
+
+		if (add_fdb)
+			rvu_sw_l2_offl_drop_pending_del(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);
+	}
+
+	spin_lock_bh(&rvu_sw_l2_state_lock);
+	if (rvu_sw_l2_offl_wq && atomic_read(&l2_offl_list_cnt))
+		queue_work(rvu_sw_l2_offl_wq, &l2_offl_work.work);
+	spin_unlock_bh(&rvu_sw_l2_state_lock);
+}
+
+int rvu_sw_l2_init_offl_wq(struct rvu *rvu, u16 pcifunc, bool fw_up)
+{
+	struct rvu_switch *rswitch = &rvu->rswitch;
+
+	if (!fw_up) {
+		rvu_sw_l2_destroy_wqs(rvu);
+		return 0;
+	}
+
+	spin_lock_bh(&rvu_sw_l2_state_lock);
+	if (fw_is_up && rvu_sw_l2_offl_wq && fdb_refresh_wq) {
+		rswitch->pcifunc = pcifunc;
+		rswitch->flags |= RVU_SWITCH_FLAG_FW_READY;
+		spin_unlock_bh(&rvu_sw_l2_state_lock);
+		return 0;
+	}
+	spin_unlock_bh(&rvu_sw_l2_state_lock);
+
+	if (rvu_sw_l2_offl_wq || fdb_refresh_wq)
+		rvu_sw_l2_destroy_wqs(rvu);
+
+	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_refresh_wq", 0, 0);
+	if (!fdb_refresh_wq) {
+		dev_err(rvu->dev, "fdb refresh workqueue allocation failed\n");
+		destroy_workqueue(rvu_sw_l2_offl_wq);
+		rvu_sw_l2_offl_wq = NULL;
+		return -ENOMEM;
+	}
+
+	spin_lock_bh(&rvu_sw_l2_state_lock);
+	fw_is_up = true;
+	rswitch->pcifunc = pcifunc;
+	rswitch->flags |= RVU_SWITCH_FLAG_FW_READY;
+	spin_unlock_bh(&rvu_sw_l2_state_lock);
+
+	return 0;
+}
+
+int rvu_sw_l2_fdb_list_entry_add(struct rvu *rvu, u16 pcifunc, u8 *mac)
+{
+	struct workqueue_struct *wq;
+	struct l2_entry *l2_entry;
+
+	if (!is_pf_func_valid(rvu, pcifunc))
+		return -EINVAL;
+
+	spin_lock_bh(&rvu_sw_l2_state_lock);
+	if (!fdb_refresh_wq) {
+		spin_unlock_bh(&rvu_sw_l2_state_lock);
+		return -EINVAL;
+	}
+	spin_unlock_bh(&rvu_sw_l2_state_lock);
+
+	if (atomic_read(&fdb_refresh_list_cnt) >= RVU_SW_L2_LIST_MAX) {
+		rvu_sw_l2_list_cnt_warn(rvu->dev, &fdb_refresh_list_cnt,
+					"fdb refresh");
+		return -ENOMEM;
+	}
+
+	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);
+	if (atomic_read(&fdb_refresh_list_cnt) >= RVU_SW_L2_LIST_MAX) {
+		rvu_sw_l2_list_cnt_warn(rvu->dev, &fdb_refresh_list_cnt,
+					"fdb refresh");
+		mutex_unlock(&fdb_refresh_list_lock);
+		kfree(l2_entry);
+		return -ENOMEM;
+	}
+	list_add_tail(&l2_entry->list, &fdb_refresh_lh);
+	rvu_sw_l2_list_cnt_inc(rvu->dev, &fdb_refresh_list_cnt, "fdb refresh");
+	mutex_unlock(&fdb_refresh_list_lock);
+
+	spin_lock_bh(&rvu_sw_l2_state_lock);
+	wq = fdb_refresh_wq;
+	if (wq)
+		queue_work(wq, &fdb_refresh_work.work);
+	spin_unlock_bh(&rvu_sw_l2_state_lock);
+
+	if (!wq) {
+		mutex_lock(&fdb_refresh_list_lock);
+		list_del_init(&l2_entry->list);
+		rvu_sw_l2_list_cnt_dec(rvu->dev, &fdb_refresh_list_cnt,
+				       "fdb refresh");
+		mutex_unlock(&fdb_refresh_list_lock);
+		kfree(l2_entry);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+int rvu_mbox_handler_fdb_notify(struct rvu *rvu,
+				struct fdb_notify_req *req,
+				struct msg_rsp *rsp)
+{
+	struct workqueue_struct *wq;
+	struct l2_entry *l2_entry;
+
+	spin_lock_bh(&rvu_sw_l2_state_lock);
+	if (!(rvu->rswitch.flags & RVU_SWITCH_FLAG_FW_READY) ||
+	    !rvu_sw_l2_offl_wq) {
+		spin_unlock_bh(&rvu_sw_l2_state_lock);
+		return 0;
+	}
+	spin_unlock_bh(&rvu_sw_l2_state_lock);
+
+	if (atomic_read(&l2_offl_list_cnt) >= RVU_SW_L2_LIST_MAX) {
+		rvu_sw_l2_list_cnt_warn(rvu->dev, &l2_offl_list_cnt, "offload");
+		return -ENOMEM;
+	}
+
+	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);
+	if (atomic_read(&l2_offl_list_cnt) >= RVU_SW_L2_LIST_MAX) {
+		rvu_sw_l2_list_cnt_warn(rvu->dev, &l2_offl_list_cnt, "offload");
+		mutex_unlock(&l2_offl_list_lock);
+		kfree(l2_entry);
+		return -ENOMEM;
+	}
+	list_add_tail(&l2_entry->list, &l2_offl_lh);
+	rvu_sw_l2_list_cnt_inc(rvu->dev, &l2_offl_list_cnt, "offload");
+	mutex_unlock(&l2_offl_list_lock);
+
+	spin_lock_bh(&rvu_sw_l2_state_lock);
+	wq = rvu_sw_l2_offl_wq;
+	if (wq)
+		queue_work(wq, &l2_offl_work.work);
+	spin_unlock_bh(&rvu_sw_l2_state_lock);
+
+	if (!wq) {
+		mutex_lock(&l2_offl_list_lock);
+		list_del_init(&l2_entry->list);
+		rvu_sw_l2_list_cnt_dec(rvu->dev, &l2_offl_list_cnt, "offload");
+		mutex_unlock(&l2_offl_list_lock);
+		kfree(l2_entry);
+	}
+
+	return 0;
+}
+
+void rvu_sw_l2_shutdown(void)
+{
+	if (!fdb_refresh_wq && !rvu_sw_l2_offl_wq)
+		return;
+
+	rvu_sw_l2_destroy_wqs(l2_offl_work.rvu);
+}
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 00000000000000..6685431d60a236
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l2.h
@@ -0,0 +1,14 @@
+/* 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
+
+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/af/switch/rvu_sw_l3.c b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l3.c
new file mode 100644
index 00000000000000..b9ba5ea1aaef89
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l3.c
@@ -0,0 +1,259 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Marvell RVU Admin Function driver
+ *
+ * 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
+
+#define RVU_SW_L3_BATCH_MAX						\
+	((int)(sizeof_field(struct af2swdev_notify_req, entry) /	\
+	       sizeof(struct fib_entry)))
+
+struct l3_entry {
+	struct list_head list;
+	/* Always this AF driver's rvu; stored for clarity only (single RVU). */
+	struct rvu *rvu;
+	u32 port_id;
+	int cnt;
+	struct fib_entry entry[];
+};
+
+static DEFINE_MUTEX(l3_offl_llock);
+/*
+ * Pending FIB updates from host PFs on this RVU. The list is per-AF-module
+ * (one octeontx2-af instance / one RVU chip), not shared across RVU devices.
+ */
+static LIST_HEAD(l3_offl_lh);
+
+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);
+
+/*
+ * FIB offload to the switch ASIC: one octeontx2 AF driver instance, one
+ * switch PF (switchdev), and one sw_l3_offl_wq per SoC.
+ */
+
+static void rvu_sw_l3_drain_list(struct list_head *lh)
+{
+	struct l3_entry *entry;
+
+	while ((entry = list_first_entry_or_null(lh, struct l3_entry, list))) {
+		list_del(&entry->list);
+		kfree(entry);
+	}
+}
+
+static void rvu_sw_l3_queue_work(void)
+{
+	if (sw_l3_offl_wq)
+		queue_delayed_work(sw_l3_offl_wq, &l3_offl_work,
+				   msecs_to_jiffies(10));
+}
+
+static int rvu_sw_l3_ensure_wq(void)
+{
+	if (sw_l3_offl_wq)
+		return 0;
+
+	sw_l3_offl_wq = alloc_workqueue("sw_af_fib_wq", 0, 0);
+	if (!sw_l3_offl_wq)
+		return -ENOMEM;
+
+	return 0;
+}
+
+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, i;
+	bool rc;
+
+	BUILD_BUG_ON(sizeof_field(struct af2swdev_notify_req, entry) !=
+		     sizeof(struct fib_entry) * RVU_SW_L3_BATCH_MAX);
+
+	l3_entry = list_first_entry_or_null(lh, struct l3_entry, list);
+	if (!l3_entry)
+		return 0;
+
+	/*
+	 * Octeontx2 has a single AF (one struct rvu) per RVU chip. All queued
+	 * entries therefore share the same rvu and the same switch PF below.
+	 * Host PF identity is carried per fib_entry (port_id), not by picking
+	 * a different switch PF here.
+	 */
+	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);
+		return -ENOMEM;
+	}
+
+	dst = &req->entry[0];
+	/*
+	 * Batch fib_entry records from multiple host PF notifies into one
+	 * af2swdev message. Safe on octeontx2: every l3_entry targets the
+	 * same switch PF; egress port is encoded in each fib_entry.port_id.
+	 */
+	while ((l3_entry =
+		list_first_entry_or_null(lh,
+					 struct l3_entry, list)) != NULL) {
+		entry = l3_entry->entry;
+		cnt = l3_entry->cnt;
+
+		/* af2swdev_notify_req.entry[] holds RVU_SW_L3_BATCH_MAX slots;
+		 * stop before copying the next l3_entry when the mbox buffer
+		 * would overflow. Leftovers stay on lh and are re-queued.
+		 */
+		if (tot_cnt + cnt > RVU_SW_L3_BATCH_MAX)
+			break;
+
+		sz = sizeof(*entry) * cnt;
+
+		memcpy(dst, entry, sz);
+		for (i = 0; i < cnt; i++)
+			dst[i].port_id = l3_entry->port_id;
+		tot_cnt += cnt;
+		dst += cnt;
+
+		list_del_init(&l3_entry->list);
+		kfree(l3_entry);
+	}
+	if (!tot_cnt) {
+		mutex_unlock(&rvu->mbox_lock);
+		return -EINVAL;
+	}
+
+	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 void sw_l3_offl_work_handler(struct work_struct *work)
+{
+	struct list_head l3lh;
+
+	INIT_LIST_HEAD(&l3lh);
+
+	mutex_lock(&l3_offl_llock);
+	if (list_empty(&l3_offl_lh)) {
+		mutex_unlock(&l3_offl_llock);
+		return;
+	}
+	list_splice_init(&l3_offl_lh, &l3lh);
+	mutex_unlock(&l3_offl_llock);
+
+	if (rvu_sw_l3_offl_rule_push(&l3lh))
+		pr_err("%s: Error to push rules\n", __func__);
+
+	/* rvu_sw_l3_offl_rule_push() may leave entries when a batch is full. */
+	if (!list_empty(&l3lh)) {
+		mutex_lock(&l3_offl_llock);
+		list_splice_tail(&l3lh, &l3_offl_lh);
+		mutex_unlock(&l3_offl_llock);
+		if (sw_l3_offl_wq)
+			queue_delayed_work(sw_l3_offl_wq, &l3_offl_work,
+					   msecs_to_jiffies(100));
+		return;
+	}
+
+	mutex_lock(&l3_offl_llock);
+	if (!list_empty(&l3_offl_lh))
+		rvu_sw_l3_queue_work();
+	mutex_unlock(&l3_offl_llock);
+}
+
+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, rc;
+
+	if (!(rvu->rswitch.flags & RVU_SWITCH_FLAG_FW_READY))
+		return -EAGAIN;
+
+	/* Reject single notifies larger than af2swdev_notify_req.entry[]. */
+	if (!req->cnt || req->cnt > RVU_SW_L3_BATCH_MAX)
+		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);
+
+	/* Host PFs on this RVU share one AF and one switch PF offload path. */
+	mutex_lock(&l3_offl_llock);
+	rc = rvu_sw_l3_ensure_wq();
+	if (rc) {
+		mutex_unlock(&l3_offl_llock);
+		kfree(l3_entry);
+		return rc;
+	}
+
+	list_add_tail(&l3_entry->list, &l3_offl_lh);
+	if (sw_l3_offl_wq)
+		rvu_sw_l3_queue_work();
+	mutex_unlock(&l3_offl_llock);
+
+	return 0;
+}
+
+void rvu_sw_l3_shutdown(void)
+{
+	if (!sw_l3_offl_wq)
+		return;
+
+	cancel_delayed_work_sync(&l3_offl_work);
+	destroy_workqueue(sw_l3_offl_wq);
+	sw_l3_offl_wq = NULL;
+
+	mutex_lock(&l3_offl_llock);
+	rvu_sw_l3_drain_list(&l3_offl_lh);
+	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
new file mode 100644
index 00000000000000..153f1415466deb
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l3.h
@@ -0,0 +1,12 @@
+/* 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
+
+void rvu_sw_l3_shutdown(void);
+#endif
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/Makefile b/drivers/net/ethernet/marvell/octeontx2/nic/Makefile
index 883e9f4d601c5c..e4deead6d1e00c 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/Makefile
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/Makefile
@@ -9,7 +9,14 @@ 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 switch/sw_trace.o
+rvu_nicpf-$(CONFIG_OCTEONTX_SWITCH) += switch/sw_nb.o switch/sw_fib.o \
+				       switch/sw_nb_v4.o
+ifneq ($(CONFIG_IPV6),)
+rvu_nicpf-$(CONFIG_OCTEONTX_SWITCH) += switch/sw_nb_v6.o
+endif
+
 rvu_nicvf-y := otx2_vf.o
 rvu_rep-y := rep.o
 
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c
index 2e33b33ec99341..0cd6049c637e1f 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c
@@ -28,6 +28,7 @@
 #include <rvu_trace.h>
 #include "cn10k_ipsec.h"
 #include "otx2_xsk.h"
+#include "switch/sw_nb.h"
 
 #define DRV_NAME	"rvu_nicpf"
 #define DRV_STRING	"Marvell RVU NIC Physical Function Driver"
@@ -993,6 +994,7 @@ static int otx2_process_mbox_msg_up(struct otx2_nic *pf,
 MBOX_UP_CGX_MESSAGES
 MBOX_UP_MCS_MESSAGES
 MBOX_UP_REP_MESSAGES
+MBOX_UP_AF2PF_FDB_REFRESH_MESSAGES
 #undef M
 		break;
 	default:
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_txrx.h b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_txrx.h
index acf259d7200883..73a98b94426b04 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];
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c
index b022f52c684508..6f2fc4caf70ccd 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c
@@ -9,6 +9,7 @@
 #include <linux/module.h>
 #include <linux/pci.h>
 #include <linux/net_tstamp.h>
+#include <net/switchdev.h>
 
 #include "otx2_common.h"
 #include "otx2_reg.h"
@@ -114,6 +115,33 @@ static void otx2vf_vfaf_mbox_handler(struct work_struct *work)
 	}
 }
 
+#if IS_ENABLED(CONFIG_OCTEONTX_SWITCH)
+static int otx2vf_mbox_af2pf_fdb_refresh(struct otx2_nic *vf,
+					 struct af2pf_fdb_refresh_req *req,
+					 struct msg_rsp *rsp)
+{
+	struct switchdev_notifier_fdb_info item = {0};
+
+	item.addr = req->mac;
+	item.info.dev = vf->netdev;
+	if (req->flags & FDB_DEL)
+		call_switchdev_notifiers(SWITCHDEV_FDB_DEL_TO_BRIDGE,
+					 item.info.dev, &item.info, NULL);
+	else
+		call_switchdev_notifiers(SWITCHDEV_FDB_ADD_TO_BRIDGE,
+					 item.info.dev, &item.info, NULL);
+
+	return 0;
+}
+#else
+static int otx2vf_mbox_af2pf_fdb_refresh(struct otx2_nic *vf,
+					 struct af2pf_fdb_refresh_req *req,
+					 struct msg_rsp *rsp)
+{
+	return 0;
+}
+#endif
+
 static int otx2vf_process_mbox_msg_up(struct otx2_nic *vf,
 				      struct mbox_msghdr *req)
 {
@@ -141,6 +169,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 = otx2vf_mbox_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/rep.c b/drivers/net/ethernet/marvell/octeontx2/nic/rep.c
index 0f5d5642d3f7a1..1900235fabc578 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,18 +400,55 @@ 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;
+	int mbox_err;
+#if IS_ENABLED(CONFIG_OCTEONTX_SWITCH)
+	int err;
+#endif
+
+	rvu_rep_devlink_set_switch_id(priv, &attrs.switch_id);
+
+#if IS_ENABLED(CONFIG_OCTEONTX_SWITCH)
+	if (ena) {
+		err = sw_nb_register(netdev);
+		if (err)
+			return err;
+	}
+#endif
 
 	mutex_lock(&priv->mbox.lock);
 	req = otx2_mbox_alloc_msg_esw_cfg(&priv->mbox);
 	if (!req) {
 		mutex_unlock(&priv->mbox.lock);
+#if IS_ENABLED(CONFIG_OCTEONTX_SWITCH)
+		if (ena)
+			sw_nb_unregister(netdev);
+#endif
 		return -ENOMEM;
 	}
 	req->ena = ena;
-	otx2_sync_mbox_msg(&priv->mbox);
+	memcpy(req->switch_id, attrs.switch_id.id, attrs.switch_id.id_len);
+	mbox_err = otx2_sync_mbox_msg(&priv->mbox);
 	mutex_unlock(&priv->mbox.lock);
-	return 0;
+
+#if IS_ENABLED(CONFIG_OCTEONTX_SWITCH)
+	if (ena && mbox_err) {
+		sw_nb_unregister(netdev);
+		return mbox_err;
+	}
+
+	if (!ena) {
+		err = sw_nb_unregister(netdev);
+		if (err && !mbox_err)
+			return err;
+	}
+#endif
+
+	return mbox_err;
 }
 
 static netdev_tx_t rvu_rep_xmit(struct sk_buff *skb, struct net_device *dev)
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 00000000000000..eb5e2ce44ca207
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.c
@@ -0,0 +1,234 @@
+// 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 "../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
+
+#define SW_FDB_LIST_MAX 4096
+
+static DEFINE_SPINLOCK(sw_fdb_llock);
+static LIST_HEAD(sw_fdb_lh);
+static atomic_t sw_fdb_list_cnt = ATOMIC_INIT(0);
+
+struct sw_fdb_list_entry {
+	struct list_head list;
+	u64 flags;
+	struct otx2_nic *pf;
+	netdevice_tracker dev_tracker;
+	u8  mac[ETH_ALEN];
+	bool add_fdb;
+};
+
+static struct workqueue_struct *sw_fdb_wq;
+static struct work_struct sw_fdb_work;
+
+static void sw_fdb_list_cnt_warn(struct net_device *netdev)
+{
+	int n = atomic_read(&sw_fdb_list_cnt);
+
+	if (n < 0)
+		netdev_warn(netdev, "FDB list count underflow: %d\n", n);
+	else if (n > SW_FDB_LIST_MAX)
+		netdev_warn(netdev, "FDB list count overflow: %d (max %d)\n",
+			    n, SW_FDB_LIST_MAX);
+}
+
+static int sw_fdb_list_count(void)
+{
+	return atomic_read(&sw_fdb_list_cnt);
+}
+
+static void sw_fdb_list_cnt_inc(struct net_device *netdev)
+{
+	atomic_inc(&sw_fdb_list_cnt);
+	sw_fdb_list_cnt_warn(netdev);
+}
+
+static void sw_fdb_list_cnt_dec(struct net_device *netdev)
+{
+	atomic_dec(&sw_fdb_list_cnt);
+	sw_fdb_list_cnt_warn(netdev);
+}
+
+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;
+	struct workqueue_struct *wq;
+	LIST_HEAD(tlist);
+
+	spin_lock_bh(&sw_fdb_llock);
+	list_splice_init(&sw_fdb_lh, &tlist);
+	spin_unlock_bh(&sw_fdb_llock);
+
+	while ((entry =
+		list_first_entry_or_null(&tlist,
+					 struct sw_fdb_list_entry,
+					 list)) != NULL) {
+		list_del_init(&entry->list);
+		sw_fdb_list_cnt_dec(entry->pf->netdev);
+		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);
+		netdev_put(entry->pf->netdev, &entry->dev_tracker);
+		kfree(entry);
+	}
+
+	spin_lock_bh(&sw_fdb_llock);
+	wq = sw_fdb_wq;
+	if (wq && !list_empty(&sw_fdb_lh))
+		queue_work(wq, &sw_fdb_work);
+	spin_unlock_bh(&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;
+	struct workqueue_struct *wq;
+
+	spin_lock_bh(&sw_fdb_llock);
+	if (!sw_fdb_wq) {
+		spin_unlock_bh(&sw_fdb_llock);
+		return -EINVAL;
+	}
+	spin_unlock_bh(&sw_fdb_llock);
+
+	if (sw_fdb_list_count() >= SW_FDB_LIST_MAX)
+		return -ENOMEM;
+
+	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;
+	netdev_hold(dev, &entry->dev_tracker, GFP_ATOMIC);
+
+	spin_lock_bh(&sw_fdb_llock);
+	wq = sw_fdb_wq;
+	if (wq) {
+		list_add_tail(&entry->list, &sw_fdb_lh);
+		sw_fdb_list_cnt_inc(dev);
+		queue_work(wq, &sw_fdb_work);
+	}
+	spin_unlock_bh(&sw_fdb_llock);
+
+	if (!wq) {
+		netdev_put(dev, &entry->dev_tracker);
+		kfree(entry);
+		return -EINVAL;
+	}
+
+	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;
+	struct workqueue_struct *wq;
+	LIST_HEAD(tlist);
+
+	spin_lock_bh(&sw_fdb_llock);
+	wq = sw_fdb_wq;
+	sw_fdb_wq = NULL;
+	spin_unlock_bh(&sw_fdb_llock);
+
+	if (!wq)
+		return;
+
+	cancel_work_sync(&sw_fdb_work);
+	destroy_workqueue(wq);
+
+	spin_lock_bh(&sw_fdb_llock);
+	list_splice_init(&sw_fdb_lh, &tlist);
+	spin_unlock_bh(&sw_fdb_llock);
+
+	while ((entry =
+		list_first_entry_or_null(&tlist,
+					 struct sw_fdb_list_entry,
+					 list)) != NULL) {
+		list_del_init(&entry->list);
+		sw_fdb_list_cnt_dec(entry->pf->netdev);
+		netdev_put(entry->pf->netdev, &entry->dev_tracker);
+		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;
+	if (req->flags & FDB_DEL)
+		call_switchdev_notifiers(SWITCHDEV_FDB_DEL_TO_BRIDGE,
+					 item.info.dev, &item.info, NULL);
+	else
+		call_switchdev_notifiers(SWITCHDEV_FDB_ADD_TO_BRIDGE,
+					 item.info.dev, &item.info, NULL);
+
+	return 0;
+}
+#endif
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 00000000000000..3b06a77e6b56ae
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Marvell switch driver
+ *
+ * Copyright (C) 2026 Marvell.
+ *
+ */
+#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);
+
+#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 00000000000000..1c1f23d5d1364d
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fib.c
@@ -0,0 +1,205 @@
+// 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 "../otx2_reg.h"
+#include "../otx2_common.h"
+#include "../otx2_struct.h"
+#include "../cn10k.h"
+#include "sw_nb.h"
+#include "sw_fib.h"
+
+#if IS_ENABLED(CONFIG_OCTEONTX_SWITCH)
+
+#define SW_FIB_BATCH_MAX 16
+
+/*
+ * One switch PF registers notifiers via sw_nb_register(); a second call
+ * returns -EBUSY. A single sw_fib_wq therefore serves the one switchdev
+ * instance on octeontx2, matching the FDB offload path.
+ */
+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;
+	netdevice_tracker dev_tracker;
+	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 > SW_FIB_BATCH_MAX)
+		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);
+		if (sw_fib_notify(lentry->pf, lentry->cnt, lentry->entry)) {
+			netdev_err(lentry->pf->netdev,
+				   "Failed to notify FIB update to AF, will retry\n");
+			spin_lock_bh(&sw_fib_llock);
+			if (sw_fib_wq) {
+				list_add_tail(&lentry->lh, &sw_fib_lh);
+				queue_delayed_work(sw_fib_wq, &sw_fib_work,
+						   msecs_to_jiffies(100));
+				spin_unlock_bh(&sw_fib_llock);
+				continue;
+			}
+			spin_unlock_bh(&sw_fib_llock);
+			netdev_put(lentry->pf->netdev, &lentry->dev_tracker);
+			kfree(lentry->entry);
+			kfree(lentry);
+			continue;
+		}
+		netdev_put(lentry->pf->netdev, &lentry->dev_tracker);
+		kfree(lentry->entry);
+		kfree(lentry);
+	}
+
+	spin_lock_bh(&sw_fib_llock);
+	if (!list_empty(&sw_fib_lh) && sw_fib_wq)
+		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;
+	struct workqueue_struct *wq;
+
+	if (cnt <= 0 || cnt > SW_FIB_BATCH_MAX) {
+		kfree(entry);
+		return -EINVAL;
+	}
+
+	spin_lock_bh(&sw_fib_llock);
+	if (!sw_fib_wq) {
+		spin_unlock_bh(&sw_fib_llock);
+		kfree(entry);
+		return -EINVAL;
+	}
+	spin_unlock_bh(&sw_fib_llock);
+
+	lentry = kcalloc(1, sizeof(*lentry), GFP_ATOMIC);
+	if (!lentry) {
+		kfree(entry);
+		return -ENOMEM;
+	}
+
+	lentry->pf = pf;
+	lentry->cnt = cnt;
+	lentry->entry = entry;
+	INIT_LIST_HEAD(&lentry->lh);
+	netdev_hold(dev, &lentry->dev_tracker, GFP_ATOMIC);
+
+	spin_lock_bh(&sw_fib_llock);
+	wq = sw_fib_wq;
+	if (wq) {
+		list_add_tail(&lentry->lh, &sw_fib_lh);
+		queue_delayed_work(wq, &sw_fib_work,
+				   msecs_to_jiffies(10));
+	}
+	spin_unlock_bh(&sw_fib_llock);
+
+	if (!wq) {
+		netdev_put(dev, &lentry->dev_tracker);
+		kfree(lentry);
+		kfree(entry);
+		return -EINVAL;
+	}
+
+	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;
+	struct workqueue_struct *wq;
+	LIST_HEAD(tlist);
+
+	spin_lock_bh(&sw_fib_llock);
+	wq = sw_fib_wq;
+	sw_fib_wq = NULL;
+	spin_unlock_bh(&sw_fib_llock);
+
+	if (!wq)
+		return;
+
+	cancel_delayed_work_sync(&sw_fib_work);
+	destroy_workqueue(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);
+		netdev_put(lentry->pf->netdev, &lentry->dev_tracker);
+		kfree(lentry->entry);
+		kfree(lentry);
+	}
+}
+
+#endif /* CONFIG_OCTEONTX_SWITCH */
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 00000000000000..2e0e7a80c40ce5
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fib.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Marvell switch driver
+ *
+ * Copyright (C) 2026 Marvell.
+ *
+ */
+#ifndef SW_FIB_H_
+#define SW_FIB_H_
+
+#include <linux/kconfig.h>
+
+struct fib_entry;
+struct net_device;
+
+#if IS_ENABLED(CONFIG_OCTEONTX_SWITCH)
+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);
+#else
+static inline int sw_fib_add_to_list(struct net_device *dev,
+				     struct fib_entry *entry, int cnt)
+{
+	return 0;
+}
+static inline void sw_fib_deinit(void) {}
+static inline int sw_fib_init(void) { return 0; }
+#endif
+
+#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 00000000000000..f4366cb9735328
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.c
@@ -0,0 +1,766 @@
+// 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 <linux/mutex.h>
+#include <linux/refcount.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;
+	netdevice_tracker dev_tracker;
+	struct fl_tuple tuple;
+};
+
+static struct workqueue_struct *sw_fl_wq;
+static struct work_struct sw_fl_work;
+
+struct sw_fl_ct_cb {
+	struct list_head list;
+	struct nf_flowtable *ft;
+	struct otx2_nic *nic;
+	refcount_t ref;
+};
+
+struct sw_fl_ct_cookie {
+	struct list_head list;
+	unsigned long cookie;
+	struct nf_flowtable *ft;
+	struct otx2_nic *nic;
+};
+
+static LIST_HEAD(sw_fl_ct_cb_list);
+static DEFINE_MUTEX(sw_fl_ct_cb_lock);
+static LIST_HEAD(sw_fl_ct_cookie_list);
+static DEFINE_MUTEX(sw_fl_ct_cookie_lock);
+
+static struct sw_fl_ct_cb *sw_fl_ct_cb_find(struct nf_flowtable *ft,
+					    struct otx2_nic *nic)
+{
+	struct sw_fl_ct_cb *entry;
+
+	list_for_each_entry(entry, &sw_fl_ct_cb_list, list) {
+		if (entry->ft == ft && entry->nic == nic)
+			return entry;
+	}
+
+	return NULL;
+}
+
+static int sw_fl_ct_cb_get(struct nf_flowtable *ft, struct otx2_nic *nic)
+{
+	struct sw_fl_ct_cb *entry;
+	int err;
+
+	mutex_lock(&sw_fl_ct_cb_lock);
+	entry = sw_fl_ct_cb_find(ft, nic);
+	if (entry) {
+		refcount_inc(&entry->ref);
+		mutex_unlock(&sw_fl_ct_cb_lock);
+		return 0;
+	}
+
+	err = nf_flow_table_offload_add_cb(ft, sw_fl_setup_ft_block_ingress_cb, nic);
+	if (err && err != -EEXIST) {
+		mutex_unlock(&sw_fl_ct_cb_lock);
+		return err;
+	}
+
+	entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+	if (!entry) {
+		if (!err)
+			nf_flow_table_offload_del_cb(ft,
+						     sw_fl_setup_ft_block_ingress_cb,
+						     nic);
+		mutex_unlock(&sw_fl_ct_cb_lock);
+		return -ENOMEM;
+	}
+
+	entry->ft = ft;
+	entry->nic = nic;
+	refcount_set(&entry->ref, 1);
+	list_add_tail(&entry->list, &sw_fl_ct_cb_list);
+	mutex_unlock(&sw_fl_ct_cb_lock);
+
+	return 0;
+}
+
+static void sw_fl_ct_cb_put(struct nf_flowtable *ft, struct otx2_nic *nic)
+{
+	struct sw_fl_ct_cb *entry;
+
+	mutex_lock(&sw_fl_ct_cb_lock);
+	entry = sw_fl_ct_cb_find(ft, nic);
+	if (!entry || !refcount_dec_and_test(&entry->ref)) {
+		mutex_unlock(&sw_fl_ct_cb_lock);
+		return;
+	}
+
+	list_del(&entry->list);
+	mutex_unlock(&sw_fl_ct_cb_lock);
+
+	nf_flow_table_offload_del_cb(ft, sw_fl_setup_ft_block_ingress_cb, nic);
+	kfree(entry);
+}
+
+static void sw_fl_ct_cookie_add(unsigned long cookie, struct nf_flowtable *ft,
+				struct otx2_nic *nic)
+{
+	struct sw_fl_ct_cookie *entry;
+
+	entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+	if (!entry)
+		return;
+
+	entry->cookie = cookie;
+	entry->ft = ft;
+	entry->nic = nic;
+
+	mutex_lock(&sw_fl_ct_cookie_lock);
+	list_add_tail(&entry->list, &sw_fl_ct_cookie_list);
+	mutex_unlock(&sw_fl_ct_cookie_lock);
+}
+
+static void sw_fl_ct_cookie_put(unsigned long cookie)
+{
+	struct sw_fl_ct_cookie *entry, *tmp;
+
+	mutex_lock(&sw_fl_ct_cookie_lock);
+	list_for_each_entry_safe(entry, tmp, &sw_fl_ct_cookie_list, list) {
+		if (entry->cookie != cookie)
+			continue;
+
+		list_del(&entry->list);
+		mutex_unlock(&sw_fl_ct_cookie_lock);
+		sw_fl_ct_cb_put(entry->ft, entry->nic);
+		kfree(entry);
+		return;
+	}
+	mutex_unlock(&sw_fl_ct_cookie_lock);
+}
+
+static void sw_fl_ct_cb_flush(void)
+{
+	struct sw_fl_ct_cookie *cookie, *ctmp;
+	struct sw_fl_ct_cb *entry, *etmp;
+
+	mutex_lock(&sw_fl_ct_cookie_lock);
+	list_for_each_entry_safe(cookie, ctmp, &sw_fl_ct_cookie_list, list) {
+		list_del(&cookie->list);
+		kfree(cookie);
+	}
+	mutex_unlock(&sw_fl_ct_cookie_lock);
+
+	mutex_lock(&sw_fl_ct_cb_lock);
+	list_for_each_entry_safe(entry, etmp, &sw_fl_ct_cb_list, list) {
+		list_del(&entry->list);
+		nf_flow_table_offload_del_cb(entry->ft,
+					     sw_fl_setup_ft_block_ingress_cb,
+					     entry->nic);
+		kfree(entry);
+	}
+	mutex_unlock(&sw_fl_ct_cb_lock);
+}
+
+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_bh(&sw_fl_lock);
+	list_splice_init(&sw_fl_lh, &tlist);
+	spin_unlock_bh(&sw_fl_lock);
+
+	while ((entry =
+		list_first_entry_or_null(&tlist,
+					 struct sw_fl_list_entry,
+					 list)) != NULL) {
+		list_del_init(&entry->list);
+		if (sw_fl_msg_send(entry->pf, &entry->tuple,
+				   entry->flags, entry->cookie)) {
+			netdev_err(entry->pf->netdev,
+				   "Failed to notify flow update to AF, will retry\n");
+			spin_lock_bh(&sw_fl_lock);
+			if (sw_fl_wq) {
+				list_add_tail(&entry->list, &sw_fl_lh);
+				queue_work(sw_fl_wq, &sw_fl_work);
+				spin_unlock_bh(&sw_fl_lock);
+				continue;
+			}
+			spin_unlock_bh(&sw_fl_lock);
+			netdev_put(entry->pf->netdev, &entry->dev_tracker);
+			kfree(entry);
+			continue;
+		}
+		netdev_put(entry->pf->netdev, &entry->dev_tracker);
+		kfree(entry);
+	}
+
+	spin_lock_bh(&sw_fl_lock);
+	if (!list_empty(&sw_fl_lh) && sw_fl_wq)
+		queue_work(sw_fl_wq, &sw_fl_work);
+	spin_unlock_bh(&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_bh(&sw_fl_lock);
+	if (!sw_fl_wq) {
+		spin_unlock_bh(&sw_fl_lock);
+		kfree(entry);
+		return -EINVAL;
+	}
+
+	netdev_hold(pf->netdev, &entry->dev_tracker, GFP_ATOMIC);
+	list_add_tail(&entry->list, &sw_fl_lh);
+	queue_work(sw_fl_wq, &sw_fl_work);
+	spin_unlock_bh(&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 nf_flowtable **ct_ft)
+{
+	struct flow_action_entry *act;
+	struct otx2_nic *out_nic;
+	int used = 0;
+	int err;
+	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:
+			if (!act->dev || !sw_nb_is_valid_dev(act->dev))
+				return -EOPNOTSUPP;
+			trace_sw_act_dump(__func__, "redirect to egress port", 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__, "register conntrack offload callback", act->id);
+			err = sw_fl_ct_cb_get(act->ct.flow_table, nic);
+			if (err) {
+				netdev_err(nic->netdev,
+					   "%s: Error to offload flow, err=%d\n",
+					   __func__, err);
+				return err;
+			}
+
+			if (ct_ft)
+				*ct_ft = act->ct.flow_table;
+			*op |= BIT_ULL(FLOW_ACTION_CT);
+			break;
+
+		case FLOW_ACTION_MANGLE:
+			trace_sw_act_dump(__func__, "header mangle action", 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__, "unsupported flow action", act->id);
+			break;
+		}
+	}
+
+	tuple->mangle_cnt = used;
+
+	if (!*op && !used) {
+		netdev_dbg(nic->netdev, "%s: Op is not valid\n", __func__);
+		return -EOPNOTSUPP;
+	}
+
+	return 0;
+}
+
+static int sw_fl_get_route(struct net *net, struct fib_result *res, __be32 addr)
+{
+	struct flowi4 fl4;
+
+	memset(&fl4, 0, sizeof(fl4));
+	fl4.daddr = addr;
+	return fib_lookup(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(dev_net(pf->netdev), &res, dst);
+	if (err) {
+		netdev_err(pf->netdev,
+			   "%s: Failed to find route to dst %pI4\n",
+			   __func__, &dst);
+		goto done;
+	}
+
+	if (res.fi->fib_type != RTN_UNICAST) {
+		netdev_err(pf->netdev,
+			   "%s: Not unicast  route to dst %pi4\n",
+			   __func__, &dst);
+		err = -EFAULT;
+		goto done;
+	}
+
+	fib_nhc = fib_info_nhc(res.fi, 0);
+	if (!fib_nhc) {
+		err = -EINVAL;
+		netdev_err(pf->netdev,
+			   "%s: Could not get fib_nhc for %pI4\n",
+			   __func__, &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: Unable to find any slave device\n",
+				   __func__);
+			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: flow acceleration support is only for cavium devices\n",
+			   __func__);
+		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) {
+			if (match.key->ip_proto != IPPROTO_TCP &&
+			    match.key->ip_proto != IPPROTO_UDP)
+				return -EOPNOTSUPP;
+
+			ip_proto = match.key->ip_proto;
+			if (ip_proto == IPPROTO_UDP)
+				*features |= BIT_ULL(NPC_IPPROTO_UDP);
+			else
+				*features |= BIT_ULL(NPC_IPPROTO_TCP);
+		}
+
+		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.mask->dst) {
+			tuple->ip4dst = match.key->dst;
+			tuple->m_ip4dst = match.mask->dst;
+			*features |= BIT_ULL(NPC_DIP_IPV4);
+		}
+
+		if (match.mask->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->m_ip4src || !tuple->m_ip4dst) {
+			netdev_err(nic->netdev,
+				   "%s: Invalid src=%pI4 and dst=%pI4 addresses\n",
+				   __func__, &tuple->ip4src, &tuple->ip4dst);
+			return -EINVAL;
+		}
+
+		if ((tuple->ip4src & tuple->m_ip4src) == (tuple->ip4dst & tuple->m_ip4dst)) {
+			netdev_err(nic->netdev,
+				   "%s: Masked values are same; Invalid src=%pI4 and dst=%pI4 addresses\n",
+				   __func__, &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.mask->dst)
+				*features |= BIT_ULL(NPC_DPORT_UDP);
+
+			if (match.mask->src)
+				*features |= BIT_ULL(NPC_SPORT_UDP);
+		} else if (ip_proto == IPPROTO_TCP) {
+			if (match.mask->dst)
+				*features |= BIT_ULL(NPC_DPORT_TCP);
+
+			if (match.mask->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 nf_flowtable *ct_ft = NULL;
+	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, &ct_ft);
+	if (rc)
+		return rc;
+
+	if (op & BIT_ULL(FLOW_ACTION_CT)) {
+		if (ct_ft)
+			sw_fl_ct_cookie_add(f->cookie, ct_ft, nic);
+		return 0;
+	}
+
+	rc  = sw_fl_parse_flow(nic, f, &tuple, &features);
+	if (rc) {
+		trace_sw_fl_dump(__func__, "flow key parse failed", &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__, "ingress pcifunc lookup failed", &tuple);
+			return rc;
+		}
+
+		rc = sw_fl_get_pcifunc(nic, tuple.ip4dst,
+				       &tuple.xmit_pf, &tuple, false);
+		if (rc) {
+			trace_sw_fl_dump(__func__, "egress pcifunc lookup failed", &tuple);
+			return rc;
+		}
+	}
+
+	trace_sw_fl_dump(__func__, "offload flow add queued", &tuple);
+	return sw_fl_add_to_list(nic, &tuple, f->cookie, true);
+}
+
+static int sw_fl_del(struct otx2_nic *nic, struct flow_cls_offload *f)
+{
+	sw_fl_ct_cookie_put(f->cookie);
+	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: Error happened while mcam alloc req\n",
+			   __func__);
+		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);
+	if (IS_ERR(rsp)) {
+		rc = PTR_ERR(rsp);
+		goto fail;
+	}
+	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 (!smp_load_acquire(&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;
+
+	smp_store_release(&init_done, true);
+	return 0;
+}
+
+void sw_fl_deinit(void)
+{
+	struct sw_fl_list_entry *entry;
+	struct workqueue_struct *wq;
+	LIST_HEAD(tlist);
+
+	smp_store_release(&init_done, false);
+
+	spin_lock_bh(&sw_fl_lock);
+	wq = sw_fl_wq;
+	sw_fl_wq = NULL;
+	spin_unlock_bh(&sw_fl_lock);
+
+	if (!wq)
+		return;
+
+	cancel_work_sync(&sw_fl_work);
+	destroy_workqueue(wq);
+
+	spin_lock_bh(&sw_fl_lock);
+	list_splice_init(&sw_fl_lh, &tlist);
+	spin_unlock_bh(&sw_fl_lock);
+
+	while ((entry =
+		list_first_entry_or_null(&tlist,
+					 struct sw_fl_list_entry,
+					 list)) != NULL) {
+		list_del_init(&entry->list);
+		netdev_put(entry->pf->netdev, &entry->dev_tracker);
+		kfree(entry);
+	}
+
+	sw_fl_ct_cb_flush();
+}
+#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
new file mode 100644
index 00000000000000..8dd816eb17d2c9
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.h
@@ -0,0 +1,15 @@
+/* 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);
+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_nb.c b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c
new file mode 100644
index 00000000000000..971af268ae9310
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c
@@ -0,0 +1,501 @@
+// 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 "../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;
+static bool sw_nb_registered;
+
+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;
+}
+
+struct net_device *sw_nb_resolve_pf_dev(struct net_device *dev)
+{
+	struct net_device *lower, *pf_dev = dev;
+	struct list_head *iter;
+
+	if (netif_is_bridge_master(dev)) {
+		netdev_for_each_lower_dev(dev, lower, iter) {
+			pf_dev = lower;
+			break;
+		}
+	} else if (is_vlan_dev(dev)) {
+		pf_dev = vlan_dev_real_dev(dev);
+	}
+
+	if (!sw_nb_is_cavium_dev(pf_dev))
+		return NULL;
+
+	return pf_dev;
+}
+
+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;
+}
+
+bool sw_nb_is_valid_dev(struct net_device *netdev)
+{
+	struct netdev_nested_priv priv;
+	struct net_device *br;
+	int cnt = 0;
+	bool valid;
+
+	priv.flags = true;
+	priv.data = &cnt;
+
+	rcu_read_lock();
+
+	if (netif_is_bridge_master(netdev) || is_vlan_dev(netdev)) {
+		netdev_walk_all_lower_dev_rcu(netdev, sw_nb_check_slaves, &priv);
+		valid = priv.flags && cnt;
+		rcu_read_unlock();
+		return valid;
+	}
+
+	if (netif_is_bridge_port(netdev)) {
+		br = netdev_master_upper_dev_get_rcu(netdev);
+		if (!br) {
+			rcu_read_unlock();
+			return false;
+		}
+		netdev_walk_all_lower_dev_rcu(br, sw_nb_check_slaves, &priv);
+		valid = priv.flags && cnt;
+		rcu_read_unlock();
+		return valid;
+	}
+
+	rcu_read_unlock();
+
+	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;
+	int rc = 0;
+
+	if (!sw_nb_is_valid_dev(dev))
+		return NOTIFY_DONE;
+
+	switch (event) {
+	case SWITCHDEV_FDB_ADD_TO_DEVICE:
+		if (fdb_info->is_local)
+			break;
+		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;
+		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;
+}
+
+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=%pI4 dstlen=%u type=%u\n",
+			    __func__, event, (const __be32 *)&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=%pI4 gw=%pI4\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);
+
+	i6dev = __in6_dev_get(dev);
+#if IS_ENABLED(CONFIG_IPV6)
+	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, ret = 0;
+
+	if (!sw_nb_registered)
+		return 0;
+
+	err = unregister_switchdev_notifier(&sw_nb_fdb);
+	if (err) {
+		netdev_err(netdev, "Failed to unregister switchdev nb\n");
+		ret = err;
+	}
+
+	err = unregister_fib_notifier(&init_net, &sw_nb_fib);
+	if (err) {
+		netdev_err(netdev, "Failed to unregister fib nb\n");
+		if (!ret)
+			ret = err;
+	}
+
+	err = unregister_netevent_notifier(&sw_nb_netevent);
+	if (err) {
+		netdev_err(netdev, "Failed to unregister netevent\n");
+		if (!ret)
+			ret = err;
+	}
+
+	err = unregister_inetaddr_notifier(&sw_nb_v4_inetaddr);
+	if (err) {
+		netdev_err(netdev, "Failed to unregister addr event\n");
+		if (!ret)
+			ret = err;
+	}
+
+#if IS_ENABLED(CONFIG_IPV6)
+	err = unregister_inet6addr_notifier(&sw_nb_v6_inetaddr);
+	if (err) {
+		netdev_err(netdev, "Failed to unregister addr event\n");
+		if (!ret)
+			ret = err;
+	}
+#endif
+
+	err = unregister_netdevice_notifier(&sw_nb_netdev);
+	if (err) {
+		netdev_err(netdev, "Failed to unregister netdev notifier\n");
+		if (!ret)
+			ret = err;
+	}
+
+	sw_fl_deinit();
+	sw_fib_deinit();
+	sw_fdb_deinit();
+
+	sw_nb_pf_netdev = NULL;
+	sw_nb_registered = false;
+
+	return ret;
+}
+EXPORT_SYMBOL(sw_nb_unregister);
+
+int sw_nb_register(struct net_device *netdev)
+{
+	int err;
+
+	/* One switch PF / switchdev instance registers system-wide notifiers. */
+	if (sw_nb_registered)
+		return -EBUSY;
+
+	sw_nb_pf_netdev = netdev;
+
+	err = sw_fdb_init();
+	if (err)
+		goto err_clear;
+
+	err = sw_fib_init();
+	if (err)
+		goto err_fdb;
+
+	err = sw_fl_init();
+	if (err)
+		goto err_fib;
+
+	err = register_switchdev_notifier(&sw_nb_fdb);
+	if (err) {
+		netdev_err(netdev, "Failed to register switchdev nb\n");
+		goto err_helpers;
+	}
+
+	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;
+	}
+
+	sw_nb_registered = true;
+
+	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);
+
+err_helpers:
+	sw_fl_deinit();
+err_fib:
+	sw_fib_deinit();
+err_fdb:
+	sw_fdb_deinit();
+err_clear:
+	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
new file mode 100644
index 00000000000000..a701574de1e46b
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Marvell switch driver
+ *
+ * Copyright (C) 2026 Marvell.
+ *
+ */
+#ifndef SW_NB_H_
+#define SW_NB_H_
+
+#include <linux/kconfig.h>
+
+struct net_device;
+struct otx2_nic;
+struct af2pf_fdb_refresh_req;
+struct msg_rsp;
+
+int otx2_mbox_up_handler_af2pf_fdb_refresh(struct otx2_nic *pf,
+					   struct af2pf_fdb_refresh_req *req,
+					   struct msg_rsp *rsp);
+
+#if IS_ENABLED(CONFIG_OCTEONTX_SWITCH)
+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,
+};
+
+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);
+struct net_device *sw_nb_resolve_pf_dev(struct net_device *dev);
+
+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);
+#else
+static inline int sw_nb_register(struct net_device *netdev) { return 0; }
+static inline int sw_nb_unregister(struct net_device *netdev) { return 0; }
+#endif
+
+#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 00000000000000..c1e50bb0fbd3ad
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.c
@@ -0,0 +1,343 @@
+// 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_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_device *idev;
+	struct in_ifaddr *ifa;
+	struct fib_entry *entry;
+	struct otx2_nic *pf;
+
+	idev = __in_dev_get_rtnl(dev);
+	if (!idev || !idev->ifa_list)
+		return NOTIFY_DONE;
+
+	if (!sw_nb_is_valid_dev(dev))
+		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 = ifa->ifa_address;
+	entry->dst_len = 32;
+	entry->mac_valid = 1;
+	entry->host = 1;
+
+	pf_dev = sw_nb_resolve_pf_dev(dev);
+	if (!pf_dev) {
+		kfree(entry);
+		return NOTIFY_DONE;
+	}
+
+	if (netif_is_bridge_master(dev)) {
+		entry->bridge = 1;
+	} else if (is_vlan_dev(dev)) {
+		entry->vlan_valid = 1;
+		entry->vlan_tag = cpu_to_be16(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 %pI4, %pM, dev=%s\n",
+		   __func__, &entry->dst, entry->mac, dev->name);
+	sw_fib_add_to_list(pf_dev, entry, 1);
+
+	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 netdev_hw_addr *dev_addr;
+	struct net_device *pf_dev;
+	struct in_device *idev;
+	struct fib_entry *entry;
+	struct otx2_nic *pf;
+
+	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 || !idev->ifa_list)
+		return NOTIFY_DONE;
+
+	entry = kcalloc(1, sizeof(*entry), GFP_ATOMIC);
+	if (!entry)
+		return NOTIFY_DONE;
+
+	entry->cmd = sw_nb_inetaddr_event_to_otx2_event(event, dev);
+	entry->dst = ifa->ifa_address;
+	entry->dst_len = 32;
+	entry->mac_valid = 1;
+	entry->host = 1;
+
+	pf_dev = sw_nb_resolve_pf_dev(dev);
+	if (!pf_dev) {
+		kfree(entry);
+		return NOTIFY_DONE;
+	}
+
+	if (netif_is_bridge_master(dev)) {
+		entry->bridge = 1;
+	} else if (is_vlan_dev(dev)) {
+		entry->vlan_valid = 1;
+		entry->vlan_tag = cpu_to_be16(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 %pI4, %pM, %s\n",
+		   __func__, &entry->dst, entry->mac, dev->name);
+
+	sw_fib_add_to_list(pf_dev, entry, 1);
+	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 net_device *host_pf_dev = NULL;
+	struct netdev_hw_addr *dev_addr;
+	struct net_device *nh_pf_dev;
+	struct neighbour *neigh;
+	struct fib_entry *entry;
+	struct net_device *dev;
+	struct fib_nh *fib_nh;
+	struct fib_info *fi;
+	struct otx2_nic *pf;
+	__be32 *haddr;
+	int hcnt = 0;
+	int i, cnt;
+
+	/* 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;
+	}
+
+	haddr = kcalloc(fi->fib_nhs, sizeof(*haddr), GFP_ATOMIC);
+	if (!haddr)
+		return NOTIFY_DONE;
+
+	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;
+
+		nh_pf_dev = sw_nb_resolve_pf_dev(dev);
+		if (!nh_pf_dev)
+			continue;
+
+		entry = kcalloc(1, sizeof(*entry), GFP_ATOMIC);
+		if (!entry)
+			break;
+
+		entry->cmd = sw_nb_fib_event_to_otx2_event(event, dev);
+		entry->dst = (__force __be32)fen_info->dst;
+		entry->dst_len = fen_info->dst_len;
+		entry->gw = fib_nh->fib_nh_gw4;
+
+		if (netif_is_bridge_master(dev)) {
+			entry->bridge = 1;
+		} else if (is_vlan_dev(dev)) {
+			entry->vlan_valid = 1;
+			entry->vlan_tag = cpu_to_be16(vlan_dev_vlan_id(dev));
+		}
+
+		pf = netdev_priv(nh_pf_dev);
+		entry->port_id = pf->pcifunc;
+
+		if (!fib_nh->fib_nh_gw4) {
+			if (!entry->dst && !entry->dst_len) {
+				kfree(entry);
+				continue;
+			}
+			sw_fib_add_to_list(nh_pf_dev, entry, 1);
+			continue;
+		}
+
+		entry->gw_valid = 1;
+
+		if (fib_nh->nh_saddr)
+			haddr[hcnt++] = 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();
+			kfree(entry);
+			continue;
+		}
+
+		if (is_valid_ether_addr(neigh->ha)) {
+			entry->mac_valid = 1;
+			neigh_ha_snapshot(entry->mac, neigh, fib_nh->fib_nh_dev);
+		}
+		rcu_read_unlock();
+
+		netdev_dbg(dev, "%s: FIB route Rule cmd=%llu dst=%pI4 dst_len=%u gw=%pI4\n",
+			   __func__, entry->cmd, &entry->dst, entry->dst_len,
+			   &entry->gw);
+		sw_fib_add_to_list(nh_pf_dev, entry, 1);
+	}
+
+	if (!hcnt) {
+		kfree(haddr);
+		return NOTIFY_DONE;
+	}
+
+	for (i = 0; i < hcnt; i++) {
+		fib_nh = fi->fib_nh;
+		for (cnt = 0; cnt < fi->fib_nhs; cnt++, fib_nh++) {
+			if (fib_nh->nh_saddr == haddr[i]) {
+				host_pf_dev = sw_nb_resolve_pf_dev(fib_nh->fib_nh_dev);
+				break;
+			}
+		}
+
+		if (!host_pf_dev)
+			continue;
+
+		entry = kcalloc(1, sizeof(*entry), GFP_ATOMIC);
+		if (!entry)
+			break;
+
+		pf = netdev_priv(host_pf_dev);
+		entry->cmd = sw_nb_fib_event_to_otx2_event(event, host_pf_dev);
+		entry->dst = haddr[i];
+		entry->dst_len = 32;
+		entry->mac_valid = 1;
+		entry->host = 1;
+		entry->port_id = pf->pcifunc;
+
+		for_each_dev_addr(host_pf_dev, dev_addr) {
+			ether_addr_copy(entry->mac, dev_addr->addr);
+			break;
+		}
+
+		netdev_dbg(host_pf_dev,
+			   "%s: FIB host Rule cmd=%llu dst=%pI4 dst_len=%u gw=%pI4 %s\n",
+			   __func__, entry->cmd, &entry->dst, entry->dst_len,
+			   &entry->gw, host_pf_dev->name);
+		sw_fib_add_to_list(host_pf_dev, entry, 1);
+	}
+
+	kfree(haddr);
+	return NOTIFY_DONE;
+}
+
+int sw_nb_net_v4_neigh_update(struct notifier_block *nb,
+			      unsigned long event, void *ptr)
+{
+	struct net_device *pf_dev;
+	struct neighbour *n = ptr;
+	struct fib_entry *entry;
+	struct otx2_nic *pf;
+
+	if (n->tbl != &arp_tbl)
+		return NOTIFY_DONE;
+
+	if (!sw_nb_is_valid_dev(n->dev))
+		return NOTIFY_DONE;
+
+	entry = kcalloc(1, sizeof(*entry), GFP_ATOMIC);
+	if (!entry)
+		return NOTIFY_DONE;
+
+	entry->cmd = OTX2_NEIGH_UPDATE;
+	entry->dst = *(__be32 *)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);
+
+	pf_dev = sw_nb_resolve_pf_dev(n->dev);
+	if (!pf_dev) {
+		kfree(entry);
+		return NOTIFY_DONE;
+	}
+
+	if (netif_is_bridge_master(n->dev)) {
+		entry->bridge = 1;
+	} else if (is_vlan_dev(n->dev)) {
+		entry->vlan_valid = 1;
+		entry->vlan_tag = cpu_to_be16(vlan_dev_vlan_id(n->dev));
+	}
+
+	pf = netdev_priv(pf_dev);
+	entry->port_id = pf->pcifunc;
+
+	sw_fib_add_to_list(pf_dev, entry, 1);
+	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 00000000000000..c6dbf4b93a9a2e
--- /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 00000000000000..0d94c33c53a66b
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v6.c
@@ -0,0 +1,288 @@
+// 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_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 net_device *pf_dev;
+	struct inet6_ifaddr *ifp;
+	struct inet6_dev *i6dev;
+	struct fib_entry *entry;
+	struct in6_addr addr;
+	struct otx2_nic *pf;
+	u32 prefix_len;
+
+	i6dev = __in6_dev_get(dev);
+	if (!i6dev)
+		return NOTIFY_DONE;
+
+	if (!sw_nb_is_valid_dev(dev))
+		return NOTIFY_DONE;
+
+	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;
+	}
+
+	addr = ifp->addr;
+	prefix_len = ifp->prefix_len;
+	rcu_read_unlock();
+
+	entry = kcalloc(1, sizeof(*entry), GFP_KERNEL);
+	if (!entry)
+		return NOTIFY_DONE;
+
+	pf_dev = sw_nb_resolve_pf_dev(dev);
+	if (!pf_dev) {
+		kfree(entry);
+		return NOTIFY_DONE;
+	}
+
+	entry->cmd = sw_nb_inetaddr_event_to_otx2_event(event, dev);
+	memcpy(entry->dst6, &addr, sizeof(entry->dst6));
+	entry->dst6_plen = prefix_len;
+	entry->host = 1;
+	entry->ipv6 = 1;
+
+	pf = netdev_priv(pf_dev);
+	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 addr=%pI6c plen=%u mac=%pM\n",
+		   &addr, prefix_len, entry->mac);
+	sw_fib_add_to_list(pf_dev, entry, 1);
+	return NOTIFY_DONE;
+}
+
+int sw_nb_v6_fib_event(struct notifier_block *nb,
+		       unsigned long event, void *ptr)
+{
+	struct fib_notifier_info *info = ptr;
+	struct fib6_entry_notifier_info *f6_eni;
+	struct net_device *fib_dev, *pf_dev;
+	struct fib_entry *entry;
+	struct fib6_info *f6i;
+	struct neighbour *neigh;
+	struct fib6_nh *nh6;
+	struct rt6key *key;
+	struct otx2_nic *pf;
+
+	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_valid_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_dev = sw_nb_resolve_pf_dev(fib_dev);
+	if (!pf_dev)
+		return NOTIFY_DONE;
+
+	pf = netdev_priv(pf_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;
+		neigh_ha_snapshot(entry->mac, neigh, fib_dev);
+		netdev_dbg(fib_dev, "fib found MAC=%pM\n", entry->mac);
+	}
+
+	sw_fib_add_to_list(pf_dev, entry, 1);
+	rcu_read_unlock();
+
+	return NOTIFY_DONE;
+}
+
+int sw_nb_net_v6_neigh_update(struct notifier_block *nb,
+			      unsigned long event, void *ptr)
+{
+	struct net_device *pf_dev;
+	struct neighbour *n = ptr;
+	struct fib_entry *entry;
+	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;
+
+	entry = kcalloc(1, sizeof(*entry), GFP_ATOMIC);
+	if (!entry)
+		return NOTIFY_DONE;
+
+	pf_dev = sw_nb_resolve_pf_dev(n->dev);
+	if (!pf_dev) {
+		kfree(entry);
+		return NOTIFY_DONE;
+	}
+
+	pf = netdev_priv(pf_dev);
+
+	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;
+	neigh_ha_snapshot(entry->mac, n, n->dev);
+	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 %pI6c mac=%pM plen=%u\n",
+		   n->primary_key, entry->mac, n->tbl->key_len * 8);
+
+	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 net_device *pf_dev;
+	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_valid_dev(dev))
+		return NOTIFY_DONE;
+
+	if (ipv6_addr_type(&ifa6->addr) & IPV6_ADDR_LINKLOCAL)
+		return NOTIFY_DONE;
+
+	entry = kcalloc(1, sizeof(*entry), GFP_ATOMIC);
+	if (!entry)
+		return NOTIFY_DONE;
+
+	pf_dev = sw_nb_resolve_pf_dev(dev);
+	if (!pf_dev) {
+		kfree(entry);
+		return NOTIFY_DONE;
+	}
+
+	pf = netdev_priv(pf_dev);
+
+	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;
+	}
+
+	sw_fib_add_to_list(pf_dev, entry, 1);
+
+	netdev_dbg(dev, "inetaddr addr=%pI6c len=%u %pM\n",
+		   &ifa6->addr, ifa6->prefix_len, entry->mac);
+
+	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 00000000000000..78c0df5eb880a7
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v6.h
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Marvell switch driver
+ *
+ * Copyright (C) 2026 Marvell.
+ *
+ */
+#ifndef SW_NB_V6_H_
+#define SW_NB_V6_H_
+
+#include <linux/kconfig.h>
+
+#if IS_ENABLED(CONFIG_IPV6)
+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);
+#else
+static inline int sw_nb_v6_fib_event(struct notifier_block *nb,
+				     unsigned long event, void *ptr)
+{
+	return NOTIFY_DONE;
+}
+
+static inline int sw_nb_net_v6_neigh_update(struct notifier_block *nb,
+					    unsigned long event, void *ptr)
+{
+	return NOTIFY_DONE;
+}
+
+static inline int sw_nb_v6_inetaddr_event(struct notifier_block *nb,
+					  unsigned long event, void *ptr)
+{
+	return NOTIFY_DONE;
+}
+
+static inline int sw_nb_v6_netdev_event(struct notifier_block *unused,
+					unsigned long event, void *ptr)
+{
+	return NOTIFY_DONE;
+}
+#endif
+
+#endif /* SW_NB_V6_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 00000000000000..672f3405de8519
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_trace.c
@@ -0,0 +1,11 @@
+// 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"
+#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 00000000000000..3c94f2d0200187
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_trace.h
@@ -0,0 +1,84 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Marvell RVU Admin Function driver
+ *
+ * Copyright (C) 2026 Marvell.
+ *
+ */
+
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM rvu_sw
+
+#if !defined(SW_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
+#define SW_TRACE_H
+
+#include <linux/types.h>
+#include <linux/tracepoint.h>
+#include <linux/byteorder/generic.h>
+
+#include "mbox.h"
+
+TRACE_EVENT(sw_fl_dump,
+	    TP_PROTO(const char *fname, const char *info, struct fl_tuple *ftuple),
+	    TP_ARGS(fname, info, ftuple),
+	    TP_STRUCT__entry(__string(f, fname)
+			     __string(info, info)
+			     __array(u8, smac, ETH_ALEN)
+			     __array(u8, dmac, ETH_ALEN)
+			     __field_struct(__be16, eth_type)
+			     __field_struct(__be32, sip)
+			     __field_struct(__be32, dip)
+			     __field(u8, ip_proto)
+			     __field_struct(__be16, sport)
+			     __field_struct(__be16, dport)
+			     __field(u8, uni_di)
+			     __field(u16, in_pf)
+			     __field(u16, out_pf)
+	    ),
+	    TP_fast_assign(__assign_str(f);
+			   __assign_str(info);
+			   memcpy(__entry->smac, ftuple->smac, ETH_ALEN);
+			   memcpy(__entry->dmac, ftuple->dmac, ETH_ALEN);
+			   __entry->sip = ftuple->ip4src;
+			   __entry->dip = ftuple->ip4dst;
+			   __entry->eth_type = ftuple->eth_type;
+			   __entry->ip_proto = ftuple->proto;
+			   __entry->sport = ftuple->sport;
+			   __entry->dport = ftuple->dport;
+			   __entry->uni_di = ftuple->uni_di;
+			   __entry->in_pf = ftuple->in_pf;
+			   __entry->out_pf = ftuple->xmit_pf;
+	    ),
+	    TP_printk("[%s] %s: %pM %pI4:%u to %pM %pI4:%u eth_type=%#x proto=%u uni=%u in=%#x out=%#x",
+		      __get_str(f), __get_str(info),
+		      __entry->smac, &__entry->sip, __entry->sport,
+		      __entry->dmac, &__entry->dip, __entry->dport,
+		      __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, const char *info, u32 act),
+	    TP_ARGS(fname, info, act),
+	    TP_STRUCT__entry(__string(fname, fname)
+			     __string(info, info)
+			     __field(u32, act)
+	    ),
+
+	    TP_fast_assign(__assign_str(fname);
+			   __assign_str(info);
+			   __entry->act = act;
+	    ),
+
+	    TP_printk("[%s] %s: act=%u",
+		      __get_str(fname), __get_str(info), __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>

3/2 2026/07/14 05:54 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 65413
							output: 27
							thoughts: 764
						

Thoughts:
**Fuzzing Decision for Marvell OcteonTX2 Switch Driver Patch**

Okay, let's break this down. My initial assessment is that this patch series *absolutely* introduces functional changes to the kernel that necessitate fuzzing. The addition of the Marvell OcteonTX2 switch driver support is a pretty significant piece of work.

The critical flag is `CONFIG_OCTEONTX_SWITCH`, which hinges on `(64BIT && COMPILE_TEST) || ARM64` and `OCTEONTX2_PF`, so we can expect the fuzzer to be testing some architecture-dependent code paths as well.

The changes span a few key areas within `drivers/net/ethernet/marvell/octeontx2/`. We've got new files in `af/switch/` and `nic/switch/`, which is a clear indication of a new functional area. Plus, the modifications to existing files (`af/mbox.h`, `af/rvu.c`, `rvu_nix.c`, `rvu_npc.c`, `rvu_npc_fs.c`, `rvu_rep.c`, `nic/otx2_pf.c`, `otx2_txrx.h`, `otx2_vf.c`, and `rep.c`) reinforce that we're dealing with substantial additions and modifications.

This goes beyond just a driver update. The code is implementing new mailbox messages, enabling switchdev offloading, and handling FDB, FIB, and flow offloading. That's a huge surface area for potential vulnerabilities, and we *definitely* need to fuzz it. So, `WorthFuzzing=true`.

Now, let's identify the key functions. Based on the file modifications, the new mailbox handlers in `rvu.c`, `rvu_nix.c`, `rvu_npc.c`, `rvu_npc_fs.c`, `rvu_rep.c`, `rvu_sw.c`, `rvu_sw_fl.c`, `rvu_sw_l2.c`, and `rvu_sw_l3.c` are going to be critical targets. Specifically, functions like: `rvu_mbox_handler_iface_get_info`, `rvu_mbox_handler_npc_flow_del_n_free`, `rvu_mbox_handler_npc_mcam_mul_stats`, `rvu_mbox_handler_npc_mcam_get_features`, `rvu_mbox_handler_swdev2af_notify`, `rvu_mbox_handler_fl_get_stats`, `rvu_mbox_handler_fl_notify`, `rvu_mbox_handler_fdb_notify`, `rvu_mbox_handler_fib_notify`, `otx2vf_mbox_af2pf_fdb_refresh` and `otx2_mbox_up_handler_af2pf_fdb_refresh` are all potential sources of bugs.

We should also put the switchdev notifiers and flow offload callbacks on the list. `sw_nb_fdb_event`, `sw_nb_fib_event`, `sw_nb_net_event`, `sw_nb_netdev_event`, and `sw_fl_setup_ft_block_ingress_cb` are important for switch integration.

As for configuration, `OCTEONTX_SWITCH` is the key, and it looks like `OCTEONTX2_PF`, `OCTEONTX2_AF` are probably also needed. And `OCTEONTX2_MBOX` is selected by `OCTEONTX2_PF`, so that's important to keep in mind. We might need to make sure those are all enabled for our fuzzing setup.

Okay, let's verify these functions are actually implemented and exposed and make sure those config options are working correctly. Then, we can focus our fuzzing efforts on those specific functions and the paths that involve them.



4/2 2026/07/14 05:54 tool grepper
0m Args:
{
  "Expression": "rvu_mbox_handler_swdev2af_notify"
}

Results:
{
  "Output": "drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.c=35=static int rvu_sw_swdev2af_sender_check(struct rvu *rvu,\n--\ndrivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.c-49-\ndrivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.c:50:int rvu_mbox_handler_swdev2af_notify(struct rvu *rvu,\ndrivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw.c-51-\t\t\t\t     struct swdev2af_notify_req *req,\n"
}

5/2 2026/07/14 05:54 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 67277
							output: 27
							thoughts: 19
						

Thoughts:
Okay, let's dive into this. Here's my thought process on what we're looking at, summarized in the first person, assuming I'm already pretty familiar with the subject matter:

**Analyzing `rvu_mbox_handler_iface_get_info`**

Alright, so the task at hand is to understand `rvu_mbox_handler_iface_get_info`. This function name immediately suggests it's involved with getting interface information related to message box (mbox) handling, likely within the RVU framework, given the "rvu" prefix. My initial guess is that it's probably the access point for retrieving details about a specific interface registered with an mbox handler. I'll need to dig into the definition to be sure. What kind of info are we after? Is it the interface ID, capabilities, the associated handler, any associated buffers? The interface's status? The exact nature of this "info" is crucial. Let's see if the code itself, or related documentation, clarifies the specific data structures and fields returned by this function. I'll also be looking for usage examples to understand how it's intended to be utilized within the overall mbox communication flow. Potential error conditions and their handling will also be of particular interest.



6/2 2026/07/14 05:54 tool grepper
0m Args:
{
  "Expression": "rvu_mbox_handler_iface_get_info"
}

Results:
{
  "Output": "drivers/net/ethernet/marvell/octeontx2/af/rvu.c=1994=static bool rvu_iface_get_info_permitted(struct rvu *rvu, u16 pcifunc)\n--\ndrivers/net/ethernet/marvell/octeontx2/af/rvu.c-2001-\ndrivers/net/ethernet/marvell/octeontx2/af/rvu.c:2002:int rvu_mbox_handler_iface_get_info(struct rvu *rvu, struct msg_req *req,\ndrivers/net/ethernet/marvell/octeontx2/af/rvu.c-2003-\t\t\t\t    struct iface_get_info_rsp *rsp)\n"
}

7/2 2026/07/14 05:54 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 67793
							output: 30
							thoughts: 22
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience, presented in the first person:

**Diving into `rvu_mbox_handler_npc_flow_del_n_free`**

Alright, let's get right to it. My first instinct, looking at that function name, `rvu_mbox_handler_npc_flow_del_n_free`, is to immediately parse it. Okay, it's clearly related to the RVU (I assume related to a virtualized or remote device context) and some kind of message handling within the NPC – likely the Network Processing Core. The "flow_del_n_free" part screams resource management: deletion and memory deallocation. I need to understand the implications of calling this function. What kind of flows are we talking about? Is it ingress, egress, or both? What are the dependencies? Are there any potential race conditions I need to worry about when freeing that memory, especially if it's related to hardware offload or shared resources? Are there any locks involved, and if so, how are they managed? Are we talking about a simple hash table lookup or something more complex? I need to analyze the surrounding code to determine how this function is integrated into the larger flow management and garbage collection scheme. Is it called synchronously or asynchronously? Is it interrupt-safe? Any of these details will influence the overall system stability. It’s also imperative to review any relevant documentation for associated APIs. I need to ensure that the proper preconditions are met before calling this, and that any post-conditions are properly handled. There's a lot packed into that simple function name. Time to dig in!



8/2 2026/07/14 05:54 tool grepper
0m Args:
{
  "Expression": "rvu_mbox_handler_npc_flow_del_n_free"
}

Results:
{
  "Output": "drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c=3490=int rvu_mbox_handler_npc_mcam_free_entry(struct rvu *rvu,\n--\ndrivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c-3546-\ndrivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c:3547:int rvu_mbox_handler_npc_flow_del_n_free(struct rvu *rvu,\ndrivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c-3548-\t\t\t\t\t struct npc_flow_del_n_free_req *mreq,\n"
}

9/2 2026/07/14 05:54 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 68399
							output: 29
							thoughts: 21
						

Thoughts:
Here's my thought process, as if I were considering that function:

**Analyzing `rvu_mbox_handler_npc_mcam_mul_stats`**

Alright, let's dive into `rvu_mbox_handler_npc_mcam_mul_stats`. Given the naming convention, my first assumption is that this function likely handles messages related to the Multi-CAM (MCAM) configuration and statistics within the Non-Programmable Configuration (NPC) module of an RVU (likely a specific hardware component or architecture). "Mul" strongly suggests a multi-part or aggregate operation within the MCAM context. I'll need to immediately think about the possible data structures involved. Specifically, I should consider the structures used to store MCAM rules, statistics counters, and any inter-module communication aspects. What kind of message formats does it support? Does it interact with shared memory regions?

I need to understand what specific statistics are being managed or retrieved. Is it related to rule hits, misses, resource utilization, or perhaps performance metrics? If it's dealing with stats, I need to consider what the unit is for the reported numbers, any potential overflows, and how the data is being stored. What's the context - is this an interrupt handler, a polling function, or called through a more complex message passing protocol? I must look for any lock protection mechanism used in the function, since this potentially manages shared resources.

Furthermore, I have to see how this function integrates into the broader RVU system. What other parts of the system interact with the NPC MCAM? I'll need to track down the call sites and see which messages or data formats trigger this function. Finally, the return value and error handling also should be analyzed in order to see if it is well integrated with the error code and status reporting framework.



10/2 2026/07/14 05:55 tool grepper
0m Args:
{
  "Expression": "rvu_mbox_handler_npc_mcam_mul_stats"
}

Results:
{
  "Output": "drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c=4383=int rvu_mbox_handler_npc_mcam_entry_stats(struct rvu *rvu,\n--\ndrivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c-4434-\ndrivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c:4435:int rvu_mbox_handler_npc_mcam_mul_stats(struct rvu *rvu,\ndrivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c-4436-\t\t\t\t\tstruct npc_mcam_get_mul_stats_req *req,\n--\ndrivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_fl.c=141=int rvu_sw_fl_stats_sync2db(struct rvu *rvu, struct fl_info *fl, int cnt)\n--\ndrivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_fl.c-176-\ndrivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_fl.c:177:\tif (rvu_mbox_handler_npc_mcam_mul_stats(rvu, req, rsp)) {\ndrivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_fl.c-178-\t\tdev_err(rvu-\u003edev, \"Error to get multiple stats\\n\");\n"
}

11/2 2026/07/14 05:55 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 69249
							output: 29
							thoughts: 21
						

Thoughts:
Okay, let's dive into this. Here's my summary, taking the first-person perspective as requested:

**Assessing `rvu_mbox_handler_npc_mcam_get_features`**

Right, let's get down to brass tacks. I need to take a look at the function `rvu_mbox_handler_npc_mcam_get_features`. Given my expertise, I know exactly what this likely *should* be doing - probably handling a mailbox message specifically requesting the supported features of the NPC MCAM (Network Processing Controller's Multi-Channel Advanced Memory). I'm immediately thinking about: what features are relevant in this context? Is this just a simple "give me a feature bitmap" kind of thing, or are there nuances in how the features are requested or presented? Are there specific versions or types of MCAM being supported, and will the feature set change based on that? I'll need to look closely at the data structures involved in the message to understand what the caller is expecting and how the response is constructed. I need to be prepared for the usual suspects: potential error checking (what happens if the MCAM isn't initialized or available? Are there edge cases?), and making sure that the data returned is consistent with any existing feature-related documentation or specifications. Let's dig in and see if it's up to par.



12/2 2026/07/14 05:55 tool grepper
0m Args:
{
  "Expression": "rvu_mbox_handler_npc_mcam_get_features"
}

Results:
{
  "Output": "drivers/net/ethernet/marvell/octeontx2/af/rvu_npc_fs.c=1911=static int npc_delete_flow(struct rvu *rvu, struct rvu_npc_mcam_rule *rule,\n--\ndrivers/net/ethernet/marvell/octeontx2/af/rvu_npc_fs.c-1931-\ndrivers/net/ethernet/marvell/octeontx2/af/rvu_npc_fs.c:1932:int rvu_mbox_handler_npc_mcam_get_features(struct rvu *rvu,\ndrivers/net/ethernet/marvell/octeontx2/af/rvu_npc_fs.c-1933-\t\t\t\t\t   struct msg_req *req,\n"
}

13/2 2026/07/14 05:55 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 69796
							output: 26
							thoughts: 18
						

Thoughts:
Okay, let's dive into this. Here's my thought process on checking `rvu_mbox_handler_fl_get_stats`, summarized in the first person, assuming I'm an expert:

**Investigating `rvu_mbox_handler_fl_get_stats`**

Alright, time to dissect `rvu_mbox_handler_fl_get_stats`. Based on the naming convention, this function is likely responsible for fetching statistics related to the flow level, probably within the context of the mailbox handler. Given that context, I'm expecting this to be a crucial function for monitoring and potentially debugging flow-related issues, or perhaps for performance analysis. I'll need to immediately figure out what kind of flow it's referring to, is it a specific hardware flow, a software representation, or something in-between. Then I will need to understand what stats it's grabbing. Are we talking packet counts, byte counts, error tallies, queue depth, latency, or something else entirely? A solid understanding of the types of stats collected will illuminate the specific purpose of this function and allow me to predict where bottlenecks or issues might arise. I will need to look through the code to identify its usage and how its information is collected and is used down the line. Finally, I will identify any potential areas of concern, like race conditions, locking issues, or performance bottlenecks, that might require closer scrutiny. Let's see what we've got.



14/2 2026/07/14 05:55 tool grepper
0m Args:
{
  "Expression": "rvu_mbox_handler_fl_get_stats"
}

Results:
{
  "Output": "drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_fl.c=232=static void sw_fl_offl_work_handler(struct work_struct *work)\n--\ndrivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_fl.c-263-\ndrivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_fl.c:264:int rvu_mbox_handler_fl_get_stats(struct rvu *rvu,\ndrivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_fl.c-265-\t\t\t\t  struct fl_get_stats_req *req,\n"
}

15/2 2026/07/14 05:55 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 70350
							output: 24
							thoughts: 16
						

Thoughts:
Okay, let's dive into this. Here's my thought process on what I'm about to do, summarized in the first person:

**Analyzing `rvu_mbox_handler_fl_notify`**

Alright, time to get my hands dirty. My immediate thought is, let's dissect `rvu_mbox_handler_fl_notify`. That identifier immediately suggests a function related to the `rvu` (likely a specific RVU implementation or subsystem), and it's dealing with mailboxes (`mbox`) and handlers. The `fl_` prefix usually points towards a flag or feature flag in such contexts, and the use of `notify` strongly suggests that the function's responsibility is to signal something, to make an announcement. Probably related to the handling of a flag, feature flag, or event. I need to understand its purpose within the system and how it interacts with the rest of the message-handling and interrupt process. I want to see how the RVU component uses this to signal an event or condition. It's time to dig into the code and get some answers.



16/2 2026/07/14 05:55 tool grepper
0m Args:
{
  "Expression": "rvu_mbox_handler_fl_notify"
}

Results:
{
  "Output": "drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_fl.c=264=int rvu_mbox_handler_fl_get_stats(struct rvu *rvu,\n--\ndrivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_fl.c-282-\ndrivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_fl.c:283:int rvu_mbox_handler_fl_notify(struct rvu *rvu,\ndrivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_fl.c-284-\t\t\t       struct fl_notify_req *req,\n"
}

17/2 2026/07/14 05:55 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 70821
							output: 25
							thoughts: 17
						

Thoughts:
Okay, let's dive into this. Here's my thought process on what we're looking at, summarized from the perspective of an expert in the field:

**Analyzing `rvu_mbox_handler_fdb_notify`**

Alright, let's get right to it. My initial thought is to scrutinize `rvu_mbox_handler_fdb_notify`. This function is likely the central point for handling Fabric Data Base (FDB) notifications within this particular system. Based on the name alone, I'd wager it's responsible for the critical task of receiving and interpreting messages related to FDB updates, additions, deletions, or status changes.

I'll be looking for clues immediately about message processing. I need to understand: What are the inputs? Are they structured or unstructured? What data structures are used internally to process the notifications? Does it involve any locking mechanisms? Are there any obvious points of potential contention or performance bottlenecks? Any calls to other functions or modules that are suspicious?

I'm expecting to see a switch statement or series of conditional checks, or perhaps even a function pointer table, that directs the notification to the correct processing path. I want to see if any sort of queueing is happening or if this function is blocking, and how it handles failures.

The code here is a critical piece of the puzzle, and a thorough assessment of this function is paramount to understanding FDB behavior.



18/2 2026/07/14 05:55 tool grepper
0m Args:
{
  "Expression": "rvu_mbox_handler_fdb_notify"
}

Results:
{
  "Output": "drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l2.c=379=int rvu_sw_l2_fdb_list_entry_add(struct rvu *rvu, u16 pcifunc, u8 *mac)\n--\ndrivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l2.c-437-\ndrivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l2.c:438:int rvu_mbox_handler_fdb_notify(struct rvu *rvu,\ndrivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l2.c-439-\t\t\t\tstruct fdb_notify_req *req,\n"
}

19/2 2026/07/14 05:55 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 71383
							output: 24
							thoughts: 16
						

Thoughts:
Okay, let's dive into this. Here's my thought process on what we're looking at, summarized from the inside:

**Analyzing `rvu_mbox_handler_fib_notify`**

Alright, time to get to it. First, I need to take a look at `rvu_mbox_handler_fib_notify`. This function name immediately suggests it's involved with the FIB (Forwarding Information Base) and notifications, likely related to updates or changes within the forwarding plane. Given the "mbox" prefix, I'm guessing this interacts with a mailbox mechanism, potentially for inter-process communication or hardware signalling. So, my initial line of thought is that this function likely receives and processes messages concerning FIB updates. I need to understand its role in the overall architecture. Is it a central point for receiving FIB change events? Is it responsible for propagating these changes to other parts of the system, maybe other line cards or the data plane itself? My focus now will be on tracing the entry points, understanding the data structures involved, and the actions performed within the function. I'm looking for the "what," "where," and "why" of its operation.



20/2 2026/07/14 05:55 tool grepper
0m Args:
{
  "Expression": "rvu_mbox_handler_fib_notify"
}

Results:
{
  "Output": "drivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l3.c=170=static void sw_l3_offl_work_handler(struct work_struct *work)\n--\ndrivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l3.c-203-\ndrivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l3.c:204:int rvu_mbox_handler_fib_notify(struct rvu *rvu,\ndrivers/net/ethernet/marvell/octeontx2/af/switch/rvu_sw_l3.c-205-\t\t\t\tstruct fib_notify_req *req,\n"
}

21/2 2026/07/14 05:55 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 71873
							output: 29
							thoughts: 21
						

Thoughts:
Here's my attempt at summarizing the thought process, tailored for an expert audience and written in the first person:

**Investigating `otx2vf_mbox_af2pf_fdb_refresh`**

Okay, so I'm looking at `otx2vf_mbox_af2pf_fdb_refresh`. My initial thought is to dive straight into why this function is even being called. Is it part of the normal data path? Is it triggered by some specific event, like a MAC address conflict resolution, or maybe a VLAN change? I need to understand its context within the broader architecture.

Next, I'll want to trace its execution flow. I need to know where it's being called from, what the inputs are, and what other functions it interacts with. Are there any potential race conditions I should be aware of? Are we talking about a simple FDB refresh, or is there some more complex logic tied to the virtual fabric? The name suggests it's related to the FDB and virtual fabric, so my suspicions are immediately piqued about potential interactions with VF adapters or OTX-related forwarding processes.

I'll be looking for any potential performance bottlenecks or memory leaks within this function. Are there any inefficient loops, or could we be accidentally triggering a flood of FDB updates under certain circumstances? I should also examine the data structures it uses and make sure they are thread-safe if multiple contexts are accessing the FDB. I wonder what the cache coherency implications are.

Finally, I'll need to know what happens if this function fails or encounters an error. Does it handle exceptions gracefully, or could we end up with corrupted FDB entries or dropped packets? If there are error recovery procedures, I need to know what they are and see how robust they are. Any logging, error messages, or statistics generated are of particular interest.



22/2 2026/07/14 05:55 tool grepper
0m Args:
{
  "Expression": "otx2vf_mbox_af2pf_fdb_refresh"
}

Results:
{
  "Output": "drivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c=87=static void otx2vf_vfaf_mbox_handler(struct work_struct *work)\n--\ndrivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c-118-#if IS_ENABLED(CONFIG_OCTEONTX_SWITCH)\ndrivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c:119:static int otx2vf_mbox_af2pf_fdb_refresh(struct otx2_nic *vf,\ndrivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c-120-\t\t\t\t\t struct af2pf_fdb_refresh_req *req,\n--\ndrivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c-136-#else\ndrivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c:137:static int otx2vf_mbox_af2pf_fdb_refresh(struct otx2_nic *vf,\ndrivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c-138-\t\t\t\t\t struct af2pf_fdb_refresh_req *req,\n--\ndrivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c=145=static int otx2vf_process_mbox_msg_up(struct otx2_nic *vf,\n--\ndrivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c-182-\t\trsp-\u003ehdr.rc = 0;\ndrivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c:183:\t\terr = otx2vf_mbox_af2pf_fdb_refresh(vf,\ndrivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c-184-\t\t\t\t\t\t    (struct af2pf_fdb_refresh_req *)req,\n"
}

23/2 2026/07/14 05:55 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 72864
							output: 32
							thoughts: 24
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience, and delivered as a first-person summary:

**Digging into `otx2_mbox_up_handler_af2pf_fdb_refresh`**

Alright, time to dive into that routine. Let's see, what's got my attention now is `otx2_mbox_up_handler_af2pf_fdb_refresh`. That name alone tells me it's likely involved in handling messages related to the OTX2 mailbox, specifically updates to the Address Forwarding Database (FDB) concerning traffic from AF (presumably Application Fabric) to the Packet Fabric (PF). So, I'm expecting this is a critical component for packet forwarding integrity, especially in a distributed or multi-chip setup. I'll need to refresh my understanding of the messaging flow between AF and PF. This is likely responsible for maintaining FDB synchronization and preventing forwarding loops or black holes. My initial questions are around the refresh mechanism itself. Is it push-based, pull-based, or a combination? What triggers the refresh? Are there any potential race conditions I should be aware of, especially concerning concurrent updates or deletions to the FDB from other parts of the system? Are there any potential memory leak points? The naming convention suggests there should be message structures related to this handler. I'll need to locate those to understand the data being passed and how the refresh occurs. I'll need to look at any error handling and recovery that's in place, too, because if this fails, the implications for the whole system could be severe. Let's get to the code.



24/2 2026/07/14 05:56 tool grepper
0m Args:
{
  "Expression": "otx2_mbox_up_handler_af2pf_fdb_refresh"
}

Results:
{
  "Output": "drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.c-22-\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.c:23:int otx2_mbox_up_handler_af2pf_fdb_refresh(struct otx2_nic *pf,\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.c-24-\t\t\t\t\t   struct af2pf_fdb_refresh_req *req,\n--\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.c=185=void sw_fdb_deinit(void)\n--\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.c-216-\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.c:217:int otx2_mbox_up_handler_af2pf_fdb_refresh(struct otx2_nic *pf,\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fdb.c-218-\t\t\t\t\t   struct af2pf_fdb_refresh_req *req,\n--\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.h=15=struct msg_rsp;\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.h-16-\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.h:17:int otx2_mbox_up_handler_af2pf_fdb_refresh(struct otx2_nic *pf,\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.h-18-\t\t\t\t\t   struct af2pf_fdb_refresh_req *req,\n"
}

25/2 2026/07/14 05:56 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 73777
							output: 22
							thoughts: 14
						

Thoughts:
Okay, here's my interpretation of that thought process, assuming I'm deeply familiar with the relevant technical domain and context.

**Checking `sw_nb_fdb_event`**

Alright, *that* thought immediately focuses my attention. I need to take a look at the `sw_nb_fdb_event`. Just the name itself tells me we're likely dealing with software (sw), network bridging (nb), and the Forwarding Database (FDB). The "event" implies something significant has occurred or is about to occur related to the FDB. Given my expertise, I'm already anticipating what kinds of issues this could point to: potential inconsistencies in the MAC address table, learning or aging problems, flooding, maybe even a security vulnerability being exploited through MAC address spoofing. Understanding the nature of this event is crucial. I need to figure out what triggered it, what data is associated with it, and what the current state of the bridging system is. It could be anything from a simple MAC address learning event to a complete FDB corruption. My immediate next steps are going to depend heavily on the context of *where* I found this and what the associated logging or system information reveals. Is it a persistent error? A sporadic occurrence? I need to drill down into the details of this event to understand its impact and address the root cause effectively.



26/2 2026/07/14 05:56 tool grepper
0m Args:
{
  "Expression": "sw_nb_fdb_event"
}

Results:
{
  "Output": "drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c=102=bool sw_nb_is_valid_dev(struct net_device *netdev)\n--\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c-137-\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c:138:static int sw_nb_fdb_event(struct notifier_block *unused,\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c-139-\t\t\t   unsigned long event, void *ptr)\n--\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c=171=static struct notifier_block sw_nb_fdb = {\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c:172:\t.notifier_call = sw_nb_fdb_event,\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c-173-};\n"
}

27/2 2026/07/14 05:56 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 74404
							output: 21
							thoughts: 13
						

Thoughts:
Here's a summary of my thought process, as requested:

**Checking `sw_nb_fib_event`**

Okay, let's dive in. First, I need to evaluate the context of `sw_nb_fib_event`. Is this a system-level event related to software, specifically non-blocking FIB lookups? Or is this something tied into a particular application or a more abstracted network configuration component? I need to quickly establish the potential scope. Immediately, I'm thinking about typical debugging scenarios. Is this a potential trigger point for performance bottlenecks, or maybe even error propagation within a larger control plane/data plane interaction? What are the potential consequences if this event is somehow mishandled or if it's exhibiting unexpected behavior? I want to determine if this has the potential for a cascading negative effect. I'll need to consider how this event integrates into the overall data flow, which could shed light on whether this requires immediate attention or is something that can be analyzed later. I need to figure out what type of data is being passed around, which will help me see if the event carries any relevant information or flags. I need to determine where it's called from and how frequently it happens. Understanding its role will inform the next steps. I'm going to start by getting a broad overview and then narrowing my focus.



28/2 2026/07/14 05:56 tool grepper
0m Args:
{
  "Expression": "sw_nb_fib_event"
}

Results:
{
  "Output": "drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c=175=static void __maybe_unused\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c:176:sw_nb_fib_event_dump(unsigned long event, void *ptr)\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c-177-{\n--\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c-205-#define SWITCH_NB_FIB_EVENT_DUMP(...) \\\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c:206:\tsw_nb_fib_event_dump(__VA_ARGS__)\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c-207-\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c:208:int sw_nb_fib_event_to_otx2_event(int event, struct net_device *netdev)\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c-209-{\n--\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c-224-\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c:225:static int sw_nb_fib_event(struct notifier_block *nb,\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c-226-\t\t\t   unsigned long event, void *ptr)\n--\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c=256=static struct notifier_block sw_nb_fib = {\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c:257:\t.notifier_call = sw_nb_fib_event,\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c-258-};\n--\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.h=39=bool sw_nb_is_cavium_dev(struct net_device *netdev);\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.h:40:int sw_nb_fib_event_to_otx2_event(int event, struct net_device *netdev);\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.h-41-int sw_nb_inetaddr_event_to_otx2_event(int event, struct net_device *netdev);\n--\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.c=146=int sw_nb_v4_fib_event(struct notifier_block *nb,\n--\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.c-205-\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.c:206:\t\tentry-\u003ecmd = sw_nb_fib_event_to_otx2_event(event, dev);\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.c-207-\t\tentry-\u003edst = (__force __be32)fen_info-\u003edst;\n--\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.c-276-\t\tpf = netdev_priv(host_pf_dev);\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.c:277:\t\tentry-\u003ecmd = sw_nb_fib_event_to_otx2_event(event, host_pf_dev);\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.c-278-\t\tentry-\u003edst = haddr[i];\n--\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v6.c=99=int sw_nb_v6_fib_event(struct notifier_block *nb,\n--\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v6.c-156-\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v6.c:157:\tentry-\u003ecmd = sw_nb_fib_event_to_otx2_event(event, fib_dev);\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v6.c-158-\tentry-\u003eipv6 = 1;\n"
}

29/2 2026/07/14 05:56 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 76024
							output: 21
							thoughts: 13
						

Thoughts:
Here's my attempt at summarizing the thought process, tailored for an expert audience:

**Analyzing `sw_nb_net_event`**

Okay, time to dive in. My immediate thought is to check `sw_nb_net_event`. This is a crucial area, and depending on the context, could be a key indicator for numerous network-related issues. I'm going to start by reviewing it to see if there's any immediate evidence of unusual activity, resource bottlenecks, or error conditions. It could be related to several aspects, like the networking stack, network interfaces, or connection management. I will start looking at any relevant metrics to see if there are any suspicious trends or anomalies, or maybe specific events that need further investigation. My goal is to determine if this is a starting point for the problem, or if it is just a routine operation and not critical.



30/2 2026/07/14 05:56 tool grepper
0m Args:
{
  "Expression": "sw_nb_net_event"
}

Results:
{
  "Output": "drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c=256=static struct notifier_block sw_nb_fib = {\n--\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c-259-\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c:260:static int sw_nb_net_event(struct notifier_block *nb,\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c-261-\t\t\t   unsigned long event, void *ptr)\n--\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c=284=static struct notifier_block sw_nb_netevent = {\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c:285:\t.notifier_call = sw_nb_net_event,\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c-286-\n"
}

31/2 2026/07/14 05:56 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 76539
							output: 22
							thoughts: 14
						

Thoughts:
Okay, let's dive into this. Here's my thought process on this:

**Analyzing `sw_nb_netdev_event`**

Right, so I see `sw_nb_netdev_event`. My initial instinct is to dig into what's happening there. This name suggests it's a critical piece related to network device events, potentially within a software-defined networking (SDN) or network bridge context, given the 'sw' prefix. I need to understand the specifics. I'll be looking for where this function is defined, what it receives as inputs (the event type, device information, state transitions, etc.), and what actions it takes. Is it a notification mechanism? Does it trigger control plane updates? Does it interact with forwarding tables? I need to understand its role in maintaining network state and handling network interface changes. It's likely that it plays a vital role in network interface up/down events, link state changes, or even MAC address learning and flooding. The goal is to fully understand its impact on overall network behaviour. I'll need to trace its call graph and look for its side effects to evaluate its importance.



32/2 2026/07/14 05:56 tool grepper
0m Args:
{
  "Expression": "sw_nb_netdev_event"
}

Results:
{
  "Output": "drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c=311=static struct notifier_block sw_nb_v6_inetaddr = {\n--\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c-315-\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c:316:static int sw_nb_netdev_event(struct notifier_block *unused,\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c-317-\t\t\t      unsigned long event, void *ptr)\n--\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c=345=static struct notifier_block sw_nb_netdev = {\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c:346:\t.notifier_call = sw_nb_netdev_event,\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c-347-};\n"
}

33/2 2026/07/14 05:56 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 77123
							output: 27
							thoughts: 19
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience, and delivered as a first-person summary:

**Digging into `sw_fl_setup_ft_block_ingress_cb`**

Alright, let's dive into this `sw_fl_setup_ft_block_ingress_cb` function. My immediate thought is, what are we trying to achieve here? I'm assuming, given the name, that this callback is responsible for setting up or configuring something related to the filtering table and blocking ingress traffic. More specifically, I expect it handles the setup for blocking ingress traffic.

Knowing the context of how this code fits into the larger architecture is key; I need to keep in mind the data plane versus the control plane aspects and the role of the forwarding table. I need to figure out which structures it interacts with, what resources it allocates (if any), and how it integrates into the overall ingress processing pipeline. Is this invoked during initialization, dynamically when a block rule is added, or during some other event? What's its interaction with the other ingress callback functions? Is it responsible to create ingress rules in the hardware?

I'm thinking about typical failure modes: memory allocation errors, potential race conditions if multiple threads are involved, and the implications of incorrect configuration. I need to understand how the function handles errors, if it cleans up properly, and what its overall impact on performance is likely to be.

My next step is to examine the function's definition, the surrounding code, and any relevant documentation. I need to form a mental model of this function’s interaction with all the relevant parts of the system.



34/2 2026/07/14 05:56 tool grepper
0m Args:
{
  "Expression": "sw_fl_setup_ft_block_ingress_cb"
}

Results:
{
  "Output": "drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.c-26-#if !IS_ENABLED(CONFIG_OCTEONTX_SWITCH)\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.c:27:int sw_fl_setup_ft_block_ingress_cb(enum tc_setup_type type,\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.c-28-\t\t\t\t    void *type_data, void *cb_priv)\n--\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.c=82=static int sw_fl_ct_cb_get(struct nf_flowtable *ft, struct otx2_nic *nic)\n--\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.c-94-\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.c:95:\terr = nf_flow_table_offload_add_cb(ft, sw_fl_setup_ft_block_ingress_cb, nic);\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.c-96-\tif (err \u0026\u0026 err != -EEXIST) {\n--\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.c-104-\t\t\tnf_flow_table_offload_del_cb(ft,\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.c:105:\t\t\t\t\t\t     sw_fl_setup_ft_block_ingress_cb,\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.c-106-\t\t\t\t\t\t     nic);\n--\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.c=120=static void sw_fl_ct_cb_put(struct nf_flowtable *ft, struct otx2_nic *nic)\n--\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.c-133-\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.c:134:\tnf_flow_table_offload_del_cb(ft, sw_fl_setup_ft_block_ingress_cb, nic);\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.c-135-\tkfree(entry);\n--\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.c=174=static void sw_fl_ct_cb_flush(void)\n--\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.c-189-\t\tnf_flow_table_offload_del_cb(entry-\u003eft,\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.c:190:\t\t\t\t\t     sw_fl_setup_ft_block_ingress_cb,\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.c-191-\t\t\t\t\t     entry-\u003enic);\n--\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.c=696=static bool init_done;\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.c-697-\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.c:698:int sw_fl_setup_ft_block_ingress_cb(enum tc_setup_type type,\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.c-699-\t\t\t\t    void *type_data, void *cb_priv)\n--\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.h=11=int sw_fl_init(void);\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.h:12:int sw_fl_setup_ft_block_ingress_cb(enum tc_setup_type type,\ndrivers/net/ethernet/marvell/octeontx2/nic/switch/sw_fl.h-13-\t\t\t\t    void *type_data, void *cb_priv);\n"
}

35/2 2026/07/14 05:56 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 78681
							output: 456
							thoughts: 557
						

Thoughts:
**Configuration and Fuzzing Strategy for OCTEONTX_SWITCH**

Alright, let's get down to brass tacks. We're dealing with `OCTEONTX_SWITCH` here, and I need to ensure the correct dependencies are in place. My initial thought is that we need to confirm whether `OCTEONTX2_PF` and `OCTEONTX2_AF` are *required* along with `OCTEONTX_SWITCH`. A quick check confirms that `OCTEONTX_SWITCH` indeed depends on `OCTEONTX2_PF`. Further, `OCTEONTX2_PF` selects `OCTEONTX2_MBOX`, which is essential. `OCTEONTX2_AF` appears to be the admin function driver, so that's going in as well. So, I'll add `OCTEONTX_SWITCH`, `OCTEONTX2_PF`, and `OCTEONTX2_AF` to the configuration.

Now, for the fun part: fuzzing. I need to assemble a list of functions that warrant a closer look under fuzzing. Based on my preliminary analysis, these mailbox handlers and notifier callbacks are good candidates. So the list starts with:

*   `rvu_mbox_handler_iface_get_info`
*   `rvu_mbox_handler_npc_flow_del_n_free`
*   `rvu_mbox_handler_npc_mcam_mul_stats`
*   `rvu_mbox_handler_npc_mcam_get_features`
*   `rvu_mbox_handler_swdev2af_notify`
*   `rvu_mbox_handler_fl_get_stats`
*   `rvu_mbox_handler_fl_notify`
*   `rvu_mbox_handler_fdb_notify`
*   `rvu_mbox_handler_fib_notify`
*   `otx2vf_mbox_af2pf_fdb_refresh`
*   `otx2_mbox_up_handler_af2pf_fdb_refresh`
*   `sw_nb_fdb_event`
*   `sw_nb_fib_event`
*   `sw_nb_net_event`
*   `sw_nb_netdev_event`
*   `sw_fl_setup_ft_block_ingress_cb`

I'm thinking, "Is that it? Am I missing anything crucial here?" So, I'll quickly check for any other functions related to the network bridge. Ah yes, there's more to consider: `sw_nb_v4_fib_event`, `sw_nb_net_v4_neigh_update`, `sw_nb_v4_inetaddr_event`, `sw_nb_v4_netdev_event`. and `sw_nb_v6_fib_event`, `sw_nb_net_v6_neigh_update`, `sw_nb_v6_inetaddr_event`, `sw_nb_v6_netdev_event`.
Also I need to add: `sw_fdb_add_to_list`, `sw_fib_add_to_list`, `sw_fl_add_to_list` to cover all aspects.

So, taking all of that into consideration, I'll be sure to include both the mailbox handlers and the various notifier callbacks to give this a comprehensive sweep. I believe that should give us the coverage we need.



36/2 2026/07/14 05:56 tool set-results
0m Args:
{
  "EnableConfigs": [
    "OCTEONTX_SWITCH",
    "OCTEONTX2_PF",
    "OCTEONTX2_AF"
  ],
  "FocusSymbols": [
    "rvu_mbox_handler_iface_get_info",
    "rvu_mbox_handler_npc_flow_del_n_free",
    "rvu_mbox_handler_npc_mcam_mul_stats",
    "rvu_mbox_handler_npc_mcam_get_features",
    "rvu_mbox_handler_swdev2af_notify",
    "rvu_mbox_handler_fl_get_stats",
    "rvu_mbox_handler_fl_notify",
    "rvu_mbox_handler_fdb_notify",
    "rvu_mbox_handler_fib_notify",
    "otx2vf_mbox_af2pf_fdb_refresh",
    "otx2_mbox_up_handler_af2pf_fdb_refresh",
    "sw_nb_fdb_event",
    "sw_nb_fib_event",
    "sw_nb_net_event",
    "sw_nb_netdev_event",
    "sw_fl_setup_ft_block_ingress_cb",
    "sw_nb_v4_fib_event",
    "sw_nb_net_v4_neigh_update",
    "sw_nb_v4_inetaddr_event",
    "sw_nb_v4_netdev_event",
    "sw_nb_v6_fib_event",
    "sw_nb_net_v6_neigh_update",
    "sw_nb_v6_inetaddr_event",
    "sw_nb_v6_netdev_event"
  ],
  "Reasoning": "The patch adds support for Marvell OcteonTX2 switch driver, introducing new mailbox messages, switchdev offloading, FDB, FIB, and flow offloading. This is a functional change that adds new reachable code paths.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "OCTEONTX_SWITCH",
    "OCTEONTX2_PF",
    "OCTEONTX2_AF"
  ],
  "FocusSymbols": [
    "rvu_mbox_handler_iface_get_info",
    "rvu_mbox_handler_npc_flow_del_n_free",
    "rvu_mbox_handler_npc_mcam_mul_stats",
    "rvu_mbox_handler_npc_mcam_get_features",
    "rvu_mbox_handler_swdev2af_notify",
    "rvu_mbox_handler_fl_get_stats",
    "rvu_mbox_handler_fl_notify",
    "rvu_mbox_handler_fdb_notify",
    "rvu_mbox_handler_fib_notify",
    "otx2vf_mbox_af2pf_fdb_refresh",
    "otx2_mbox_up_handler_af2pf_fdb_refresh",
    "sw_nb_fdb_event",
    "sw_nb_fib_event",
    "sw_nb_net_event",
    "sw_nb_netdev_event",
    "sw_fl_setup_ft_block_ingress_cb",
    "sw_nb_v4_fib_event",
    "sw_nb_net_v4_neigh_update",
    "sw_nb_v4_inetaddr_event",
    "sw_nb_v4_netdev_event",
    "sw_nb_v6_fib_event",
    "sw_nb_net_v6_neigh_update",
    "sw_nb_v6_inetaddr_event",
    "sw_nb_v6_netdev_event"
  ],
  "Reasoning": "The patch adds support for Marvell OcteonTX2 switch driver, introducing new mailbox messages, switchdev offloading, FDB, FIB, and flow offloading. This is a functional change that adds new reachable code paths.",
  "WorthFuzzing": true
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)