This patch adds support for offloading FDB entries added on upper bond devices. First of all, the call to switchdev_bridge_port_offload() is updated so that the notifier blocks needed for FDB events replay are available to the bridge core. Using switchdev_handle_*() helpers is also necessary because each FDB event needs to be fanned out to any DPAA2 switch lower device. This triggers another change in the return type used by the dpaa2_switch_port_fdb_event() - from notifier types to regular errno types. Handling of the SWITCHDEV_FDB_ADD_TO_DEVICE/SWITCHDEV_FDB_DEL_TO_DEVICE events is updated so that the newly dpaa2_switch_lag_fdb_add() / dpaa2_switch_lag_fdb_del() functions are called anytime a port is under a bond device. This will allow us to manage refcounting on FDB entries which are added on the upper bond devices. The DPAA2 switch uses shared-VLAN learning which means that the vid parameter is not used when adding an FDB entry to HW. The current behavior when dealing with FDB entries with the same MAC address but different VLANs is to add the entry to HW every time while removal will get done on the first 'bridge fdb del' command issued by the user. The same behavior is kept also for FDBs added on bond devices by keeping the refcount on the {vid, addr} pair while the HW operation disregards entirely the vid parameter. Signed-off-by: Ioana Ciornei --- Changes in v2: - Update dpaa2_switch_foreign_dev_check() so that we check if between the switch port and the foreign net_device is an offloaded path. Before this change we also checked if the foreign_dev was offloaded or not by the switch port. - Update the switchdev_bridge_port_unoffload() by passing it the proper context and the notifier blocks. - Add dev_hold() and dev_put() calls for orig_dev --- .../ethernet/freescale/dpaa2/dpaa2-switch.c | 206 ++++++++++++++++-- .../ethernet/freescale/dpaa2/dpaa2-switch.h | 22 ++ 2 files changed, 208 insertions(+), 20 deletions(-) diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index f8aa65463386..46c148a8aec3 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c @@ -25,6 +25,9 @@ #define DEFAULT_VLAN_ID 1 +static struct notifier_block dpaa2_switch_port_switchdev_nb; +static struct notifier_block dpaa2_switch_port_switchdev_blocking_nb; + static u16 dpaa2_switch_port_get_fdb_id(struct ethsw_port_priv *port_priv) { return port_priv->fdb->fdb_id; @@ -62,6 +65,25 @@ dpaa2_switch_lag_get_unused(struct ethsw_core *ethsw) return NULL; } +static struct ethsw_port_priv * +dpaa2_switch_lag_get_primary(struct dpaa2_switch_lag *lag) +{ + struct ethsw_core *ethsw = lag->ethsw; + struct ethsw_port_priv *port_priv; + int i; + + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { + port_priv = ethsw->ports[i]; + if (!port_priv) + continue; + + if (port_priv->lag == lag) + return port_priv; + } + + return NULL; +} + static void dpaa2_switch_port_set_fdb(struct ethsw_port_priv *port_priv, struct net_device *upper_dev, bool linking) @@ -573,6 +595,91 @@ static int dpaa2_switch_port_fdb_del(struct ethsw_port_priv *port_priv, return dpaa2_switch_port_fdb_del_mc(port_priv, addr); } +static struct dpaa2_mac_addr * +dpaa2_switch_mac_addr_find(struct list_head *addr_list, + const unsigned char *addr, u16 vid) +{ + struct dpaa2_mac_addr *a; + + list_for_each_entry(a, addr_list, list) + if (ether_addr_equal(a->addr, addr) && a->vid == vid) + return a; + + return NULL; +} + +static int dpaa2_switch_lag_fdb_add(struct dpaa2_switch_lag *lag, + const unsigned char *addr, u16 vid) +{ + struct ethsw_port_priv *port_priv; + struct dpaa2_mac_addr *a; + int err = 0; + + mutex_lock(&lag->fdb_lock); + + a = dpaa2_switch_mac_addr_find(&lag->fdbs, addr, vid); + if (a) { + refcount_inc(&a->refcount); + goto out; + } + + port_priv = dpaa2_switch_lag_get_primary(lag); + if (!port_priv) { + err = -ENOENT; + goto out; + } + + a = kzalloc(sizeof(*a), GFP_KERNEL); + if (!a) { + err = -ENOMEM; + goto out; + } + + err = dpaa2_switch_port_fdb_add(port_priv, addr); + if (err) { + kfree(a); + goto out; + } + + ether_addr_copy(a->addr, addr); + a->vid = vid; + refcount_set(&a->refcount, 1); + list_add_tail(&a->list, &lag->fdbs); + +out: + mutex_unlock(&lag->fdb_lock); + + return err; +} + +static void dpaa2_switch_lag_fdb_del(struct dpaa2_switch_lag *lag, + const unsigned char *addr, u16 vid) +{ + struct ethsw_port_priv *port_priv; + struct dpaa2_mac_addr *a; + + mutex_lock(&lag->fdb_lock); + + a = dpaa2_switch_mac_addr_find(&lag->fdbs, addr, vid); + if (!a) + goto out; + + port_priv = dpaa2_switch_lag_get_primary(lag); + if (!port_priv) + goto out; + + if (!refcount_dec_and_test(&a->refcount)) + goto out; + + dpaa2_switch_port_fdb_del(port_priv, addr); + + list_del(&a->list); + kfree(a); + +out: + mutex_unlock(&lag->fdb_lock); +} + static void dpaa2_switch_port_get_stats(struct net_device *netdev, struct rtnl_link_stats64 *stats) { @@ -1520,6 +1627,32 @@ bool dpaa2_switch_port_dev_check(const struct net_device *netdev) return netdev->netdev_ops == &dpaa2_switch_port_ops; } +static bool dpaa2_switch_foreign_dev_check(const struct net_device *dev, + const struct net_device *foreign_dev) +{ + struct ethsw_port_priv *port_priv = netdev_priv(dev); + struct ethsw_core *ethsw = port_priv->ethsw_data; + struct ethsw_port_priv *other_port; + int i; + + if (netif_is_bridge_master(foreign_dev)) + if (port_priv->fdb->bridge_dev == foreign_dev) + return false; + + if (netif_is_bridge_port(foreign_dev)) { + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { + other_port = ethsw->ports[i]; + + if (!other_port || !other_port->lag) + continue; + if (other_port->lag->bond_dev == foreign_dev) + return false; + } + } + + return true; +} + static int dpaa2_switch_port_connect_mac(struct ethsw_port_priv *port_priv) { struct fsl_mc_device *dpsw_port_dev, *dpmac_dev; @@ -2145,8 +2278,10 @@ static int dpaa2_switch_port_bridge_join(struct net_device *netdev, goto err_egress_flood; brport_dev = dpaa2_switch_port_to_bridge_port(port_priv); - err = switchdev_bridge_port_offload(brport_dev, netdev, NULL, - NULL, NULL, false, extack); + err = switchdev_bridge_port_offload(brport_dev, netdev, port_priv, + &dpaa2_switch_port_switchdev_nb, + &dpaa2_switch_port_switchdev_blocking_nb, + false, extack); if (err) goto err_switchdev_offload; @@ -2185,7 +2320,9 @@ static void dpaa2_switch_port_pre_bridge_leave(struct net_device *netdev) brport_dev = dpaa2_switch_port_to_bridge_port(port_priv); - switchdev_bridge_port_unoffload(brport_dev, NULL, NULL, NULL); + switchdev_bridge_port_unoffload(brport_dev, port_priv, + &dpaa2_switch_port_switchdev_nb, + &dpaa2_switch_port_switchdev_blocking_nb); } static int dpaa2_switch_port_bridge_leave(struct net_device *netdev) @@ -2785,14 +2922,18 @@ struct ethsw_switchdev_event_work { struct work_struct work; struct switchdev_notifier_fdb_info fdb_info; struct net_device *dev; + struct net_device *orig_dev; unsigned long event; + u16 vid; }; static void dpaa2_switch_event_work(struct work_struct *work) { struct ethsw_switchdev_event_work *switchdev_work = container_of(work, struct ethsw_switchdev_event_work, work); + struct net_device *orig_dev = switchdev_work->orig_dev; struct net_device *dev = switchdev_work->dev; + struct ethsw_port_priv *port_priv = netdev_priv(dev); struct switchdev_notifier_fdb_info *fdb_info; int err; @@ -2801,16 +2942,25 @@ static void dpaa2_switch_event_work(struct work_struct *work) switch (switchdev_work->event) { case SWITCHDEV_FDB_ADD_TO_DEVICE: - err = dpaa2_switch_port_fdb_add(netdev_priv(dev), - fdb_info->addr); + if (port_priv->lag) + err = dpaa2_switch_lag_fdb_add(port_priv->lag, + fdb_info->addr, + switchdev_work->vid); + else + err = dpaa2_switch_port_fdb_add(netdev_priv(dev), + fdb_info->addr); if (err) break; fdb_info->offloaded = true; - call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED, dev, + call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED, orig_dev, &fdb_info->info, NULL); break; case SWITCHDEV_FDB_DEL_TO_DEVICE: - dpaa2_switch_port_fdb_del(netdev_priv(dev), fdb_info->addr); + if (port_priv->lag) + dpaa2_switch_lag_fdb_del(port_priv->lag, fdb_info->addr, + switchdev_work->vid); + else + dpaa2_switch_port_fdb_del(port_priv, fdb_info->addr); break; } @@ -2818,35 +2968,43 @@ static void dpaa2_switch_event_work(struct work_struct *work) kfree(switchdev_work->fdb_info.addr); kfree(switchdev_work); dev_put(dev); + dev_put(orig_dev); } -static int dpaa2_switch_port_fdb_event(struct notifier_block *nb, - unsigned long event, void *ptr) +static int +dpaa2_switch_port_fdb_event(struct net_device *dev, + struct net_device *orig_dev, + unsigned long event, const void *ctx, + const struct switchdev_notifier_fdb_info *fdb_info) { - struct net_device *dev = switchdev_notifier_info_to_dev(ptr); struct ethsw_port_priv *port_priv = netdev_priv(dev); struct ethsw_switchdev_event_work *switchdev_work; - struct switchdev_notifier_fdb_info *fdb_info = ptr; struct ethsw_core *ethsw = port_priv->ethsw_data; - if (!dpaa2_switch_port_dev_check(dev)) - return NOTIFY_DONE; + if (ctx && ctx != port_priv) + return 0; + + /* For the moment, do nothing with entries towards foreign devices */ + if (dpaa2_switch_foreign_dev_check(dev, orig_dev)) + return 0; if (!fdb_info->added_by_user || fdb_info->is_local) - return NOTIFY_DONE; + return 0; switchdev_work = kzalloc_obj(*switchdev_work, GFP_ATOMIC); if (!switchdev_work) - return NOTIFY_BAD; + return -ENOMEM; INIT_WORK(&switchdev_work->work, dpaa2_switch_event_work); switchdev_work->dev = dev; switchdev_work->event = event; + switchdev_work->orig_dev = orig_dev; + switchdev_work->vid = fdb_info->vid; switch (event) { case SWITCHDEV_FDB_ADD_TO_DEVICE: case SWITCHDEV_FDB_DEL_TO_DEVICE: - memcpy(&switchdev_work->fdb_info, ptr, + memcpy(&switchdev_work->fdb_info, fdb_info, sizeof(switchdev_work->fdb_info)); switchdev_work->fdb_info.addr = kzalloc(ETH_ALEN, GFP_ATOMIC); if (!switchdev_work->fdb_info.addr) @@ -2857,19 +3015,20 @@ static int dpaa2_switch_port_fdb_event(struct notifier_block *nb, /* Take a reference on the device to avoid being freed. */ dev_hold(dev); + dev_hold(orig_dev); break; default: kfree(switchdev_work); - return NOTIFY_DONE; + return 0; } queue_work(ethsw->workqueue, &switchdev_work->work); - return NOTIFY_DONE; + return 0; err_addr_alloc: kfree(switchdev_work); - return NOTIFY_BAD; + return -ENOMEM; } /* Called under rcu_read_lock() */ @@ -2877,13 +3036,18 @@ static int dpaa2_switch_port_event(struct notifier_block *nb, unsigned long event, void *ptr) { struct net_device *dev = switchdev_notifier_info_to_dev(ptr); + int err; switch (event) { case SWITCHDEV_PORT_ATTR_SET: return dpaa2_switch_port_attr_set_event(dev, ptr); case SWITCHDEV_FDB_ADD_TO_DEVICE: case SWITCHDEV_FDB_DEL_TO_DEVICE: - return dpaa2_switch_port_fdb_event(nb, event, ptr); + err = switchdev_handle_fdb_event_to_device(dev, event, ptr, + dpaa2_switch_port_dev_check, + dpaa2_switch_foreign_dev_check, + dpaa2_switch_port_fdb_event); + return notifier_from_errno(err); default: return NOTIFY_DONE; } @@ -3971,6 +4135,8 @@ static int dpaa2_switch_probe(struct fsl_mc_device *sw_dev) ethsw->lags[i].ethsw = ethsw; ethsw->lags[i].id = i + 1; ethsw->lags[i].in_use = 0; + mutex_init(ðsw->lags[i].fdb_lock); + INIT_LIST_HEAD(ðsw->lags[i].fdbs); } for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h index 56debbdefd13..96d780980d77 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h @@ -100,6 +100,13 @@ struct dpaa2_switch_fq { u32 fqid; }; +struct dpaa2_mac_addr { + unsigned char addr[ETH_ALEN]; + u16 vid; + refcount_t refcount; + struct list_head list; +}; + struct dpaa2_switch_fdb { struct net_device *bridge_dev; u16 fdb_id; @@ -111,6 +118,9 @@ struct dpaa2_switch_lag { struct net_device *bond_dev; bool in_use; u8 id; + /* Protects the list of fdbs installed on this LAG */ + struct mutex fdb_lock; + struct list_head fdbs; }; struct dpaa2_switch_acl_entry { @@ -286,4 +296,16 @@ int dpaa2_switch_block_offload_mirror(struct dpaa2_switch_filter_block *block, int dpaa2_switch_block_unoffload_mirror(struct dpaa2_switch_filter_block *block, struct ethsw_port_priv *port_priv); + +static inline bool +dpaa2_switch_port_offloads_bridge_port(struct ethsw_port_priv *port_priv, + const struct net_device *dev) +{ + if (port_priv->lag && port_priv->lag->bond_dev == dev) + return true; + if (port_priv->netdev == dev) + return true; + return false; +} + #endif /* __ETHSW_H */ -- 2.25.1