Commit 06d21290 authored by David S. Miller's avatar David S. Miller

Merge branch 'switchdev-blocking-notifiers'

Petr Machata says:

====================
switchdev: Convert switchdev_port_obj_{add,del}() to notifiers

An offloading driver may need to have access to switchdev events on
ports that aren't directly under its control. An example is a VXLAN port
attached to a bridge offloaded by a driver. The driver needs to know
about VLANs configured on the VXLAN device. However the VXLAN device
isn't stashed between the bridge and a front-panel-port device (such as
is the case e.g. for LAG devices), so the usual switchdev ops don't
reach the driver.

VXLAN is likely not the only device type like this: in theory any L2
tunnel device that needs offloading will prompt requirement of this
sort.

A way to fix this is to give up the notion of port object addition /
deletion as a switchdev operation, which assumes somewhat tight coupling
between the message producer and consumer. And instead send the message
over a notifier chain.

The series starts with a clean-up patch #1, where
SWITCHDEV_OBJ_PORT_{VLAN, MDB}() are fixed up to lift the constraint
that the passed-in argument be a simple variable named "obj".

switchdev_port_obj_add and _del are invoked in a context that permits
blocking. Not only that, at least for the VLAN notification, being able
to signal failure is actually important. Therefore introduce a new
blocking notifier chain that the new events will be sent on. That's done
in patch #2. Retain the current (atomic) notifier chain for the
preexisting notifications.

In patch #3, introduce two new switchdev notifier types,
SWITCHDEV_PORT_OBJ_ADD and SWITCHDEV_PORT_OBJ_DEL. These notifier types
communicate the same event as the corresponding switchdev op, except in
a form of a notification. struct switchdev_notifier_port_obj_info was
added to carry the fields that correspond to the switchdev op arguments.
An additional field, handled, will be used to communicate back to
switchdev that the event has reached an interested party, which will be
important for the two-phase commit.

In patches #4, #5, and #7, rocker, DSA resp. ethsw are updated to
subscribe to the switchdev blocking notifier chain, and handle the new
notifier types. #6 introduces a helper to determine whether a
netdevice corresponds to a front panel port.

What these three drivers have in common is that their ports don't
support any uppers besides bridge. That makes it possible to ignore any
notifiers that don't reference a front-panel port device, because they
are certainly out of scope.

Unlike the previous three, mlxsw and ocelot drivers admit stacked
devices as uppers. While the current switchdev code recursively descends
through layers of lower devices, eventually calling the op on a
front-panel port device, the notifier would reference a stacking device
that's one of front-panel ports uppers. The filtering is thus more
complex.

For ocelot, such iteration is currently pretty much required, because
there's no bookkeeping of LAG devices. mlxsw does keep the list of LAGs,
however it iterates the lower devices anyway when deciding whether an
event on a tunnel device pertains to the driver or not.

Therefore this patch set instead introduces, in patch #8, a helper to
iterate through lowers, much like the current switchdev code does,
looking for devices that match a given predicate.

Then in patches #9 and #10, first mlxsw and then ocelot are updated to
dispatch the newly-added notifier types to the preexisting
port_obj_add/_del handlers. The dispatch is done via the new helper, to
recursively descend through lower devices.

Finally in patch #11, the actual switch is made, retiring the current
SDO-based code in favor of a notifier.

