diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index 949a7241a00f..307b3b7a1bfb 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; @@ -585,6 +588,81 @@ 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 = lag->primary; + 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; + } + + 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 = lag->primary; + struct dpaa2_mac_addr *a; + + mutex_lock(&lag->fdb_lock); + + a = dpaa2_switch_mac_addr_find(&lag->fdbs, addr, vid); + if (!a) + goto out; + + if (!refcount_dec_and_test(&a->refcount)) + goto out; + + list_del(&a->list); + kfree(a); + + dpaa2_switch_port_fdb_del(port_priv, addr); + +out: + mutex_unlock(&lag->fdb_lock); +} + static void dpaa2_switch_port_get_stats(struct net_device *netdev, struct rtnl_link_stats64 *stats) { @@ -1533,6 +1611,33 @@ 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) + continue; + if (dpaa2_switch_port_offloads_bridge_port(other_port, + 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; @@ -2100,8 +2205,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; @@ -2143,7 +2250,9 @@ static void dpaa2_switch_port_pre_bridge_leave(struct net_device *netdev) if (!brport_dev) return; - 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); /* Make sure that any FDB add/del operations are completed before the * bridge layout changes @@ -2425,9 +2534,10 @@ static int dpaa2_switch_set_lag_cfg(struct net_device *bond_dev, u8 lag_id, struct ethsw_core *ethsw) { struct dpaa2_switch_lag *lag = ðsw->lags[lag_id - 1]; - struct ethsw_port_priv *primary, *new_primary = NULL; - struct ethsw_port_priv *port_priv = NULL; + struct ethsw_port_priv *primary, *port_priv; + struct ethsw_port_priv *new_primary = NULL; struct dpsw_lag_cfg cfg = {0}; + struct dpaa2_mac_addr *a; u8 num_ifs = 0; int err, i; @@ -2454,7 +2564,6 @@ static int dpaa2_switch_set_lag_cfg(struct net_device *bond_dev, u8 lag_id, /* Build the interface list, always placing the primary first */ if (primary) cfg.if_id[num_ifs++] = primary->idx; - for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { port_priv = ethsw->ports[i]; if (port_priv == primary) @@ -2477,11 +2586,32 @@ static int dpaa2_switch_set_lag_cfg(struct net_device *bond_dev, u8 lag_id, lag->in_use = false; } + /* When the primary changes, migrate the FDB entries from the old + * primary to the new one: remove them before reconfiguring the LAG in + * hardware and re-add them on the new primary afterwards. We do not + * touch any refcounting since the intention is to change the HW entry, + * not the parallel software tracking. + */ + if (new_primary) { + mutex_lock(&lag->fdb_lock); + list_for_each_entry(a, &lag->fdbs, list) + dpaa2_switch_port_fdb_del(lag->primary, a->addr); + mutex_unlock(&lag->fdb_lock); + } + err = dpsw_lag_set(ethsw->mc_io, 0, ethsw->dpsw_handle, &cfg); if (err) return err; if (new_primary) { + mutex_lock(&lag->fdb_lock); + list_for_each_entry(a, &lag->fdbs, list) { + err = dpaa2_switch_port_fdb_add(new_primary, a->addr); + if (err) + netdev_err(new_primary->netdev, "Unable to migrate FDB\n"); + } + mutex_unlock(&lag->fdb_lock); + synchronize_net(); lag->primary = new_primary; } @@ -2763,67 +2893,97 @@ 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; + struct dpaa2_switch_lag *lag; int err; fdb_info = &switchdev_work->fdb_info; + /* The lag structures are freed only from dpaa2_switch_remove(), which + * first flushes this workqueue, so the pointer stays valid for the + * lifetime of the work item. Only the dereference needs the RCU + * read-side lock; the FDB helpers below can sleep and must run outside + * of it. + */ + rcu_read_lock(); + lag = rcu_dereference(port_priv->lag); + rcu_read_unlock(); + switch (switchdev_work->event) { case SWITCHDEV_FDB_ADD_TO_DEVICE: - err = dpaa2_switch_port_fdb_add(netdev_priv(dev), - fdb_info->addr); + if (lag) + err = dpaa2_switch_lag_fdb_add(lag, fdb_info->addr, + switchdev_work->vid); + else + err = dpaa2_switch_port_fdb_add(port_priv, + 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 (lag) + dpaa2_switch_lag_fdb_del(lag, fdb_info->addr, + switchdev_work->vid); + else + dpaa2_switch_port_fdb_del(port_priv, fdb_info->addr); break; } 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; + struct ethsw_core *ethsw = port_priv->ethsw_data; - if (!dpaa2_switch_port_dev_check(dev)) - return NOTIFY_DONE; - ethsw = port_priv->ethsw_data; + 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) @@ -2834,19 +2994,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() */ @@ -2854,13 +3015,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; } @@ -3785,6 +3951,9 @@ static void dpaa2_switch_remove(struct fsl_mc_device *sw_dev) dev = &sw_dev->dev; ethsw = dev_get_drvdata(dev); + /* Make sure that all events were handled before we kfree anything */ + flush_workqueue(ethsw->workqueue); + dpaa2_switch_teardown_irqs(sw_dev); dpsw_disable(ethsw->mc_io, 0, ethsw->dpsw_handle); @@ -3798,8 +3967,10 @@ static void dpaa2_switch_remove(struct fsl_mc_device *sw_dev) for (i = 0; i < DPAA2_SWITCH_RX_NUM_FQS; i++) netif_napi_del(ðsw->fq[i].napi); - for (i = 0; i < ethsw->sw_attr.num_ifs; i++) + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) { dpaa2_switch_remove_port(ethsw, i); + mutex_destroy(ðsw->lags[i].fdb_lock); + } kfree(ethsw->fdbs); kfree(ethsw->filter_blocks); @@ -3951,6 +4122,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++) { @@ -3999,6 +4172,8 @@ static int dpaa2_switch_probe(struct fsl_mc_device *sw_dev) err_free_netdev: for (i--; i >= 0; i--) dpaa2_switch_remove_port(ethsw, i); + for (i = 0; i < ethsw->sw_attr.num_ifs; i++) + mutex_destroy(ðsw->lags[i].fdb_lock); kfree(ethsw->lags); err_free_filter: kfree(ethsw->filter_blocks); diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h index c98bddd7e359..e8bc1469cbf7 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; @@ -112,6 +119,9 @@ struct dpaa2_switch_lag { bool in_use; u8 id; struct ethsw_port_priv *primary; + /* Protects the list of fdbs installed on this LAG */ + struct mutex fdb_lock; + struct list_head fdbs; }; struct dpaa2_switch_acl_entry { @@ -287,4 +297,18 @@ 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) +{ + struct dpaa2_switch_lag *lag = rcu_dereference_rtnl(port_priv->lag); + + if (lag && lag->bond_dev == dev) + return true; + if (port_priv->netdev == dev) + return true; + return false; +} + #endif /* __ETHSW_H */