Commit 7cd6a54a authored by Vlad Buslov's avatar Vlad Buslov Committed by Saeed Mahameed

net/mlx5: Bridge, handle FDB events

Hardware supported by mlx5 driver doesn't provide learning and requires the
driver to emulate all switch-like behavior in software. As such, all
packets by default go through miss path, appear on representor and get to
software bridge, if it is the upper device of the representor. This causes
bridge to process packet in software, learn the MAC address to FDB and send
SWITCHDEV_FDB_ADD_TO_DEVICE event to all subscribers.

In order to offload FDB entries in mlx5, register switchdev notifier
callback and implement support for both 'added_by_user' and dynamic FDB
entry SWITCHDEV_FDB_ADD_TO_DEVICE events asynchronously using new
mlx5_esw_bridge_offloads->wq ordered workqueue. In workqueue callback
offload the ingress rule (matching FDB entry MAC as packet source MAC) and
egress table rule (matching FDB entry MAC as destination MAC). For ingress
table rule also match source vport to ensure that only traffic coming from
expected bridge port is matched by offloaded rule. Save all the relevant
FDB entry data in struct mlx5_esw_bridge_fdb_entry instance and insert the
instance in new mlx5_esw_bridge->fdb_list list (for traversing all entries
by software ageing implementation in following patch) and in new
mlx5_esw_bridge->fdb_ht hash table for fast retrieval. Notify the bridge
that FDB entry has been offloaded by sending SWITCHDEV_FDB_OFFLOADED
notification.