Now that the event is distributed through a notifier, the explicit
netdevice check in rocker, DSA and ethsw doesn't let through any events
except those done on a front-panel port itself. It is therefore
unnecessary to check in VLAN-handling code whether a VLAN was added to
the bridge itself: such events will simply be ignored much sooner.
Therefore remove it in patch #12.
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 2eb487c1 ab4a1686
...@@ -1968,8 +1968,6 @@ static struct mlxsw_sp_port *mlxsw_sp_lag_rep_port(struct mlxsw_sp *mlxsw_sp, ...@@ -1968,8 +1968,6 @@ static struct mlxsw_sp_port *mlxsw_sp_lag_rep_port(struct mlxsw_sp *mlxsw_sp,
static const struct switchdev_ops mlxsw_sp_port_switchdev_ops = { static const struct switchdev_ops mlxsw_sp_port_switchdev_ops = {
.switchdev_port_attr_get = mlxsw_sp_port_attr_get, .switchdev_port_attr_get = mlxsw_sp_port_attr_get,
.switchdev_port_attr_set = mlxsw_sp_port_attr_set, .switchdev_port_attr_set = mlxsw_sp_port_attr_set,
.switchdev_port_obj_add = mlxsw_sp_port_obj_add,
.switchdev_port_obj_del = mlxsw_sp_port_obj_del,
}; };
static int static int
...@@ -3118,6 +3116,32 @@ static struct notifier_block mlxsw_sp_switchdev_notifier = { ...@@ -3118,6 +3116,32 @@ static struct notifier_block mlxsw_sp_switchdev_notifier = {
.notifier_call = mlxsw_sp_switchdev_event, .notifier_call = mlxsw_sp_switchdev_event,
}; };
static int mlxsw_sp_switchdev_blocking_event(struct notifier_block *unused,
unsigned long event, void *ptr)
{
struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
int err;
switch (event) {
case SWITCHDEV_PORT_OBJ_ADD:
err = switchdev_handle_port_obj_add(dev, ptr,
mlxsw_sp_port_dev_check,
mlxsw_sp_port_obj_add);
return notifier_from_errno(err);
case SWITCHDEV_PORT_OBJ_DEL:
err = switchdev_handle_port_obj_del(dev, ptr,
mlxsw_sp_port_dev_check,
mlxsw_sp_port_obj_del);
return notifier_from_errno(err);
}
return NOTIFY_DONE;
}
static struct notifier_block mlxsw_sp_switchdev_blocking_notifier = {
.notifier_call = mlxsw_sp_switchdev_blocking_event,
};
u8 u8
mlxsw_sp_bridge_port_stp_state(struct mlxsw_sp_bridge_port *bridge_port) mlxsw_sp_bridge_port_stp_state(struct mlxsw_sp_bridge_port *bridge_port)
{ {
...@@ -3127,6 +3151,7 @@ mlxsw_sp_bridge_port_stp_state(struct mlxsw_sp_bridge_port *bridge_port) ...@@ -3127,6 +3151,7 @@ mlxsw_sp_bridge_port_stp_state(struct mlxsw_sp_bridge_port *bridge_port)
static int mlxsw_sp_fdb_init(struct mlxsw_sp *mlxsw_sp) static int mlxsw_sp_fdb_init(struct mlxsw_sp *mlxsw_sp)
{ {
struct mlxsw_sp_bridge *bridge = mlxsw_sp->bridge; struct mlxsw_sp_bridge *bridge = mlxsw_sp->bridge;
struct notifier_block *nb;
int err; int err;
err = mlxsw_sp_ageing_set(mlxsw_sp, MLXSW_SP_DEFAULT_AGEING_TIME); err = mlxsw_sp_ageing_set(mlxsw_sp, MLXSW_SP_DEFAULT_AGEING_TIME);
...@@ -3141,17 +3166,33 @@ static int mlxsw_sp_fdb_init(struct mlxsw_sp *mlxsw_sp) ...@@ -3141,17 +3166,33 @@ static int mlxsw_sp_fdb_init(struct mlxsw_sp *mlxsw_sp)
return err; return err;
} }
nb = &mlxsw_sp_switchdev_blocking_notifier;
err = register_switchdev_blocking_notifier(nb);
if (err) {
dev_err(mlxsw_sp->bus_info->dev, "Failed to register switchdev blocking notifier\n");
goto err_register_switchdev_blocking_notifier;
}
INIT_DELAYED_WORK(&bridge->fdb_notify.dw, mlxsw_sp_fdb_notify_work); INIT_DELAYED_WORK(&bridge->fdb_notify.dw, mlxsw_sp_fdb_notify_work);
bridge->fdb_notify.interval = MLXSW_SP_DEFAULT_LEARNING_INTERVAL; bridge->fdb_notify.interval = MLXSW_SP_DEFAULT_LEARNING_INTERVAL;
mlxsw_sp_fdb_notify_work_schedule(mlxsw_sp); mlxsw_sp_fdb_notify_work_schedule(mlxsw_sp);
return 0; return 0;
err_register_switchdev_blocking_notifier:
unregister_switchdev_notifier(&mlxsw_sp_switchdev_notifier);
return err;
} }
static void mlxsw_sp_fdb_fini(struct mlxsw_sp *mlxsw_sp) static void mlxsw_sp_fdb_fini(struct mlxsw_sp *mlxsw_sp)
{ {
struct notifier_block *nb;
cancel_delayed_work_sync(&mlxsw_sp->bridge->fdb_notify.dw); cancel_delayed_work_sync(&mlxsw_sp->bridge->fdb_notify.dw);
unregister_switchdev_notifier(&mlxsw_sp_switchdev_notifier);
nb = &mlxsw_sp_switchdev_blocking_notifier;
unregister_switchdev_blocking_notifier(nb);
unregister_switchdev_notifier(&mlxsw_sp_switchdev_notifier);
} }
int mlxsw_sp_switchdev_init(struct mlxsw_sp *mlxsw_sp) int mlxsw_sp_switchdev_init(struct mlxsw_sp *mlxsw_sp)
......
...@@ -1337,8 +1337,6 @@ static int ocelot_port_obj_del(struct net_device *dev, ...@@ -1337,8 +1337,6 @@ static int ocelot_port_obj_del(struct net_device *dev,
static const struct switchdev_ops ocelot_port_switchdev_ops = { static const struct switchdev_ops ocelot_port_switchdev_ops = {
.switchdev_port_attr_get = ocelot_port_attr_get, .switchdev_port_attr_get = ocelot_port_attr_get,
.switchdev_port_attr_set = ocelot_port_attr_set, .switchdev_port_attr_set = ocelot_port_attr_set,
.switchdev_port_obj_add = ocelot_port_obj_add,
.switchdev_port_obj_del = ocelot_port_obj_del,
}; };
static int ocelot_port_bridge_join(struct ocelot_port *ocelot_port, static int ocelot_port_bridge_join(struct ocelot_port *ocelot_port,
...@@ -1595,6 +1593,34 @@ struct notifier_block ocelot_netdevice_nb __read_mostly = { ...@@ -1595,6 +1593,34 @@ struct notifier_block ocelot_netdevice_nb __read_mostly = {
}; };
EXPORT_SYMBOL(ocelot_netdevice_nb); EXPORT_SYMBOL(ocelot_netdevice_nb);
static int ocelot_switchdev_blocking_event(struct notifier_block *unused,
unsigned long event, void *ptr)
{
struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
int err;
switch (event) {
/* Blocking events. */
case SWITCHDEV_PORT_OBJ_ADD:
err = switchdev_handle_port_obj_add(dev, ptr,
ocelot_netdevice_dev_check,
ocelot_port_obj_add);
return notifier_from_errno(err);
case SWITCHDEV_PORT_OBJ_DEL:
err = switchdev_handle_port_obj_del(dev, ptr,
ocelot_netdevice_dev_check,
ocelot_port_obj_del);
return notifier_from_errno(err);
}
return NOTIFY_DONE;
}
struct notifier_block ocelot_switchdev_blocking_nb __read_mostly = {
.notifier_call = ocelot_switchdev_blocking_event,
};
EXPORT_SYMBOL(ocelot_switchdev_blocking_nb);
int ocelot_probe_port(struct ocelot *ocelot, u8 port, int ocelot_probe_port(struct ocelot *ocelot, u8 port,
void __iomem *regs, void __iomem *regs,
struct phy_device *phy) struct phy_device *phy)
......
...@@ -499,5 +499,6 @@ int ocelot_probe_port(struct ocelot *ocelot, u8 port, ...@@ -499,5 +499,6 @@ int ocelot_probe_port(struct ocelot *ocelot, u8 port,
struct phy_device *phy); struct phy_device *phy);
extern struct notifier_block ocelot_netdevice_nb; extern struct notifier_block ocelot_netdevice_nb;
extern struct notifier_block ocelot_switchdev_blocking_nb;
#endif #endif
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include <linux/of_platform.h> #include <linux/of_platform.h>
#include <linux/mfd/syscon.h> #include <linux/mfd/syscon.h>
#include <linux/skbuff.h> #include <linux/skbuff.h>
#include <net/switchdev.h>
#include "ocelot.h" #include "ocelot.h"
...@@ -328,6 +329,7 @@ static int mscc_ocelot_probe(struct platform_device *pdev) ...@@ -328,6 +329,7 @@ static int mscc_ocelot_probe(struct platform_device *pdev)
} }
register_netdevice_notifier(&ocelot_netdevice_nb); register_netdevice_notifier(&ocelot_netdevice_nb);
register_switchdev_blocking_notifier(&ocelot_switchdev_blocking_nb);
dev_info(&pdev->dev, "Ocelot switch probed\n"); dev_info(&pdev->dev, "Ocelot switch probed\n");
...@@ -342,6 +344,7 @@ static int mscc_ocelot_remove(struct platform_device *pdev) ...@@ -342,6 +344,7 @@ static int mscc_ocelot_remove(struct platform_device *pdev)
struct ocelot *ocelot = platform_get_drvdata(pdev); struct ocelot *ocelot = platform_get_drvdata(pdev);
ocelot_deinit(ocelot); ocelot_deinit(ocelot);
unregister_switchdev_blocking_notifier(&ocelot_switchdev_blocking_nb);
unregister_netdevice_notifier(&ocelot_netdevice_nb); unregister_netdevice_notifier(&ocelot_netdevice_nb);
return 0; return 0;
......
...@@ -1632,9 +1632,6 @@ rocker_world_port_obj_vlan_add(struct rocker_port *rocker_port, ...@@ -1632,9 +1632,6 @@ rocker_world_port_obj_vlan_add(struct rocker_port *rocker_port,
{ {
struct rocker_world_ops *wops = rocker_port->rocker->wops; struct rocker_world_ops *wops = rocker_port->rocker->wops;
if (netif_is_bridge_master(vlan->obj.orig_dev))
return -EOPNOTSUPP;
if (!wops->port_obj_vlan_add) if (!wops->port_obj_vlan_add)
return -EOPNOTSUPP; return -EOPNOTSUPP;
...@@ -2145,8 +2142,6 @@ static int rocker_port_obj_del(struct net_device *dev, ...@@ -2145,8 +2142,6 @@ static int rocker_port_obj_del(struct net_device *dev,
static const struct switchdev_ops rocker_port_switchdev_ops = { static const struct switchdev_ops rocker_port_switchdev_ops = {
.switchdev_port_attr_get = rocker_port_attr_get, .switchdev_port_attr_get = rocker_port_attr_get,
.switchdev_port_attr_set = rocker_port_attr_set, .switchdev_port_attr_set = rocker_port_attr_set,
.switchdev_port_obj_add = rocker_port_obj_add,
.switchdev_port_obj_del = rocker_port_obj_del,
}; };
struct rocker_fib_event_work { struct rocker_fib_event_work {
...@@ -2812,12 +2807,54 @@ static int rocker_switchdev_event(struct notifier_block *unused, ...@@ -2812,12 +2807,54 @@ static int rocker_switchdev_event(struct notifier_block *unused,
return NOTIFY_DONE; return NOTIFY_DONE;
} }
static int
rocker_switchdev_port_obj_event(unsigned long event, struct net_device *netdev,
struct switchdev_notifier_port_obj_info *port_obj_info)
{
int err = -EOPNOTSUPP;
switch (event) {
case SWITCHDEV_PORT_OBJ_ADD:
err = rocker_port_obj_add(netdev, port_obj_info->obj,
port_obj_info->trans);
break;
case SWITCHDEV_PORT_OBJ_DEL:
err = rocker_port_obj_del(netdev, port_obj_info->obj);
break;
}
port_obj_info->handled = true;
return notifier_from_errno(err);
}
static int rocker_switchdev_blocking_event(struct notifier_block *unused,
unsigned long event, void *ptr)
{
struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
if (!rocker_port_dev_check(dev))
return NOTIFY_DONE;
switch (event) {
case SWITCHDEV_PORT_OBJ_ADD:
case SWITCHDEV_PORT_OBJ_DEL:
return rocker_switchdev_port_obj_event(event, dev, ptr);
}
return NOTIFY_DONE;
}
static struct notifier_block rocker_switchdev_notifier = { static struct notifier_block rocker_switchdev_notifier = {
.notifier_call = rocker_switchdev_event, .notifier_call = rocker_switchdev_event,
}; };
static struct notifier_block rocker_switchdev_blocking_notifier = {
.notifier_call = rocker_switchdev_blocking_event,
};
static int rocker_probe(struct pci_dev *pdev, const struct pci_device_id *id) static int rocker_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{ {
struct notifier_block *nb;
struct rocker *rocker; struct rocker *rocker;
int err; int err;
...@@ -2933,6 +2970,13 @@ static int rocker_probe(struct pci_dev *pdev, const struct pci_device_id *id) ...@@ -2933,6 +2970,13 @@ static int rocker_probe(struct pci_dev *pdev, const struct pci_device_id *id)
goto err_register_switchdev_notifier; goto err_register_switchdev_notifier;
} }
nb = &rocker_switchdev_blocking_notifier;
err = register_switchdev_blocking_notifier(nb);
if (err) {
dev_err(&pdev->dev, "Failed to register switchdev blocking notifier\n");
goto err_register_switchdev_blocking_notifier;
}
rocker->hw.id = rocker_read64(rocker, SWITCH_ID); rocker->hw.id = rocker_read64(rocker, SWITCH_ID);
dev_info(&pdev->dev, "Rocker switch with id %*phN\n", dev_info(&pdev->dev, "Rocker switch with id %*phN\n",
...@@ -2940,6 +2984,8 @@ static int rocker_probe(struct pci_dev *pdev, const struct pci_device_id *id) ...@@ -2940,6 +2984,8 @@ static int rocker_probe(struct pci_dev *pdev, const struct pci_device_id *id)
return 0; return 0;
err_register_switchdev_blocking_notifier:
unregister_switchdev_notifier(&rocker_switchdev_notifier);
err_register_switchdev_notifier: err_register_switchdev_notifier:
unregister_fib_notifier(&rocker->fib_nb); unregister_fib_notifier(&rocker->fib_nb);
err_register_fib_notifier: err_register_fib_notifier:
...@@ -2971,6 +3017,10 @@ static int rocker_probe(struct pci_dev *pdev, const struct pci_device_id *id) ...@@ -2971,6 +3017,10 @@ static int rocker_probe(struct pci_dev *pdev, const struct pci_device_id *id)
static void rocker_remove(struct pci_dev *pdev) static void rocker_remove(struct pci_dev *pdev)
{ {
struct rocker *rocker = pci_get_drvdata(pdev); struct rocker *rocker = pci_get_drvdata(pdev);
struct notifier_block *nb;
nb = &rocker_switchdev_blocking_notifier;
unregister_switchdev_blocking_notifier(nb);
unregister_switchdev_notifier(&rocker_switchdev_notifier); unregister_switchdev_notifier(&rocker_switchdev_notifier);
unregister_fib_notifier(&rocker->fib_nb); unregister_fib_notifier(&rocker->fib_nb);
......
...@@ -719,9 +719,6 @@ static int port_vlans_add(struct net_device *netdev, ...@@ -719,9 +719,6 @@ static int port_vlans_add(struct net_device *netdev,
struct ethsw_port_priv *port_priv = netdev_priv(netdev); struct ethsw_port_priv *port_priv = netdev_priv(netdev);
int vid, err = 0; int vid, err = 0;
if (netif_is_bridge_master(vlan->obj.orig_dev))
return -EOPNOTSUPP;
if (switchdev_trans_ph_prepare(trans)) if (switchdev_trans_ph_prepare(trans))
return 0; return 0;
...@@ -930,8 +927,6 @@ static int swdev_port_obj_del(struct net_device *netdev, ...@@ -930,8 +927,6 @@ static int swdev_port_obj_del(struct net_device *netdev,
static const struct switchdev_ops ethsw_port_switchdev_ops = { static const struct switchdev_ops ethsw_port_switchdev_ops = {
.switchdev_port_attr_get = swdev_port_attr_get, .switchdev_port_attr_get = swdev_port_attr_get,
.switchdev_port_attr_set = swdev_port_attr_set, .switchdev_port_attr_set = swdev_port_attr_set,
.switchdev_port_obj_add = swdev_port_obj_add,
.switchdev_port_obj_del = swdev_port_obj_del,
}; };
/* For the moment, only flood setting needs to be updated */ /* For the moment, only flood setting needs to be updated */
...@@ -972,6 +967,11 @@ static int port_bridge_leave(struct net_device *netdev) ...@@ -972,6 +967,11 @@ static int port_bridge_leave(struct net_device *netdev)
return err; return err;
} }
static bool ethsw_port_dev_check(const struct net_device *netdev)
{
return netdev->netdev_ops == &ethsw_port_ops;
}
static int port_netdevice_event(struct notifier_block *unused, static int port_netdevice_event(struct notifier_block *unused,
unsigned long event, void *ptr) unsigned long event, void *ptr)
{ {
...@@ -980,7 +980,7 @@ static int port_netdevice_event(struct notifier_block *unused, ...@@ -980,7 +980,7 @@ static int port_netdevice_event(struct notifier_block *unused,
struct net_device *upper_dev; struct net_device *upper_dev;
int err = 0; int err = 0;
if (netdev->netdev_ops != &ethsw_port_ops) if (!ethsw_port_dev_check(netdev))
return NOTIFY_DONE; return NOTIFY_DONE;
/* Handle just upper dev link/unlink for the moment */ /* Handle just upper dev link/unlink for the moment */
...@@ -1083,10 +1083,51 @@ static int port_switchdev_event(struct notifier_block *unused, ...@@ -1083,10 +1083,51 @@ static int port_switchdev_event(struct notifier_block *unused,
return NOTIFY_BAD; return NOTIFY_BAD;
} }
static int
ethsw_switchdev_port_obj_event(unsigned long event, struct net_device *netdev,
struct switchdev_notifier_port_obj_info *port_obj_info)
{
int err = -EOPNOTSUPP;
switch (event) {
case SWITCHDEV_PORT_OBJ_ADD:
err = swdev_port_obj_add(netdev, port_obj_info->obj,
port_obj_info->trans);
break;
case SWITCHDEV_PORT_OBJ_DEL:
err = swdev_port_obj_del(netdev, port_obj_info->obj);
break;
}
port_obj_info->handled = true;
return notifier_from_errno(err);
}
static int port_switchdev_blocking_event(struct notifier_block *unused,
unsigned long event, void *ptr)
{
struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
if (!ethsw_port_dev_check(dev))
return NOTIFY_DONE;
switch (event) {
case SWITCHDEV_PORT_OBJ_ADD: /* fall through */
case SWITCHDEV_PORT_OBJ_DEL:
return ethsw_switchdev_port_obj_event(event, dev, ptr);
}
return NOTIFY_DONE;
}
static struct notifier_block port_switchdev_nb = { static struct notifier_block port_switchdev_nb = {
.notifier_call = port_switchdev_event, .notifier_call = port_switchdev_event,
}; };
static struct notifier_block port_switchdev_blocking_nb = {
.notifier_call = port_switchdev_blocking_event,
};
static int ethsw_register_notifier(struct device *dev) static int ethsw_register_notifier(struct device *dev)
{ {
int err; int err;
...@@ -1103,8 +1144,16 @@ static int ethsw_register_notifier(struct device *dev) ...@@ -1103,8 +1144,16 @@ static int ethsw_register_notifier(struct device *dev)
goto err_switchdev_nb; goto err_switchdev_nb;
} }
err = register_switchdev_blocking_notifier(&port_switchdev_blocking_nb);
if (err) {
dev_err(dev, "Failed to register switchdev blocking notifier\n");
goto err_switchdev_blocking_nb;
}
return 0; return 0;
err_switchdev_blocking_nb:
unregister_switchdev_notifier(&port_switchdev_nb);
err_switchdev_nb: err_switchdev_nb:
unregister_netdevice_notifier(&port_nb); unregister_netdevice_notifier(&port_nb);
return err; return err;
...@@ -1291,8 +1340,15 @@ static int ethsw_port_init(struct ethsw_port_priv *port_priv, u16 port) ...@@ -1291,8 +1340,15 @@ static int ethsw_port_init(struct ethsw_port_priv *port_priv, u16 port)
static void ethsw_unregister_notifier(struct device *dev) static void ethsw_unregister_notifier(struct device *dev)
{ {
struct notifier_block *nb;
int err; int err;
nb = &port_switchdev_blocking_nb;
err = unregister_switchdev_blocking_notifier(nb);
if (err)
dev_err(dev,
"Failed to unregister switchdev blocking notifier (%d)\n", err);
err = unregister_switchdev_notifier(&port_switchdev_nb); err = unregister_switchdev_notifier(&port_switchdev_nb);
if (err) if (err)
dev_err(dev, dev_err(dev,
......
...@@ -95,8 +95,8 @@ struct switchdev_obj_port_vlan { ...@@ -95,8 +95,8 @@ struct switchdev_obj_port_vlan {
u16 vid_end; u16 vid_end;
}; };
#define SWITCHDEV_OBJ_PORT_VLAN(obj) \ #define SWITCHDEV_OBJ_PORT_VLAN(OBJ) \
container_of(obj, struct switchdev_obj_port_vlan, obj) container_of((OBJ), struct switchdev_obj_port_vlan, obj)
/* SWITCHDEV_OBJ_ID_PORT_MDB */ /* SWITCHDEV_OBJ_ID_PORT_MDB */
struct switchdev_obj_port_mdb { struct switchdev_obj_port_mdb {
...@@ -105,8 +105,8 @@ struct switchdev_obj_port_mdb { ...@@ -105,8 +105,8 @@ struct switchdev_obj_port_mdb {
u16 vid; u16 vid;
}; };
#define SWITCHDEV_OBJ_PORT_MDB(obj) \ #define SWITCHDEV_OBJ_PORT_MDB(OBJ) \
container_of(obj, struct switchdev_obj_port_mdb, obj) container_of((OBJ), struct switchdev_obj_port_mdb, obj)
void switchdev_trans_item_enqueue(struct switchdev_trans *trans, void switchdev_trans_item_enqueue(struct switchdev_trans *trans,
void *data, void (*destructor)(void const *), void *data, void (*destructor)(void const *),
...@@ -121,10 +121,6 @@ typedef int switchdev_obj_dump_cb_t(struct switchdev_obj *obj); ...@@ -121,10 +121,6 @@ typedef int switchdev_obj_dump_cb_t(struct switchdev_obj *obj);
* @switchdev_port_attr_get: Get a port attribute (see switchdev_attr). * @switchdev_port_attr_get: Get a port attribute (see switchdev_attr).
* *
* @switchdev_port_attr_set: Set a port attribute (see switchdev_attr). * @switchdev_port_attr_set: Set a port attribute (see switchdev_attr).
*
* @switchdev_port_obj_add: Add an object to port (see switchdev_obj_*).
*
* @switchdev_port_obj_del: Delete an object from port (see switchdev_obj_*).
*/ */
struct switchdev_ops { struct switchdev_ops {
int (*switchdev_port_attr_get)(struct net_device *dev, int (*switchdev_port_attr_get)(struct net_device *dev,
...@@ -132,11 +128,6 @@ struct switchdev_ops { ...@@ -132,11 +128,6 @@ struct switchdev_ops {
int (*switchdev_port_attr_set)(struct net_device *dev, int (*switchdev_port_attr_set)(struct net_device *dev,
const struct switchdev_attr *attr, const struct switchdev_attr *attr,
struct switchdev_trans *trans); struct switchdev_trans *trans);
int (*switchdev_port_obj_add)(struct net_device *dev,
const struct switchdev_obj *obj,
struct switchdev_trans *trans);
int (*switchdev_port_obj_del)(struct net_device *dev,
const struct switchdev_obj *obj);
}; };
enum switchdev_notifier_type { enum switchdev_notifier_type {
...@@ -146,6 +137,9 @@ enum switchdev_notifier_type { ...@@ -146,6 +137,9 @@ enum switchdev_notifier_type {
SWITCHDEV_FDB_DEL_TO_DEVICE, SWITCHDEV_FDB_DEL_TO_DEVICE,
SWITCHDEV_FDB_OFFLOADED, SWITCHDEV_FDB_OFFLOADED,
SWITCHDEV_PORT_OBJ_ADD, /* Blocking. */
SWITCHDEV_PORT_OBJ_DEL, /* Blocking. */
SWITCHDEV_VXLAN_FDB_ADD_TO_BRIDGE, SWITCHDEV_VXLAN_FDB_ADD_TO_BRIDGE,
SWITCHDEV_VXLAN_FDB_DEL_TO_BRIDGE, SWITCHDEV_VXLAN_FDB_DEL_TO_BRIDGE,
SWITCHDEV_VXLAN_FDB_ADD_TO_DEVICE, SWITCHDEV_VXLAN_FDB_ADD_TO_DEVICE,
...@@ -165,6 +159,13 @@ struct switchdev_notifier_fdb_info { ...@@ -165,6 +159,13 @@ struct switchdev_notifier_fdb_info {
offloaded:1; offloaded:1;
}; };
struct switchdev_notifier_port_obj_info {
struct switchdev_notifier_info info; /* must be first */
const struct switchdev_obj *obj;
struct switchdev_trans *trans;
bool handled;
};
static inline struct net_device * static inline struct net_device *
switchdev_notifier_info_to_dev(const struct switchdev_notifier_info *info) switchdev_notifier_info_to_dev(const struct switchdev_notifier_info *info)
{ {
...@@ -182,10 +183,17 @@ int switchdev_port_obj_add(struct net_device *dev, ...@@ -182,10 +183,17 @@ int switchdev_port_obj_add(struct net_device *dev,
const struct switchdev_obj *obj); const struct switchdev_obj *obj);
int switchdev_port_obj_del(struct net_device *dev, int switchdev_port_obj_del(struct net_device *dev,
const struct switchdev_obj *obj); const struct switchdev_obj *obj);
int register_switchdev_notifier(struct notifier_block *nb); int register_switchdev_notifier(struct notifier_block *nb);
int unregister_switchdev_notifier(struct notifier_block *nb); int unregister_switchdev_notifier(struct notifier_block *nb);
int call_switchdev_notifiers(unsigned long val, struct net_device *dev, int call_switchdev_notifiers(unsigned long val, struct net_device *dev,
struct switchdev_notifier_info *info); struct switchdev_notifier_info *info);
int register_switchdev_blocking_notifier(struct notifier_block *nb);
int unregister_switchdev_blocking_notifier(struct notifier_block *nb);
int call_switchdev_blocking_notifiers(unsigned long val, struct net_device *dev,
struct switchdev_notifier_info *info);
void switchdev_port_fwd_mark_set(struct net_device *dev, void switchdev_port_fwd_mark_set(struct net_device *dev,
struct net_device *group_dev, struct net_device *group_dev,
bool joining); bool joining);
...@@ -193,6 +201,18 @@ void switchdev_port_fwd_mark_set(struct net_device *dev, ...@@ -193,6 +201,18 @@ void switchdev_port_fwd_mark_set(struct net_device *dev,
bool switchdev_port_same_parent_id(struct net_device *a, bool switchdev_port_same_parent_id(struct net_device *a,
struct net_device *b); struct net_device *b);
int switchdev_handle_port_obj_add(struct net_device *dev,
struct switchdev_notifier_port_obj_info *port_obj_info,
bool (*check_cb)(const struct net_device *dev),
int (*add_cb)(struct net_device *dev,
const struct switchdev_obj *obj,
struct switchdev_trans *trans));
int switchdev_handle_port_obj_del(struct net_device *dev,
struct switchdev_notifier_port_obj_info *port_obj_info,
bool (*check_cb)(const struct net_device *dev),
int (*del_cb)(struct net_device *dev,
const struct switchdev_obj *obj));
#define SWITCHDEV_SET_OPS(netdev, ops) ((netdev)->switchdev_ops = (ops)) #define SWITCHDEV_SET_OPS(netdev, ops) ((netdev)->switchdev_ops = (ops))
#else #else
...@@ -241,12 +261,53 @@ static inline int call_switchdev_notifiers(unsigned long val, ...@@ -241,12 +261,53 @@ static inline int call_switchdev_notifiers(unsigned long val,
return NOTIFY_DONE; return NOTIFY_DONE;
} }
static inline int
register_switchdev_blocking_notifier(struct notifier_block *nb)
{
return 0;
}
static inline int
unregister_switchdev_blocking_notifier(struct notifier_block *nb)
{
return 0;
}
static inline int
call_switchdev_blocking_notifiers(unsigned long val,
struct net_device *dev,
struct switchdev_notifier_info *info)
{
return NOTIFY_DONE;
}
static inline bool switchdev_port_same_parent_id(struct net_device *a, static inline bool switchdev_port_same_parent_id(struct net_device *a,
struct net_device *b) struct net_device *b)
{ {
return false; return false;
} }
static inline int
switchdev_handle_port_obj_add(struct net_device *dev,
struct switchdev_notifier_port_obj_info *port_obj_info,
bool (*check_cb)(const struct net_device *dev),
int (*add_cb)(struct net_device *dev,
const struct switchdev_obj *obj,
struct switchdev_trans *trans))
{
return 0;
}
static inline int
switchdev_handle_port_obj_del(struct net_device *dev,
struct switchdev_notifier_port_obj_info *port_obj_info,
bool (*check_cb)(const struct net_device *dev),
int (*del_cb)(struct net_device *dev,
const struct switchdev_obj *obj))
{
return 0;
}
#define SWITCHDEV_SET_OPS(netdev, ops) do {} while (0) #define SWITCHDEV_SET_OPS(netdev, ops) do {} while (0)
#endif #endif
......
...@@ -252,9 +252,6 @@ int dsa_port_vlan_add(struct dsa_port *dp, ...@@ -252,9 +252,6 @@ int dsa_port_vlan_add(struct dsa_port *dp,
.vlan = vlan, .vlan = vlan,
}; };
if (netif_is_bridge_master(vlan->obj.orig_dev))
return -EOPNOTSUPP;
if (br_vlan_enabled(dp->bridge_dev)) if (br_vlan_enabled(dp->bridge_dev))
return dsa_port_notify(dp, DSA_NOTIFIER_VLAN_ADD, &info); return dsa_port_notify(dp, DSA_NOTIFIER_VLAN_ADD, &info);
......
...@@ -1050,8 +1050,6 @@ static const struct net_device_ops dsa_slave_netdev_ops = { ...@@ -1050,8 +1050,6 @@ static const struct net_device_ops dsa_slave_netdev_ops = {
static const struct switchdev_ops dsa_slave_switchdev_ops = { static const struct switchdev_ops dsa_slave_switchdev_ops = {
.switchdev_port_attr_get = dsa_slave_port_attr_get, .switchdev_port_attr_get = dsa_slave_port_attr_get,
.switchdev_port_attr_set = dsa_slave_port_attr_set, .switchdev_port_attr_set = dsa_slave_port_attr_set,
.switchdev_port_obj_add = dsa_slave_port_obj_add,
.switchdev_port_obj_del = dsa_slave_port_obj_del,
}; };
static struct device_type dsa_type = { static struct device_type dsa_type = {
...@@ -1557,6 +1555,44 @@ static int dsa_slave_switchdev_event(struct notifier_block *unused, ...@@ -1557,6 +1555,44 @@ static int dsa_slave_switchdev_event(struct notifier_block *unused,
return NOTIFY_BAD; return NOTIFY_BAD;
} }
static int
dsa_slave_switchdev_port_obj_event(unsigned long event,
struct net_device *netdev,
struct switchdev_notifier_port_obj_info *port_obj_info)
{
int err = -EOPNOTSUPP;
switch (event) {
case SWITCHDEV_PORT_OBJ_ADD:
err = dsa_slave_port_obj_add(netdev, port_obj_info->obj,
port_obj_info->trans);
break;
case SWITCHDEV_PORT_OBJ_DEL:
err = dsa_slave_port_obj_del(netdev, port_obj_info->obj);
break;
}
port_obj_info->handled = true;
return notifier_from_errno(err);
}
static int dsa_slave_switchdev_blocking_event(struct notifier_block *unused,
unsigned long event, void *ptr)
{
struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
if (!dsa_slave_dev_check(dev))
return NOTIFY_DONE;
switch (event) {
case SWITCHDEV_PORT_OBJ_ADD: /* fall through */
case SWITCHDEV_PORT_OBJ_DEL:
return dsa_slave_switchdev_port_obj_event(event, dev, ptr);
}
return NOTIFY_DONE;
}
static struct notifier_block dsa_slave_nb __read_mostly = { static struct notifier_block dsa_slave_nb __read_mostly = {
.notifier_call = dsa_slave_netdevice_event, .notifier_call = dsa_slave_netdevice_event,
}; };
...@@ -1565,8 +1601,13 @@ static struct notifier_block dsa_slave_switchdev_notifier = { ...@@ -1565,8 +1601,13 @@ static struct notifier_block dsa_slave_switchdev_notifier = {
.notifier_call = dsa_slave_switchdev_event, .notifier_call = dsa_slave_switchdev_event,
}; };
static struct notifier_block dsa_slave_switchdev_blocking_notifier = {
.notifier_call = dsa_slave_switchdev_blocking_event,
};
int dsa_slave_register_notifier(void) int dsa_slave_register_notifier(void)
{ {
struct notifier_block *nb;
int err; int err;
err = register_netdevice_notifier(&dsa_slave_nb); err = register_netdevice_notifier(&dsa_slave_nb);
...@@ -1577,8 +1618,15 @@ int dsa_slave_register_notifier(void) ...@@ -1577,8 +1618,15 @@ int dsa_slave_register_notifier(void)
if (err) if (err)
goto err_switchdev_nb; goto err_switchdev_nb;
nb = &dsa_slave_switchdev_blocking_notifier;
err = register_switchdev_blocking_notifier(nb);
if (err)
goto err_switchdev_blocking_nb;
return 0; return 0;
err_switchdev_blocking_nb:
unregister_switchdev_notifier(&dsa_slave_switchdev_notifier);
err_switchdev_nb: err_switchdev_nb:
unregister_netdevice_notifier(&dsa_slave_nb); unregister_netdevice_notifier(&dsa_slave_nb);
return err; return err;
...@@ -1586,8 +1634,14 @@ int dsa_slave_register_notifier(void) ...@@ -1586,8 +1634,14 @@ int dsa_slave_register_notifier(void)
void dsa_slave_unregister_notifier(void) void dsa_slave_unregister_notifier(void)
{ {
struct notifier_block *nb;
int err; int err;
nb = &dsa_slave_switchdev_blocking_notifier;
err = unregister_switchdev_blocking_notifier(nb);
if (err)
pr_err("DSA: failed to unregister switchdev blocking notifier (%d)\n", err);
err = unregister_switchdev_notifier(&dsa_slave_switchdev_notifier); err = unregister_switchdev_notifier(&dsa_slave_switchdev_notifier);
if (err) if (err)
pr_err("DSA: failed to unregister switchdev notifier (%d)\n", err); pr_err("DSA: failed to unregister switchdev notifier (%d)\n", err);
......
...@@ -353,30 +353,29 @@ static size_t switchdev_obj_size(const struct switchdev_obj *obj) ...@@ -353,30 +353,29 @@ static size_t switchdev_obj_size(const struct switchdev_obj *obj)
return 0; return 0;
} }
static int __switchdev_port_obj_add(struct net_device *dev, static int switchdev_port_obj_notify(enum switchdev_notifier_type nt,
const struct switchdev_obj *obj, struct net_device *dev,
struct switchdev_trans *trans) const struct switchdev_obj *obj,
struct switchdev_trans *trans)
{ {
const struct switchdev_ops *ops = dev->switchdev_ops; int rc;
struct net_device *lower_dev; int err;
struct list_head *iter;
int err = -EOPNOTSUPP;
if (ops && ops->switchdev_port_obj_add)
return ops->switchdev_port_obj_add(dev, obj, trans);
/* Switch device port(s) may be stacked under struct switchdev_notifier_port_obj_info obj_info = {
* bond/team/vlan dev, so recurse down to add object on .obj = obj,
* each port. .trans = trans,
*/ .handled = false,
};
netdev_for_each_lower_dev(dev, lower_dev, iter) { rc = call_switchdev_blocking_notifiers(nt, dev, &obj_info.info);
err = __switchdev_port_obj_add(lower_dev, obj, trans); err = notifier_to_errno(rc);
if (err) if (err) {
break; WARN_ON(!obj_info.handled);
return err;
} }
if (!obj_info.handled)
return err; return -EOPNOTSUPP;
return 0;
} }
static int switchdev_port_obj_add_now(struct net_device *dev, static int switchdev_port_obj_add_now(struct net_device *dev,
...@@ -397,7 +396,8 @@ static int switchdev_port_obj_add_now(struct net_device *dev, ...@@ -397,7 +396,8 @@ static int switchdev_port_obj_add_now(struct net_device *dev,
*/ */
trans.ph_prepare = true; trans.ph_prepare = true;
err = __switchdev_port_obj_add(dev, obj, &trans); err = switchdev_port_obj_notify(SWITCHDEV_PORT_OBJ_ADD,
dev, obj, &trans);
if (err) { if (err) {
/* Prepare phase failed: abort the transaction. Any /* Prepare phase failed: abort the transaction. Any
* resources reserved in the prepare phase are * resources reserved in the prepare phase are
...@@ -416,7 +416,8 @@ static int switchdev_port_obj_add_now(struct net_device *dev, ...@@ -416,7 +416,8 @@ static int switchdev_port_obj_add_now(struct net_device *dev,
*/ */
trans.ph_prepare = false; trans.ph_prepare = false;
err = __switchdev_port_obj_add(dev, obj, &trans); err = switchdev_port_obj_notify(SWITCHDEV_PORT_OBJ_ADD,
dev, obj, &trans);
WARN(err, "%s: Commit of object (id=%d) failed.\n", dev->name, obj->id); WARN(err, "%s: Commit of object (id=%d) failed.\n", dev->name, obj->id);
switchdev_trans_items_warn_destroy(dev, &trans); switchdev_trans_items_warn_destroy(dev, &trans);
...@@ -471,26 +472,8 @@ EXPORT_SYMBOL_GPL(switchdev_port_obj_add); ...@@ -471,26 +472,8 @@ EXPORT_SYMBOL_GPL(switchdev_port_obj_add);
static int switchdev_port_obj_del_now(struct net_device *dev, static int switchdev_port_obj_del_now(struct net_device *dev,
const struct switchdev_obj *obj) const struct switchdev_obj *obj)
{ {
const struct switchdev_ops *ops = dev->switchdev_ops; return switchdev_port_obj_notify(SWITCHDEV_PORT_OBJ_DEL,
struct net_device *lower_dev; dev, obj, NULL);
struct list_head *iter;
int err = -EOPNOTSUPP;
if (ops && ops->switchdev_port_obj_del)
return ops->switchdev_port_obj_del(dev, obj);
/* Switch device port(s) may be stacked under
* bond/team/vlan dev, so recurse down to delete object on
* each port.
*/
netdev_for_each_lower_dev(dev, lower_dev, iter) {
err = switchdev_port_obj_del_now(lower_dev, obj);
if (err)
break;
}
return err;
} }
static void switchdev_port_obj_del_deferred(struct net_device *dev, static void switchdev_port_obj_del_deferred(struct net_device *dev,
...@@ -535,6 +518,7 @@ int switchdev_port_obj_del(struct net_device *dev, ...@@ -535,6 +518,7 @@ int switchdev_port_obj_del(struct net_device *dev,
EXPORT_SYMBOL_GPL(switchdev_port_obj_del); EXPORT_SYMBOL_GPL(switchdev_port_obj_del);
static ATOMIC_NOTIFIER_HEAD(switchdev_notif_chain); static ATOMIC_NOTIFIER_HEAD(switchdev_notif_chain);
static BLOCKING_NOTIFIER_HEAD(switchdev_blocking_notif_chain);
/** /**
* register_switchdev_notifier - Register notifier * register_switchdev_notifier - Register notifier
...@@ -576,6 +560,31 @@ int call_switchdev_notifiers(unsigned long val, struct net_device *dev, ...@@ -576,6 +560,31 @@ int call_switchdev_notifiers(unsigned long val, struct net_device *dev,
} }
EXPORT_SYMBOL_GPL(call_switchdev_notifiers); EXPORT_SYMBOL_GPL(call_switchdev_notifiers);
int register_switchdev_blocking_notifier(struct notifier_block *nb)
{
struct blocking_notifier_head *chain = &switchdev_blocking_notif_chain;
return blocking_notifier_chain_register(chain, nb);
}
EXPORT_SYMBOL_GPL(register_switchdev_blocking_notifier);
int unregister_switchdev_blocking_notifier(struct notifier_block *nb)
{
struct blocking_notifier_head *chain = &switchdev_blocking_notif_chain;
return blocking_notifier_chain_unregister(chain, nb);
}
EXPORT_SYMBOL_GPL(unregister_switchdev_blocking_notifier);
int call_switchdev_blocking_notifiers(unsigned long val, struct net_device *dev,
struct switchdev_notifier_info *info)
{
info->dev = dev;
return blocking_notifier_call_chain(&switchdev_blocking_notif_chain,
val, info);
}
EXPORT_SYMBOL_GPL(call_switchdev_blocking_notifiers);
bool switchdev_port_same_parent_id(struct net_device *a, bool switchdev_port_same_parent_id(struct net_device *a,
struct net_device *b) struct net_device *b)
{ {
...@@ -595,3 +604,103 @@ bool switchdev_port_same_parent_id(struct net_device *a, ...@@ -595,3 +604,103 @@ bool switchdev_port_same_parent_id(struct net_device *a,
return netdev_phys_item_id_same(&a_attr.u.ppid, &b_attr.u.ppid); return netdev_phys_item_id_same(&a_attr.u.ppid, &b_attr.u.ppid);
} }
EXPORT_SYMBOL_GPL(switchdev_port_same_parent_id); EXPORT_SYMBOL_GPL(switchdev_port_same_parent_id);
static int __switchdev_handle_port_obj_add(struct net_device *dev,
struct switchdev_notifier_port_obj_info *port_obj_info,
bool (*check_cb)(const struct net_device *dev),
int (*add_cb)(struct net_device *dev,
const struct switchdev_obj *obj,
struct switchdev_trans *trans))
{
struct net_device *lower_dev;
struct list_head *iter;
int err = -EOPNOTSUPP;
if (check_cb(dev)) {
/* This flag is only checked if the return value is success. */
port_obj_info->handled = true;
return add_cb(dev, port_obj_info->obj, port_obj_info->trans);
}
/* Switch ports might be stacked under e.g. a LAG. Ignore the
* unsupported devices, another driver might be able to handle them. But
* propagate to the callers any hard errors.
*
* If the driver does its own bookkeeping of stacked ports, it's not
* necessary to go through this helper.
*/
netdev_for_each_lower_dev(dev, lower_dev, iter) {
err = __switchdev_handle_port_obj_add(lower_dev, port_obj_info,
check_cb, add_cb);
if (err && err != -EOPNOTSUPP)
return err;
}
return err;
}
int switchdev_handle_port_obj_add(struct net_device *dev,
struct switchdev_notifier_port_obj_info *port_obj_info,
bool (*check_cb)(const struct net_device *dev),
int (*add_cb)(struct net_device *dev,
const struct switchdev_obj *obj,
struct switchdev_trans *trans))
{
int err;
err = __switchdev_handle_port_obj_add(dev, port_obj_info, check_cb,
add_cb);
if (err == -EOPNOTSUPP)
err = 0;
return err;
}
EXPORT_SYMBOL_GPL(switchdev_handle_port_obj_add);
static int __switchdev_handle_port_obj_del(struct net_device *dev,
struct switchdev_notifier_port_obj_info *port_obj_info,
bool (*check_cb)(const struct net_device *dev),
int (*del_cb)(struct net_device *dev,
const struct switchdev_obj *obj))
{
struct net_device *lower_dev;
struct list_head *iter;
int err = -EOPNOTSUPP;
if (check_cb(dev)) {
/* This flag is only checked if the return value is success. */
port_obj_info->handled = true;
return del_cb(dev, port_obj_info->obj);
}
/* Switch ports might be stacked under e.g. a LAG. Ignore the
* unsupported devices, another driver might be able to handle them. But
* propagate to the callers any hard errors.
*
* If the driver does its own bookkeeping of stacked ports, it's not
* necessary to go through this helper.
*/
netdev_for_each_lower_dev(dev, lower_dev, iter) {
err = __switchdev_handle_port_obj_del(lower_dev, port_obj_info,
check_cb, del_cb);
if (err && err != -EOPNOTSUPP)
return err;
}
return err;
}
int switchdev_handle_port_obj_del(struct net_device *dev,
struct switchdev_notifier_port_obj_info *port_obj_info,
bool (*check_cb)(const struct net_device *dev),
int (*del_cb)(struct net_device *dev,
const struct switchdev_obj *obj))
{
int err;
err = __switchdev_handle_port_obj_del(dev, port_obj_info, check_cb,
del_cb);
if (err == -EOPNOTSUPP)
err = 0;
return err;
}
EXPORT_SYMBOL_GPL(switchdev_handle_port_obj_del);
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