[PATCH net-next 6/9] octeontx2-pf: register switch notifiers for eswitch offload
From: Ratheesh Kannoth <rkannoth@marvell.com>
Date: 2026-06-30 02:48:00
Also in:
lkml
Subsystem:
marvell octeontx2 physical function driver, networking drivers, the rest · Maintainers:
Sunil Goutham, Geetha sowjanya, Ratheesh Kannoth, Subbaraya Sundeep, Bharat Bhushan, Andrew Lunn, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds
The representor enables switch mode via devlink; register and unregister the switch notifier blocks when that mode is turned on or off so the PF can observe FIB routes, neighbour updates, IPv4/IPv6 address changes, netdev state, and switchdev FDB notifications. Add sw_nb_v4.c and sw_nb_v6.c for IPv4 and IPv6-specific handling, build sw_nb_v6.o only when CONFIG_IPV6 is set, and extend sw_nb.c with device filtering for Cavium ports behind bridges and VLANs. Initialize and tear down the existing sw_fdb, sw_fib, and sw_fl helpers together with notifier registration. Signed-off-by: Ratheesh Kannoth <rkannoth@marvell.com> --- .../ethernet/marvell/octeontx2/nic/Makefile | 7 +- .../net/ethernet/marvell/octeontx2/nic/rep.c | 9 + .../marvell/octeontx2/nic/switch/sw_nb.c | 410 +++++++++++++++++- .../marvell/octeontx2/nic/switch/sw_nb.h | 28 +- .../marvell/octeontx2/nic/switch/sw_nb_v4.c | 323 ++++++++++++++ .../marvell/octeontx2/nic/switch/sw_nb_v4.h | 21 + .../marvell/octeontx2/nic/switch/sw_nb_v6.c | 237 ++++++++++ .../marvell/octeontx2/nic/switch/sw_nb_v6.h | 21 + 8 files changed, 1050 insertions(+), 6 deletions(-) create mode 100644 drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.c create mode 100644 drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.h create mode 100644 drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v6.c create mode 100644 drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v6.h
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/Makefile b/drivers/net/ethernet/marvell/octeontx2/nic/Makefile
index da87e952c187..0e12659876e0 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/Makefile
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/Makefile@@ -13,7 +13,12 @@ rvu_nicpf-y := otx2_pf.o otx2_common.o otx2_txrx.o otx2_ethtool.o \ switch/sw_fdb.o switch/sw_fl.o ifdef CONFIG_OCTEONTX_SWITCH -rvu_nicpf-y += switch/sw_nb.o switch/sw_fib.o +rvu_nicpf-y += switch/sw_nb.o switch/sw_fib.o \ + switch/sw_nb_v4.o + +ifdef CONFIG_IPV6 +rvu_nicpf-y += switch/sw_nb_v6.o +endif endif rvu_nicvf-y := otx2_vf.o
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/rep.c b/drivers/net/ethernet/marvell/octeontx2/nic/rep.c
index 257a2ae6a53e..e4c01ac87477 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/rep.c
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/rep.c@@ -15,6 +15,7 @@ #include "cn10k.h" #include "otx2_reg.h" #include "rep.h" +#include "switch/sw_nb.h" #define DRV_NAME "rvu_rep" #define DRV_STRING "Marvell RVU Representor Driver"
@@ -399,6 +400,9 @@ static void rvu_rep_get_stats64(struct net_device *dev, static int rvu_eswitch_config(struct otx2_nic *priv, u8 ena) { +#if IS_ENABLED(CONFIG_OCTEONTX_SWITCH) + struct net_device *netdev = priv->netdev; +#endif struct devlink_port_attrs attrs = {}; struct esw_cfg_req *req;
@@ -414,6 +418,11 @@ static int rvu_eswitch_config(struct otx2_nic *priv, u8 ena) memcpy(req->switch_id, attrs.switch_id.id, attrs.switch_id.id_len); otx2_sync_mbox_msg(&priv->mbox); mutex_unlock(&priv->mbox.lock); + +#if IS_ENABLED(CONFIG_OCTEONTX_SWITCH) + ena ? sw_nb_register(netdev) : sw_nb_unregister(netdev); +#endif + return 0; }
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c
index 2d14a0590c5d..5d69961f516b 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.c@@ -4,14 +4,420 @@ * Copyright (C) 2026 Marvell. * */ +#include <linux/kernel.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <net/switchdev.h> +#include <net/netevent.h> +#include <net/arp.h> +#include <net/route.h> +#include <linux/inetdevice.h> +#include <net/addrconf.h> + +#include "../otx2_reg.h" +#include "../otx2_common.h" +#include "../otx2_struct.h" +#include "../cn10k.h" #include "sw_nb.h" +#include "sw_fdb.h" +#include "sw_fib.h" +#include "sw_fl.h" +#include "sw_nb_v4.h" +#include "sw_nb_v6.h" + +/* PF netdev for netdev_* logging when notifier info has no device */ +static struct net_device *sw_nb_pf_netdev; -int sw_nb_unregister(void) +static const char *sw_nb_cmd2str[OTX2_CMD_MAX] = { + [OTX2_DEV_UP] = "OTX2_DEV_UP", + [OTX2_DEV_DOWN] = "OTX2_DEV_DOWN", + [OTX2_DEV_CHANGE] = "OTX2_DEV_CHANGE", + [OTX2_NEIGH_UPDATE] = "OTX2_NEIGH_UPDATE", + [OTX2_FIB_ENTRY_REPLACE] = "OTX2_FIB_ENTRY_REPLACE", + [OTX2_FIB_ENTRY_ADD] = "OTX2_FIB_ENTRY_ADD", + [OTX2_FIB_ENTRY_DEL] = "OTX2_FIB_ENTRY_DEL", + [OTX2_FIB_ENTRY_APPEND] = "OTX2_FIB_ENTRY_APPEND", +}; + +const char *sw_nb_get_cmd2str(int cmd) { + return sw_nb_cmd2str[cmd]; +} +EXPORT_SYMBOL(sw_nb_get_cmd2str); + +bool sw_nb_is_cavium_dev(struct net_device *netdev) +{ + struct pci_dev *pdev; + struct device *dev; + + dev = netdev->dev.parent; + if (!dev) + return false; + + pdev = container_of(dev, struct pci_dev, dev); + if (pdev->vendor != PCI_VENDOR_ID_CAVIUM) + return false; + + return true; +} + +static int sw_nb_check_slaves(struct net_device *dev, + struct netdev_nested_priv *priv) +{ + int *cnt; + + if (!priv->flags) + return 0; + + priv->flags &= sw_nb_is_cavium_dev(dev); + if (priv->flags) { + cnt = priv->data; + (*cnt)++; + } + return 0; } -int sw_nb_register(void) +bool sw_nb_is_valid_dev(struct net_device *netdev) +{ + struct netdev_nested_priv priv; + struct net_device *br; + int cnt = 0; + + priv.flags = true; + priv.data = &cnt; + + if (netif_is_bridge_master(netdev) || is_vlan_dev(netdev)) { + netdev_walk_all_lower_dev(netdev, sw_nb_check_slaves, &priv); + return priv.flags && !!*(int *)priv.data; + } + + if (netif_is_bridge_port(netdev)) { + br = netdev_master_upper_dev_get_rcu(netdev); + if (!br) + return false; + + netdev_walk_all_lower_dev(br, sw_nb_check_slaves, &priv); + return priv.flags && !!*(int *)priv.data; + } + + return sw_nb_is_cavium_dev(netdev); +} + +static int sw_nb_fdb_event(struct notifier_block *unused, + unsigned long event, void *ptr) +{ + struct net_device *dev = switchdev_notifier_info_to_dev(ptr); + struct switchdev_notifier_fdb_info *fdb_info = ptr; + + if (!sw_nb_is_valid_dev(dev)) + return NOTIFY_DONE; + + switch (event) { + case SWITCHDEV_FDB_ADD_TO_DEVICE: + if (fdb_info->is_local) + break; + break; + + case SWITCHDEV_FDB_DEL_TO_DEVICE: + if (fdb_info->is_local) + break; + break; + + default: + return NOTIFY_DONE; + } + + return NOTIFY_DONE; +} + +static struct notifier_block sw_nb_fdb = { + .notifier_call = sw_nb_fdb_event, +}; + +static void __maybe_unused +sw_nb_fib_event_dump(unsigned long event, void *ptr) { + struct fib_entry_notifier_info *fen_info = ptr; + struct net_device *log_dev; + struct fib_nh *fib_nh; + struct fib_info *fi; + int i; + + fi = fen_info->fi; + log_dev = (fi && fi->fib_nhs) ? fi->fib_nh->fib_nh_dev : sw_nb_pf_netdev; + if (log_dev) + netdev_info(log_dev, "%s: FIB event=%lu dst=%#x dstlen=%u type=%u\n", + __func__, event, fen_info->dst, fen_info->dst_len, + fen_info->type); + + if (!fi) + return; + + fib_nh = fi->fib_nh; + for (i = 0; i < fi->fib_nhs; i++, fib_nh++) { + if (!fib_nh->fib_nh_dev) + continue; + netdev_info(fib_nh->fib_nh_dev, + "%s: dev=%s saddr=%#x gw=%#x\n", + __func__, fib_nh->fib_nh_dev->name, + fib_nh->nh_saddr, fib_nh->fib_nh_gw4); + } +} + +#define SWITCH_NB_FIB_EVENT_DUMP(...) \ + sw_nb_fib_event_dump(__VA_ARGS__) + +int sw_nb_fib_event_to_otx2_event(int event, struct net_device *netdev) +{ + switch (event) { + case FIB_EVENT_ENTRY_REPLACE: + return OTX2_FIB_ENTRY_REPLACE; + case FIB_EVENT_ENTRY_ADD: + return OTX2_FIB_ENTRY_ADD; + case FIB_EVENT_ENTRY_DEL: + return OTX2_FIB_ENTRY_DEL; + default: + break; + } + + netdev_err(netdev, "Wrong FIB event %d\n", event); + return -1; +} + +static int sw_nb_fib_event(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct fib_notifier_info *info = ptr; + + switch (event) { + case FIB_EVENT_ENTRY_REPLACE: + case FIB_EVENT_ENTRY_ADD: + case FIB_EVENT_ENTRY_DEL: + break; + default: + if (sw_nb_pf_netdev) + netdev_dbg(sw_nb_pf_netdev, + "%s: Won't process FIB event %lu\n", + __func__, event); + return NOTIFY_DONE; + } + + switch (info->family) { + case AF_INET: + return sw_nb_v4_fib_event(nb, event, ptr); +#if IS_ENABLED(CONFIG_IPV6) + case AF_INET6: + return sw_nb_v6_fib_event(nb, event, ptr); +#endif + default: + break; + } + return NOTIFY_DONE; +} + +static struct notifier_block sw_nb_fib = { + .notifier_call = sw_nb_fib_event, +}; + +static int sw_nb_net_event(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct neighbour *n = ptr; + + if (!sw_nb_is_valid_dev(n->dev)) + return NOTIFY_DONE; + + if (event != NETEVENT_NEIGH_UPDATE) + return NOTIFY_DONE; + + switch (n->tbl->family) { + case AF_INET: + return sw_nb_net_v4_neigh_update(nb, event, ptr); +#if IS_ENABLED(CONFIG_IPV6) + case AF_INET6: + return sw_nb_net_v6_neigh_update(nb, event, ptr); +#endif + default: + break; + } + return NOTIFY_DONE; +} + +static struct notifier_block sw_nb_netevent = { + .notifier_call = sw_nb_net_event, + +}; + +int sw_nb_inetaddr_event_to_otx2_event(int event, struct net_device *netdev) +{ + switch (event) { + case NETDEV_CHANGE: + return OTX2_DEV_CHANGE; + case NETDEV_UP: + return OTX2_DEV_UP; + case NETDEV_DOWN: + return OTX2_DEV_DOWN; + default: + break; + } + netdev_dbg(netdev, "%s: Wrong interaddr event %d\n", + __func__, event); + return -1; +} + +static struct notifier_block sw_nb_v4_inetaddr = { + .notifier_call = sw_nb_v4_inetaddr_event, +}; + +#if IS_ENABLED(CONFIG_IPV6) +static struct notifier_block sw_nb_v6_inetaddr = { + .notifier_call = sw_nb_v6_inetaddr_event, +}; +#endif + +static int sw_nb_netdev_event(struct notifier_block *unused, + unsigned long event, void *ptr) +{ + struct net_device *dev = netdev_notifier_info_to_dev(ptr); + struct in_device *idev; + struct inet6_dev *i6dev; + + if (event != NETDEV_CHANGE && + event != NETDEV_UP && + event != NETDEV_DOWN) { + return NOTIFY_DONE; + } + + if (!sw_nb_is_valid_dev(dev)) + return NOTIFY_DONE; + + idev = __in_dev_get_rtnl(dev); + if (idev) + sw_nb_v4_netdev_event(unused, event, ptr); + +#if IS_ENABLED(CONFIG_IPV6) + i6dev = __in6_dev_get(dev); + if (i6dev) + sw_nb_v6_netdev_event(unused, event, ptr); +#endif + + return NOTIFY_DONE; +} + +static struct notifier_block sw_nb_netdev = { + .notifier_call = sw_nb_netdev_event, +}; + +int sw_nb_unregister(struct net_device *netdev) +{ + int err; + + err = unregister_switchdev_notifier(&sw_nb_fdb); + + if (err) + netdev_err(netdev, "Failed to unregister switchdev nb\n"); + + err = unregister_fib_notifier(&init_net, &sw_nb_fib); + if (err) + netdev_err(netdev, "Failed to unregister fib nb\n"); + + err = unregister_netevent_notifier(&sw_nb_netevent); + if (err) + netdev_err(netdev, "Failed to unregister netevent\n"); + + err = unregister_inetaddr_notifier(&sw_nb_v4_inetaddr); + if (err) + netdev_err(netdev, "Failed to unregister addr event\n"); + +#if IS_ENABLED(CONFIG_IPV6) + err = unregister_inet6addr_notifier(&sw_nb_v6_inetaddr); + if (err) + netdev_err(netdev, "Failed to unregister addr event\n"); +#endif + + err = unregister_netdevice_notifier(&sw_nb_netdev); + if (err) + netdev_err(netdev, "Failed to unregister netdev notifier\n"); + + sw_fl_deinit(); + sw_fib_deinit(); + sw_fdb_deinit(); + + sw_nb_pf_netdev = NULL; + + return 0; +} +EXPORT_SYMBOL(sw_nb_unregister); + +int sw_nb_register(struct net_device *netdev) +{ + int err; + + sw_nb_pf_netdev = netdev; + + sw_fdb_init(); + sw_fib_init(); + sw_fl_init(); + + err = register_switchdev_notifier(&sw_nb_fdb); + if (err) { + netdev_err(netdev, "Failed to register switchdev nb\n"); + sw_nb_pf_netdev = NULL; + return err; + } + + err = register_fib_notifier(&init_net, &sw_nb_fib, NULL, NULL); + if (err) { + netdev_err(netdev, "Failed to register fb notifier block\n"); + goto err1; + } + + err = register_netevent_notifier(&sw_nb_netevent); + if (err) { + netdev_err(netdev, "Failed to register netevent\n"); + goto err2; + } + +#if IS_ENABLED(CONFIG_IPV6) + err = register_inet6addr_notifier(&sw_nb_v6_inetaddr); + if (err) { + netdev_err(netdev, "Failed to register addr event\n"); + goto err3; + } +#endif + + err = register_inetaddr_notifier(&sw_nb_v4_inetaddr); + if (err) { + netdev_err(netdev, "Failed to register addr event\n"); + goto err4; + } + + err = register_netdevice_notifier(&sw_nb_netdev); + if (err) { + netdev_err(netdev, "Failed to register netdevice nb\n"); + goto err5; + } + return 0; + +err5: + unregister_inetaddr_notifier(&sw_nb_v4_inetaddr); + +err4: +#if IS_ENABLED(CONFIG_IPV6) + unregister_inet6addr_notifier(&sw_nb_v6_inetaddr); + +err3: +#endif + unregister_netevent_notifier(&sw_nb_netevent); + +err2: + unregister_fib_notifier(&init_net, &sw_nb_fib); + +err1: + unregister_switchdev_notifier(&sw_nb_fdb); + sw_nb_pf_netdev = NULL; + return err; } +EXPORT_SYMBOL(sw_nb_register);
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.h b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.h
index 5f744cc3ecbb..b0ce10ed25d4 100644
--- a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.h
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb.h@@ -7,7 +7,29 @@ #ifndef SW_NB_H_ #define SW_NB_H_ -int sw_nb_register(void); -int sw_nb_unregister(void); +enum { + OTX2_DEV_UP = 1, + OTX2_DEV_DOWN, + OTX2_DEV_CHANGE, + OTX2_NEIGH_UPDATE, + OTX2_FIB_ENTRY_REPLACE, + OTX2_FIB_ENTRY_ADD, + OTX2_FIB_ENTRY_DEL, + OTX2_FIB_ENTRY_APPEND, + OTX2_CMD_MAX, +}; -#endif // SW_NB_H_ +int sw_nb_register(struct net_device *netdev); +int sw_nb_unregister(struct net_device *netdev); +bool sw_nb_is_valid_dev(struct net_device *netdev); + +int otx2_mbox_up_handler_af2pf_fdb_refresh(struct otx2_nic *pf, + struct af2pf_fdb_refresh_req *req, + struct msg_rsp *rsp); + +bool sw_nb_is_cavium_dev(struct net_device *netdev); +int sw_nb_fib_event_to_otx2_event(int event, struct net_device *netdev); +int sw_nb_inetaddr_event_to_otx2_event(int event, struct net_device *netdev); + +const char *sw_nb_get_cmd2str(int cmd); +#endif // SW_NB_H__
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.c b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.c
new file mode 100644
index 000000000000..14db824ddc06
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.c@@ -0,0 +1,323 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Marvell RVU switch driver + * + * Copyright (C) 2026 Marvell. + * + */ +#include <linux/kernel.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <net/switchdev.h> +#include <net/netevent.h> +#include <net/arp.h> +#include <net/route.h> +#include <linux/inetdevice.h> + +#include "../otx2_reg.h" +#include "../otx2_common.h" +#include "../otx2_struct.h" +#include "../cn10k.h" +#include "sw_nb.h" +#include "sw_fdb.h" +#include "sw_fib.h" +#include "sw_fl.h" +#include "sw_nb.h" +#include "sw_nb_v4.h" + +int sw_nb_v4_netdev_event(struct notifier_block *unused, + unsigned long event, void *ptr) +{ + struct net_device *dev = netdev_notifier_info_to_dev(ptr); + struct netdev_hw_addr *dev_addr; + struct net_device *pf_dev; + struct in_ifaddr *ifa; + struct fib_entry *entry; + struct in_device *idev; + struct otx2_nic *pf; + struct list_head *iter; + struct net_device *lower; + + idev = __in_dev_get_rtnl(dev); + if (!idev || !idev->ifa_list) + return NOTIFY_DONE; + + ifa = rtnl_dereference(idev->ifa_list); + + entry = kcalloc(1, sizeof(*entry), GFP_KERNEL); + entry->cmd = sw_nb_inetaddr_event_to_otx2_event(event, dev); + entry->dst = (__force u32)htonl((__force u32)ifa->ifa_address); + entry->dst_len = 32; + entry->mac_valid = 1; + entry->host = 1; + + pf_dev = dev; + if (netif_is_bridge_master(dev)) { + entry->bridge = 1; + netdev_for_each_lower_dev(dev, lower, iter) { + pf_dev = lower; + break; + } + } else if (is_vlan_dev(dev)) { + entry->vlan_valid = 1; + pf_dev = vlan_dev_real_dev(dev); + entry->vlan_tag = vlan_dev_vlan_id(dev); + } + + pf = netdev_priv(pf_dev); + entry->port_id = pf->pcifunc; + + for_each_dev_addr(dev, dev_addr) { + ether_addr_copy(entry->mac, dev_addr->addr); + break; + } + + netdev_dbg(dev, "%s: pushing netdev event from HOST interface address %#x, %pM, dev=%s\n", + __func__, entry->dst, entry->mac, dev->name); + kfree(entry); + + return NOTIFY_DONE; +} + +int sw_nb_v4_inetaddr_event(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct in_ifaddr *ifa = (struct in_ifaddr *)ptr; + struct net_device *dev = ifa->ifa_dev->dev; + struct net_device *lower, *pf_dev; + struct netdev_hw_addr *dev_addr; + struct fib_entry *entry; + struct in_device *idev; + struct list_head *iter; + struct otx2_nic *pf; + + if (event != NETDEV_CHANGE && + event != NETDEV_UP && + event != NETDEV_DOWN) { + return NOTIFY_DONE; + } + + idev = __in_dev_get_rtnl(dev); + if (!idev || !idev->ifa_list) + return NOTIFY_DONE; + + entry = kcalloc(1, sizeof(*entry), GFP_ATOMIC); + entry->cmd = sw_nb_inetaddr_event_to_otx2_event(event, dev); + entry->dst = (__force u32)htonl((__force u32)ifa->ifa_address); + entry->dst_len = 32; + entry->mac_valid = 1; + entry->host = 1; + + pf_dev = dev; + if (netif_is_bridge_master(dev)) { + entry->bridge = 1; + netdev_for_each_lower_dev(dev, lower, iter) { + pf_dev = lower; + break; + } + } else if (is_vlan_dev(dev)) { + entry->vlan_valid = 1; + pf_dev = vlan_dev_real_dev(dev); + entry->vlan_tag = vlan_dev_vlan_id(dev); + } + + pf = netdev_priv(pf_dev); + entry->port_id = pf->pcifunc; + + for_each_dev_addr(dev, dev_addr) { + ether_addr_copy(entry->mac, dev_addr->addr); + break; + } + + netdev_dbg(dev, "%s: pushing inetaddr event from HOST interface address %#x, %pM, %s\n", + __func__, entry->dst, entry->mac, dev->name); + + kfree(entry); + return NOTIFY_DONE; +} + +int sw_nb_v4_fib_event(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct fib_entry_notifier_info *fen_info = ptr; + struct fib_entry *entries, *iter; + struct net_device *dev, *pf_dev = NULL; + struct netdev_hw_addr *dev_addr; + struct net_device *lower; + struct list_head *lh; + struct neighbour *neigh; + struct fib_nh *fib_nh; + struct fib_info *fi; + struct otx2_nic *pf; + u32 *haddr; + int hcnt = 0; + int cnt, i; + + /* Process only UNICAST routes add or del */ + if (fen_info->type != RTN_UNICAST) + return NOTIFY_DONE; + + fi = fen_info->fi; + if (!fi) + return NOTIFY_DONE; + + if (fi->fib_nh_is_v6) { + struct net_device *log_dev = (fi->fib_nhs > 0) ? + fi->fib_nh->fib_nh_dev : NULL; + + if (log_dev) + netdev_dbg(log_dev, "%s: Received v6 notification\n", + __func__); + return NOTIFY_DONE; + } + + entries = kcalloc(fi->fib_nhs, sizeof(*entries), GFP_ATOMIC); + if (!entries) + return NOTIFY_DONE; + + haddr = kcalloc(fi->fib_nhs, sizeof(u32), GFP_ATOMIC); + + iter = entries; + fib_nh = fi->fib_nh; + for (i = 0; i < fi->fib_nhs; i++, fib_nh++) { + dev = fib_nh->fib_nh_dev; + + if (!dev) + continue; + + if (dev->type != ARPHRD_ETHER) + continue; + + if (!sw_nb_is_valid_dev(dev)) + continue; + + iter->cmd = sw_nb_fib_event_to_otx2_event(event, dev); + iter->dst = fen_info->dst; + iter->dst_len = fen_info->dst_len; + iter->gw = (__force u32)htonl((__force u32)fib_nh->fib_nh_gw4); + + netdev_dbg(dev, "%s: FIB route Rule cmd=%lld dst=%#x dst_len=%d gw=%#x\n", + __func__, iter->cmd, iter->dst, iter->dst_len, iter->gw); + + pf_dev = dev; + if (netif_is_bridge_master(dev)) { + iter->bridge = 1; + netdev_for_each_lower_dev(dev, lower, lh) { + pf_dev = lower; + break; + } + } else if (is_vlan_dev(dev)) { + iter->vlan_valid = 1; + pf_dev = vlan_dev_real_dev(dev); + iter->vlan_tag = vlan_dev_vlan_id(dev); + } + + pf = netdev_priv(pf_dev); + iter->port_id = pf->pcifunc; + + if (!fib_nh->fib_nh_gw4) { + if (iter->dst || iter->dst_len) + iter++; + + continue; + } + iter->gw_valid = 1; + + if (fib_nh->nh_saddr) + haddr[hcnt++] = (__force u32)fib_nh->nh_saddr; + + rcu_read_lock(); + neigh = ip_neigh_gw4(fib_nh->fib_nh_dev, fib_nh->fib_nh_gw4); + if (!neigh) { + rcu_read_unlock(); + iter++; + continue; + } + + if (is_valid_ether_addr(neigh->ha)) { + iter->mac_valid = 1; + ether_addr_copy(iter->mac, neigh->ha); + } + + iter++; + rcu_read_unlock(); + } + + cnt = iter - entries; + if (!cnt) + return NOTIFY_DONE; + + netdev_dbg(pf_dev, "pf_dev is %s cnt=%d\n", pf_dev->name, cnt); + kfree(entries); + + if (!hcnt) + return NOTIFY_DONE; + + entries = kcalloc(hcnt, sizeof(*entries), GFP_ATOMIC); + if (!entries) + return NOTIFY_DONE; + + iter = entries; + + for (i = 0; i < hcnt; i++, iter++) { + iter->cmd = sw_nb_fib_event_to_otx2_event(event, pf_dev); + iter->dst = (__force u32)htonl(haddr[i]); + iter->dst_len = 32; + iter->mac_valid = 1; + iter->host = 1; + iter->port_id = pf->pcifunc; + + for_each_dev_addr(pf_dev, dev_addr) { + ether_addr_copy(iter->mac, dev_addr->addr); + break; + } + + netdev_dbg(pf_dev, "%s: FIB host Rule cmd=%lld dst=%#x dst_len=%d gw=%#x %s\n", + __func__, iter->cmd, iter->dst, iter->dst_len, iter->gw, pf_dev->name); + } + kfree(entries); + kfree(haddr); + return NOTIFY_DONE; +} + +int sw_nb_net_v4_neigh_update(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct net_device *lower, *pf_dev; + struct neighbour *n = ptr; + struct fib_entry *entry; + struct list_head *iter; + struct otx2_nic *pf; + + if (n->tbl != &arp_tbl) + return NOTIFY_DONE; + + entry = kcalloc(1, sizeof(*entry), GFP_ATOMIC); + entry->cmd = OTX2_NEIGH_UPDATE; + entry->dst = (__force u32)htonl(*(u32 *)n->primary_key); + entry->dst_len = n->tbl->key_len * 8; + entry->mac_valid = 1; + entry->nud_state = n->nud_state; + ether_addr_copy(entry->mac, n->ha); + + pf_dev = n->dev; + if (netif_is_bridge_master(n->dev)) { + entry->bridge = 1; + netdev_for_each_lower_dev(n->dev, lower, iter) { + pf_dev = lower; + goto err; + } + } else if (is_vlan_dev(n->dev)) { + entry->vlan_valid = 1; + pf_dev = vlan_dev_real_dev(n->dev); + entry->vlan_tag = vlan_dev_vlan_id(n->dev); + } + + pf = netdev_priv(pf_dev); + entry->port_id = pf->pcifunc; + + kfree(entry); + return NOTIFY_DONE; +err: + kfree(entry); + return NOTIFY_DONE; +}
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.h b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.h
new file mode 100644
index 000000000000..c6dbf4b93a9a
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v4.h@@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Marvell switch driver + * + * Copyright (C) 2026 Marvell. + * + */ +#ifndef SW_NB_V4_H_ +#define SW_NB_V4_H_ + +int sw_nb_v4_fib_event(struct notifier_block *nb, + unsigned long event, void *ptr); + +int sw_nb_net_v4_neigh_update(struct notifier_block *nb, + unsigned long event, void *ptr); + +int sw_nb_v4_inetaddr_event(struct notifier_block *nb, + unsigned long event, void *ptr); + +int sw_nb_v4_netdev_event(struct notifier_block *unused, + unsigned long event, void *ptr); +#endif // SW_NB_V4_H__
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v6.c b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v6.c
new file mode 100644
index 000000000000..e43c28d4f15c
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v6.c@@ -0,0 +1,237 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Marvell RVU switch driver + * + * Copyright (C) 2026 Marvell. + * + */ +#include <linux/kernel.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <net/switchdev.h> +#include <net/netevent.h> +#include <net/arp.h> +#include <net/route.h> +#include <linux/inetdevice.h> +#include <net/addrconf.h> +#include <net/ip6_fib.h> +#include <net/nexthop.h> + +#include "../otx2_reg.h" +#include "../otx2_common.h" +#include "../otx2_struct.h" +#include "../cn10k.h" +#include "sw_nb.h" +#include "sw_fdb.h" +#include "sw_fib.h" +#include "sw_fl.h" +#include "sw_nb.h" +#include "sw_nb_v6.h" + +#if IS_ENABLED(CONFIG_IPV6) + +int sw_nb_v6_netdev_event(struct notifier_block *unused, + unsigned long event, void *ptr) +{ + struct net_device *dev = netdev_notifier_info_to_dev(ptr); + struct netdev_hw_addr *dev_addr; + struct inet6_ifaddr *ifp; + struct fib_entry *entry; + struct inet6_dev *i6dev; + struct otx2_nic *pf; + + i6dev = __in6_dev_get(dev); + ifp = list_first_entry_or_null(&i6dev->addr_list, + struct inet6_ifaddr, if_list); + if (!ifp) + return NOTIFY_DONE; + + if (ipv6_addr_type(&ifp->addr) & IPV6_ADDR_LINKLOCAL) + return NOTIFY_DONE; + + pf = netdev_priv(dev); + + entry = kcalloc(1, sizeof(*entry), GFP_KERNEL); + entry->cmd = sw_nb_inetaddr_event_to_otx2_event(event, dev); + memcpy(entry->dst6, &ifp->addr, sizeof(entry->dst6)); + entry->dst6_plen = ifp->prefix_len; + entry->host = 1; + entry->ipv6 = 1; + entry->port_id = pf->pcifunc; + + for_each_dev_addr(dev, dev_addr) { + entry->mac_valid = 1; + ether_addr_copy(entry->mac, dev_addr->addr); + break; + } + + netdev_dbg(dev, "netdev event %pM plen=%u mac=%pM\n", + &ifp->addr, ifp->prefix_len, entry->mac); + kfree(entry); + return NOTIFY_DONE; +} + +int sw_nb_v6_fib_event(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct fib6_entry_notifier_info *f6_eni; + struct fib_notifier_info *info = ptr; + struct net_device *fib_dev; + struct fib_entry *entry; + struct fib6_info *f6i; + struct neighbour *neigh; + struct fib6_nh *nh6; + struct otx2_nic *pf; + struct rt6key *key; + + f6_eni = container_of(info, struct fib6_entry_notifier_info, info); + f6i = f6_eni->rt; + + fib_dev = fib6_info_nh_dev(f6i); + + if (!fib_dev) + return NOTIFY_DONE; + + if (fib_dev->type != ARPHRD_ETHER) + return NOTIFY_DONE; + + if (!sw_nb_is_cavium_dev(fib_dev)) + return NOTIFY_DONE; + + if (f6i->fib6_type != RTN_UNICAST) + return NOTIFY_DONE; + + key = &f6i->fib6_dst; + if (ipv6_addr_type(&key->addr) & IPV6_ADDR_LINKLOCAL) + return NOTIFY_DONE; + + netdev_dbg(fib_dev, "fib6dst rt6key.addr=%pI6c len=%u\n", &key->addr, + key->plen); + + netdev_dbg(fib_dev, "fib6flags=%#x proto=%u type=%u\n", + f6i->fib6_flags, f6i->fib6_protocol, f6i->fib6_type); + + nh6 = f6i->nh ? nexthop_fib6_nh(f6i->nh) : f6i->fib6_nh; + netdev_dbg(nh6->fib_nh_dev ? nh6->fib_nh_dev : fib_dev, + "nh family=%u dev=%s gw=%pI6c gwfamily=%u\n", + nh6->fib_nh_family, + nh6->fib_nh_dev ? nh6->fib_nh_dev->name : "No dev", + &nh6->fib_nh_gw6, nh6->fib_nh_gw_family); + + pf = netdev_priv(fib_dev); + + entry = kcalloc(1, sizeof(*entry), GFP_ATOMIC); + if (!entry) + return NOTIFY_DONE; + + entry->cmd = sw_nb_fib_event_to_otx2_event(event, fib_dev); + entry->ipv6 = 1; + entry->port_id = pf->pcifunc; + memcpy(entry->dst6, &key->addr, sizeof(entry->dst6)); + entry->dst6_plen = key->plen; + + memcpy(entry->gw6, &nh6->fib_nh_gw6, sizeof(nh6->fib_nh_gw6)); + entry->gw_valid = !!(ipv6_addr_type(&nh6->fib_nh_gw6) & IPV6_ADDR_UNICAST); + + rcu_read_lock(); + neigh = ip_neigh_gw6(fib_dev, &nh6->fib_nh_gw6); + if (!neigh) { + rcu_read_unlock(); + return NOTIFY_DONE; + } + + if (is_valid_ether_addr(neigh->ha)) { + entry->mac_valid = 1; + ether_addr_copy(entry->mac, neigh->ha); + netdev_dbg(fib_dev, "fib found MAC=%pM\n", entry->mac); + } + + rcu_read_unlock(); + kfree(entry); + + return NOTIFY_DONE; +} + +int sw_nb_net_v6_neigh_update(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct neighbour *n = ptr; + struct fib_entry *entry; + struct net_device *pf_dev; + struct otx2_nic *pf; + + if (n->tbl != &nd_tbl) + return NOTIFY_DONE; + + if (ipv6_addr_type((struct in6_addr *)n->primary_key) & IPV6_ADDR_LINKLOCAL) + return NOTIFY_DONE; + + pf_dev = n->dev; + pf = netdev_priv(pf_dev); + + entry = kcalloc(1, sizeof(*entry), GFP_ATOMIC); + entry->cmd = OTX2_NEIGH_UPDATE; + + entry->dst6_plen = n->tbl->key_len * 8; + memcpy(entry->dst6, (struct in6_addr *)n->primary_key, + sizeof(entry->dst6)); + entry->ipv6 = 1; + entry->nud_state = n->nud_state; + ether_addr_copy(entry->mac, n->ha); + entry->mac_valid = 1; + entry->port_id = pf->pcifunc; + + netdev_dbg(n->dev, "v6 neigh update %pI6 mac=%pM plen=%u\n", + n->primary_key, n->ha, n->tbl->key_len * 8); + kfree(entry); + + return NOTIFY_DONE; +} + +int sw_nb_v6_inetaddr_event(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct inet6_ifaddr *ifa6 = (struct inet6_ifaddr *)ptr; + struct net_device *dev = ifa6->idev->dev; + struct netdev_hw_addr *dev_addr; + struct fib_entry *entry; + struct otx2_nic *pf; + + if (event != NETDEV_CHANGE && + event != NETDEV_UP && + event != NETDEV_DOWN) { + return NOTIFY_DONE; + } + + if (dev->type != ARPHRD_ETHER) + return NOTIFY_DONE; + + if (!sw_nb_is_cavium_dev(dev)) + return NOTIFY_DONE; + + if (ipv6_addr_type(&ifa6->addr) & IPV6_ADDR_LINKLOCAL) + return NOTIFY_DONE; + + pf = netdev_priv(dev); + + entry = kcalloc(1, sizeof(*entry), GFP_ATOMIC); + entry->cmd = sw_nb_inetaddr_event_to_otx2_event(event, dev); + memcpy(entry->dst6, &ifa6->addr, sizeof(entry->dst6)); + entry->dst6_plen = ifa6->prefix_len; + entry->mac_valid = 1; + entry->host = 1; + entry->ipv6 = 1; + entry->port_id = pf->pcifunc; + + for_each_dev_addr(dev, dev_addr) { + ether_addr_copy(entry->mac, dev_addr->addr); + entry->mac_valid = 1; + break; + } + + netdev_dbg(dev, "inetaddr addr=%pI6c len=%u %pM\n", + &ifa6->addr, ifa6->prefix_len, entry->mac); + kfree(entry); + + return NOTIFY_DONE; +} +#endif
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v6.h b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v6.h
new file mode 100644
index 000000000000..f73efc98c311
--- /dev/null
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/switch/sw_nb_v6.h@@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Marvell switch driver + * + * Copyright (C) 2026 Marvell. + * + */ +#ifndef SW_NB_V6_H_ +#define SW_NB_V6_H_ + +int sw_nb_v6_fib_event(struct notifier_block *nb, + unsigned long event, void *ptr); + +int sw_nb_net_v6_neigh_update(struct notifier_block *nb, + unsigned long event, void *ptr); + +int sw_nb_v6_inetaddr_event(struct notifier_block *nb, + unsigned long event, void *ptr); + +int sw_nb_v6_netdev_event(struct notifier_block *unused, + unsigned long event, void *ptr); +#endif // SW_NB_V6_H__
--
2.43.0