Delete FDB entry on reception of SWITCHDEV_FDB_DEL_TO_DEVICE event.
Signed-off-by: default avatarVlad Buslov <vladbu@nvidia.com>
Reviewed-by: default avatarJianbo Liu <jianbol@nvidia.com>
Signed-off-by: default avatarSaeed Mahameed <saeedm@nvidia.com>
parent 19e9bfa0
......@@ -12,6 +12,7 @@ Contents
- `Enabling the driver and kconfig options`_
- `Devlink info`_
- `Devlink parameters`_
- `Bridge offload`_
- `mlx5 subfunction`_
- `mlx5 function attributes`_
- `Devlink health reporters`_
......@@ -217,6 +218,20 @@ users try to enable them.
$ devlink dev eswitch set pci/0000:06:00.0 mode switchdev
Bridge offload
==============
The mlx5 driver implements support for offloading bridge rules when in switchdev
mode. Linux bridge FDBs are automatically offloaded when mlx5 switchdev
representor is attached to bridge.
- Change device to switchdev mode::
$ devlink dev eswitch set pci/0000:06:00.0 mode switchdev
- Attach mlx5 switchdev representor 'enp8s0f0' to bridge netdev 'bridge1'::
$ ip link set enp8s0f0 master bridge1
mlx5 subfunction
================
mlx5 supports subfunction management using devlink port (see :ref:`Documentation/networking/devlink/devlink-port.rst <devlink_port>`) interface.
......
......@@ -8,6 +8,13 @@
#include "esw/bridge.h"
#include "en_rep.h"
struct mlx5_bridge_switchdev_fdb_work {
struct work_struct work;
struct switchdev_notifier_fdb_info fdb_info;
struct net_device *dev;
bool add;
};
static int mlx5_esw_bridge_port_changeupper(struct notifier_block *nb, void *ptr)
{
struct mlx5_esw_bridge_offloads *br_offloads = container_of(nb,
......@@ -65,6 +72,124 @@ static int mlx5_esw_bridge_switchdev_port_event(struct notifier_block *nb,
return notifier_from_errno(err);
}
static void
mlx5_esw_bridge_cleanup_switchdev_fdb_work(struct mlx5_bridge_switchdev_fdb_work *fdb_work)
{
dev_put(fdb_work->dev);
kfree(fdb_work->fdb_info.addr);
kfree(fdb_work);
}
static void mlx5_esw_bridge_switchdev_fdb_event_work(struct work_struct *work)
{
struct mlx5_bridge_switchdev_fdb_work *fdb_work =
container_of(work, struct mlx5_bridge_switchdev_fdb_work, work);
struct switchdev_notifier_fdb_info *fdb_info =
&fdb_work->fdb_info;
struct net_device *dev = fdb_work->dev;
struct mlx5e_rep_priv *rpriv;
struct mlx5_eswitch *esw;
struct mlx5_vport *vport;
struct mlx5e_priv *priv;
u16 vport_num;
rtnl_lock();
priv = netdev_priv(dev);
rpriv = priv->ppriv;
vport_num = rpriv->rep->vport;
esw = priv->mdev->priv.eswitch;
vport = mlx5_eswitch_get_vport(esw, vport_num);
if (IS_ERR(vport))
goto out;
if (fdb_work->add)
mlx5_esw_bridge_fdb_create(dev, esw, vport, fdb_info);
else
mlx5_esw_bridge_fdb_remove(dev, esw, vport, fdb_info);
out:
rtnl_unlock();
mlx5_esw_bridge_cleanup_switchdev_fdb_work(fdb_work);
}
static struct mlx5_bridge_switchdev_fdb_work *
mlx5_esw_bridge_init_switchdev_fdb_work(struct net_device *dev, bool add,
struct switchdev_notifier_fdb_info *fdb_info)
{
struct mlx5_bridge_switchdev_fdb_work *work;
u8 *addr;
work = kzalloc(sizeof(*work), GFP_ATOMIC);
if (!work)
return ERR_PTR(-ENOMEM);
INIT_WORK(&work->work, mlx5_esw_bridge_switchdev_fdb_event_work);
memcpy(&work->fdb_info, fdb_info, sizeof(work->fdb_info));
addr = kzalloc(ETH_ALEN, GFP_ATOMIC);
if (!addr) {
kfree(work);
return ERR_PTR(-ENOMEM);
}
ether_addr_copy(addr, fdb_info->addr);
work->fdb_info.addr = addr;
dev_hold(dev);
work->dev = dev;
work->add = add;
return work;
}
static int mlx5_esw_bridge_switchdev_event(struct notifier_block *nb,
unsigned long event, void *ptr)
{
struct mlx5_esw_bridge_offloads *br_offloads = container_of(nb,
struct mlx5_esw_bridge_offloads,
nb);
struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
struct switchdev_notifier_fdb_info *fdb_info;
struct mlx5_bridge_switchdev_fdb_work *work;
struct switchdev_notifier_info *info = ptr;
struct net_device *upper;
struct mlx5e_priv *priv;
if (!mlx5e_eswitch_rep(dev))
return NOTIFY_DONE;
priv = netdev_priv(dev);
if (priv->mdev->priv.eswitch != br_offloads->esw)
return NOTIFY_DONE;
upper = netdev_master_upper_dev_get_rcu(dev);
if (!upper)
return NOTIFY_DONE;
if (!netif_is_bridge_master(upper))
return NOTIFY_DONE;
switch (event) {
case SWITCHDEV_FDB_ADD_TO_DEVICE:
case SWITCHDEV_FDB_DEL_TO_DEVICE:
fdb_info = container_of(info,
struct switchdev_notifier_fdb_info,
info);
work = mlx5_esw_bridge_init_switchdev_fdb_work(dev,
event == SWITCHDEV_FDB_ADD_TO_DEVICE,
fdb_info);
if (IS_ERR(work)) {
WARN_ONCE(1, "Failed to init switchdev work, err=%ld",
PTR_ERR(work));
return notifier_from_errno(PTR_ERR(work));
}
queue_work(br_offloads->wq, &work->work);
break;
default:
break;
}
return NOTIFY_DONE;
}
void mlx5e_rep_bridge_init(struct mlx5e_priv *priv)
{
struct mlx5_esw_bridge_offloads *br_offloads;
......@@ -81,13 +206,34 @@ void mlx5e_rep_bridge_init(struct mlx5e_priv *priv)
return;
}
br_offloads->wq = alloc_ordered_workqueue("mlx5_bridge_wq", 0);
if (!br_offloads->wq) {
esw_warn(mdev, "Failed to allocate bridge offloads workqueue\n");
goto err_alloc_wq;
}
br_offloads->nb.notifier_call = mlx5_esw_bridge_switchdev_event;
err = register_switchdev_notifier(&br_offloads->nb);
if (err) {
esw_warn(mdev, "Failed to register switchdev notifier (err=%d)\n", err);
goto err_register_swdev;
}
br_offloads->netdev_nb.notifier_call = mlx5_esw_bridge_switchdev_port_event;
err = register_netdevice_notifier(&br_offloads->netdev_nb);
if (err) {
esw_warn(mdev, "Failed to register bridge offloads netdevice notifier (err=%d)\n",
err);
mlx5_esw_bridge_cleanup(esw);
goto err_register_netdev;
}
return;
err_register_netdev:
unregister_switchdev_notifier(&br_offloads->nb);
err_register_swdev:
destroy_workqueue(br_offloads->wq);
err_alloc_wq:
mlx5_esw_bridge_cleanup(esw);
}
void mlx5e_rep_bridge_cleanup(struct mlx5e_priv *priv)
......@@ -102,6 +248,8 @@ void mlx5e_rep_bridge_cleanup(struct mlx5e_priv *priv)
return;
unregister_netdevice_notifier(&br_offloads->netdev_nb);
unregister_switchdev_notifier(&br_offloads->nb);
destroy_workqueue(br_offloads->wq);
rtnl_lock();
mlx5_esw_bridge_cleanup(esw);
rtnl_unlock();
......
......@@ -10,11 +10,14 @@
struct mlx5_flow_table;
struct mlx5_flow_group;
struct workqueue_struct;
struct mlx5_esw_bridge_offloads {
struct mlx5_eswitch *esw;
struct list_head bridges;
struct notifier_block netdev_nb;
struct notifier_block nb;
struct workqueue_struct *wq;
struct mlx5_flow_table *ingress_ft;
struct mlx5_flow_group *ingress_mac_fg;
......@@ -26,5 +29,11 @@ int mlx5_esw_bridge_vport_link(int ifindex, struct mlx5_esw_bridge_offloads *br_
struct mlx5_vport *vport, struct netlink_ext_ack *extack);
int mlx5_esw_bridge_vport_unlink(int ifindex, struct mlx5_esw_bridge_offloads *br_offloads,
struct mlx5_vport *vport, struct netlink_ext_ack *extack);
void mlx5_esw_bridge_fdb_create(struct net_device *dev, struct mlx5_eswitch *esw,
struct mlx5_vport *vport,
struct switchdev_notifier_fdb_info *fdb_info);
void mlx5_esw_bridge_fdb_remove(struct net_device *dev, struct mlx5_eswitch *esw,
struct mlx5_vport *vport,
struct switchdev_notifier_fdb_info *fdb_info);
#endif /* __MLX5_ESW_BRIDGE_H__ */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment