From af061378924f6d2f368b3769fd59fd95875dc942 Mon Sep 17 00:00:00 2001 From: Arkadi Sharshevsky Date: Thu, 8 Jun 2017 08:44:20 +0200 Subject: [PATCH] mlxsw: spectrum_switchdev: Add support for learning FDB through notification Add support for learning FDB through notification. The driver defers the hardware update via ordered work queue. Support for stacked devices is also provided. In case of a successful FDB add a notification is sent back to bridge. Signed-off-by: Arkadi Sharshevsky Reviewed-by: Ido Schimmel Signed-off-by: Jiri Pirko Signed-off-by: David S. Miller --- .../net/ethernet/mellanox/mlxsw/spectrum.c | 2 +- .../net/ethernet/mellanox/mlxsw/spectrum.h | 1 + .../mellanox/mlxsw/spectrum_switchdev.c | 137 ++++++++++++++++++ 3 files changed, 139 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum.c index db7127abce44..581cb99daec6 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum.c +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum.c @@ -3706,7 +3706,7 @@ struct mlxsw_sp *mlxsw_sp_lower_get(struct net_device *dev) return mlxsw_sp_port ? mlxsw_sp_port->mlxsw_sp : NULL; } -static struct mlxsw_sp_port *mlxsw_sp_port_dev_lower_find_rcu(struct net_device *dev) +struct mlxsw_sp_port *mlxsw_sp_port_dev_lower_find_rcu(struct net_device *dev) { struct mlxsw_sp_port *mlxsw_sp_port; diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum.h b/drivers/net/ethernet/mellanox/mlxsw/spectrum.h index 4a7a39a9f1a1..5ef98d4d0ab6 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum.h +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum.h @@ -361,6 +361,7 @@ struct mlxsw_sp *mlxsw_sp_lower_get(struct net_device *dev); struct mlxsw_sp_port *mlxsw_sp_port_dev_lower_find(struct net_device *dev); struct mlxsw_sp_port *mlxsw_sp_port_lower_dev_hold(struct net_device *dev); void mlxsw_sp_port_dev_put(struct mlxsw_sp_port *mlxsw_sp_port); +struct mlxsw_sp_port *mlxsw_sp_port_dev_lower_find_rcu(struct net_device *dev); /* spectrum_dcb.c */ #ifdef CONFIG_MLXSW_SPECTRUM_DCB diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_switchdev.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_switchdev.c index 0b50d1d2b517..0297c136d040 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_switchdev.c +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_switchdev.c @@ -1181,6 +1181,43 @@ mlxsw_sp_port_fdb_static_add(struct mlxsw_sp_port *mlxsw_sp_port, true, false); } +static int +mlxsw_sp_port_fdb_set(struct mlxsw_sp_port *mlxsw_sp_port, + struct switchdev_notifier_fdb_info *fdb_info, bool adding) +{ + struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp; + struct net_device *orig_dev = fdb_info->info.dev; + struct mlxsw_sp_port_vlan *mlxsw_sp_port_vlan; + struct mlxsw_sp_bridge_device *bridge_device; + struct mlxsw_sp_bridge_port *bridge_port; + u16 fid_index, vid; + + bridge_port = mlxsw_sp_bridge_port_find(mlxsw_sp->bridge, orig_dev); + if (!bridge_port) + return -EINVAL; + + bridge_device = bridge_port->bridge_device; + mlxsw_sp_port_vlan = mlxsw_sp_port_vlan_find_by_bridge(mlxsw_sp_port, + bridge_device, + fdb_info->vid); + if (!mlxsw_sp_port_vlan) + return 0; + + fid_index = mlxsw_sp_fid_index(mlxsw_sp_port_vlan->fid); + vid = mlxsw_sp_port_vlan->vid; + + if (!bridge_port->lagged) + return mlxsw_sp_port_fdb_uc_op(mlxsw_sp, + bridge_port->system_port, + fdb_info->addr, fid_index, + adding, false); + else + return mlxsw_sp_port_fdb_uc_lag_op(mlxsw_sp, + bridge_port->lag_id, + fdb_info->addr, fid_index, + vid, adding, false); +} + static int mlxsw_sp_port_mdb_op(struct mlxsw_sp *mlxsw_sp, const char *addr, u16 fid, u16 mid, bool adding) { @@ -2013,6 +2050,97 @@ out: mlxsw_sp_fdb_notify_work_schedule(mlxsw_sp); } +struct mlxsw_sp_switchdev_event_work { + struct work_struct work; + struct switchdev_notifier_fdb_info fdb_info; + struct net_device *dev; + unsigned long event; +}; + +static void mlxsw_sp_switchdev_event_work(struct work_struct *work) +{ + struct mlxsw_sp_switchdev_event_work *switchdev_work = + container_of(work, struct mlxsw_sp_switchdev_event_work, work); + struct net_device *dev = switchdev_work->dev; + struct switchdev_notifier_fdb_info *fdb_info; + struct mlxsw_sp_port *mlxsw_sp_port; + int err; + + rtnl_lock(); + mlxsw_sp_port = mlxsw_sp_port_dev_lower_find(dev); + if (!mlxsw_sp_port) + goto out; + + switch (switchdev_work->event) { + case SWITCHDEV_FDB_ADD_TO_DEVICE: + fdb_info = &switchdev_work->fdb_info; + err = mlxsw_sp_port_fdb_set(mlxsw_sp_port, fdb_info, true); + if (err) + break; + mlxsw_sp_fdb_call_notifiers(SWITCHDEV_FDB_OFFLOADED, + fdb_info->addr, + fdb_info->vid, dev); + break; + case SWITCHDEV_FDB_DEL_TO_DEVICE: + fdb_info = &switchdev_work->fdb_info; + mlxsw_sp_port_fdb_set(mlxsw_sp_port, fdb_info, false); + break; + } + +out: + rtnl_unlock(); + kfree(switchdev_work->fdb_info.addr); + kfree(switchdev_work); + dev_put(dev); +} + +/* Called under rcu_read_lock() */ +static int mlxsw_sp_switchdev_event(struct notifier_block *unused, + unsigned long event, void *ptr) +{ + struct net_device *dev = switchdev_notifier_info_to_dev(ptr); + struct mlxsw_sp_switchdev_event_work *switchdev_work; + struct switchdev_notifier_fdb_info *fdb_info = ptr; + + if (!mlxsw_sp_port_dev_lower_find_rcu(dev)) + return NOTIFY_DONE; + + switchdev_work = kzalloc(sizeof(*switchdev_work), GFP_ATOMIC); + if (!switchdev_work) + return NOTIFY_BAD; + + INIT_WORK(&switchdev_work->work, mlxsw_sp_switchdev_event_work); + switchdev_work->dev = dev; + switchdev_work->event = event; + + switch (event) { + case SWITCHDEV_FDB_ADD_TO_DEVICE: /* fall through */ + case SWITCHDEV_FDB_DEL_TO_DEVICE: + memcpy(&switchdev_work->fdb_info, ptr, + sizeof(switchdev_work->fdb_info)); + switchdev_work->fdb_info.addr = kzalloc(ETH_ALEN, GFP_ATOMIC); + ether_addr_copy((u8 *)switchdev_work->fdb_info.addr, + fdb_info->addr); + /* Take a reference on the device. This can be either + * upper device containig mlxsw_sp_port or just a + * mlxsw_sp_port + */ + dev_hold(dev); + break; + default: + kfree(switchdev_work); + return NOTIFY_DONE; + } + + mlxsw_core_schedule_work(&switchdev_work->work); + + return NOTIFY_DONE; +} + +static struct notifier_block mlxsw_sp_switchdev_notifier = { + .notifier_call = mlxsw_sp_switchdev_event, +}; + static int mlxsw_sp_fdb_init(struct mlxsw_sp *mlxsw_sp) { struct mlxsw_sp_bridge *bridge = mlxsw_sp->bridge; @@ -2023,6 +2151,13 @@ static int mlxsw_sp_fdb_init(struct mlxsw_sp *mlxsw_sp) dev_err(mlxsw_sp->bus_info->dev, "Failed to set default ageing time\n"); return err; } + + err = register_switchdev_notifier(&mlxsw_sp_switchdev_notifier); + if (err) { + dev_err(mlxsw_sp->bus_info->dev, "Failed to register switchdev notifier\n"); + return err; + } + INIT_DELAYED_WORK(&bridge->fdb_notify.dw, mlxsw_sp_fdb_notify_work); bridge->fdb_notify.interval = MLXSW_SP_DEFAULT_LEARNING_INTERVAL; mlxsw_sp_fdb_notify_work_schedule(mlxsw_sp); @@ -2032,6 +2167,8 @@ static int mlxsw_sp_fdb_init(struct mlxsw_sp *mlxsw_sp) static void mlxsw_sp_fdb_fini(struct mlxsw_sp *mlxsw_sp) { cancel_delayed_work_sync(&mlxsw_sp->bridge->fdb_notify.dw); + unregister_switchdev_notifier(&mlxsw_sp_switchdev_notifier); + } int mlxsw_sp_switchdev_init(struct mlxsw_sp *mlxsw_sp) -- 2.20.1