diff options
author | Alexandra Winter <wintera@linux.ibm.com> | 2021-08-06 17:26:02 +0200 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2021-08-07 09:44:02 +0100 |
commit | 4e20e73e631ac4c2f7af603f14bd44a6d77d919c (patch) | |
tree | 5df0d838739d0dc85c28d32c4e8820195f44cca9 | |
parent | 60bb1089467d52c68190f813105c897b0933bbcc (diff) |
s390/qeth: Switchdev event handler
QETH HiperSockets devices with LEARNING_SYNC capability can be used
to construct a linux bridge with:
2 isolated southbound interfaces:
a) a default network interface
b) a LEARNING-SYNC HiperSockets interface
and 1 non-isolated northbound interface. This is called a 'HiperSockets
Converged Interface' (HSCI).
The existing LEARNING_SYNC functionality is used to update the bridge fdb
with MAC addresses that should be sent-out via the HiperSockets interface,
instead of the default network interface.
Add handling of switchdev events SWITCHDEV_FDB_ADD_TO_DEVICE and
SWITCHDEV_FDB_DEL_TO_DEVICE to the qeth LEARNING_SYNC functionality. Thus
if the northbound bridgeport of an HSCI doesn't only have a single static
MAC address, but instead is a learning bridgeport, work is enqueued, so
the HiperSockets virtual switch (that is external to this Linux instance)
can update its fdb.
When BRIDGE is a loadable module, QETH_L2 mustn't be built-in:
drivers/s390/net/qeth_l2_main.o: in function 'qeth_l2_switchdev_event':
drivers/s390/net/qeth_l2_main.c:927: undefined reference to
'br_port_flag_is_set'
Add Kconfig dependency to enforce usable configurations.
Signed-off-by: Alexandra Winter <wintera@linux.ibm.com>
Reviewed-by: Wenjia Zhang <wenjia@linux.ibm.com>
Signed-off-by: Karsten Graul <kgraul@linux.ibm.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | drivers/s390/net/Kconfig | 1 | ||||
-rw-r--r-- | drivers/s390/net/qeth_l2_main.c | 83 |
2 files changed, 79 insertions, 5 deletions
diff --git a/drivers/s390/net/Kconfig b/drivers/s390/net/Kconfig index cff91b4f1a76..9c67b97faba2 100644 --- a/drivers/s390/net/Kconfig +++ b/drivers/s390/net/Kconfig @@ -74,6 +74,7 @@ config QETH_L2 def_tristate y prompt "qeth layer 2 device support" depends on QETH + depends on BRIDGE || BRIDGE=n help Select this option to be able to run qeth devices in layer 2 mode. To compile as a module, choose M. The module name is qeth_l2. diff --git a/drivers/s390/net/qeth_l2_main.c b/drivers/s390/net/qeth_l2_main.c index 3d02d35df5d3..e38a1befce3f 100644 --- a/drivers/s390/net/qeth_l2_main.c +++ b/drivers/s390/net/qeth_l2_main.c @@ -717,15 +717,79 @@ static int qeth_l2_dev2br_an_set(struct qeth_card *card, bool enable) return rc; } +static const struct net_device_ops qeth_l2_netdev_ops; + +static bool qeth_l2_must_learn(struct net_device *netdev, + struct net_device *dstdev) +{ + struct qeth_priv *priv; + + priv = netdev_priv(netdev); + return (netdev != dstdev && + (priv->brport_features & BR_LEARNING_SYNC) && + !(br_port_flag_is_set(netdev, BR_ISOLATED) && + br_port_flag_is_set(dstdev, BR_ISOLATED)) && + netdev->netdev_ops == &qeth_l2_netdev_ops); +} + +/* Called under rtnl_lock */ +static int qeth_l2_switchdev_event(struct notifier_block *unused, + unsigned long event, void *ptr) +{ + struct net_device *dstdev, *brdev, *lowerdev; + struct switchdev_notifier_fdb_info *fdb_info; + struct switchdev_notifier_info *info = ptr; + struct list_head *iter; + struct qeth_card *card; + + if (!(event == SWITCHDEV_FDB_ADD_TO_DEVICE || + event == SWITCHDEV_FDB_DEL_TO_DEVICE)) + return NOTIFY_DONE; + + dstdev = switchdev_notifier_info_to_dev(info); + brdev = netdev_master_upper_dev_get_rcu(dstdev); + if (!brdev || !netif_is_bridge_master(brdev)) + return NOTIFY_DONE; + fdb_info = container_of(info, + struct switchdev_notifier_fdb_info, + info); + iter = &brdev->adj_list.lower; + lowerdev = netdev_next_lower_dev_rcu(brdev, &iter); + while (lowerdev) { + if (qeth_l2_must_learn(lowerdev, dstdev)) { + card = lowerdev->ml_priv; + QETH_CARD_TEXT_(card, 4, "b2dqw%03x", event); + /* tbd: rc = qeth_l2_br2dev_queue_work(brdev, lowerdev, + * dstdev, event, + * fdb_info->addr); + */ + } + lowerdev = netdev_next_lower_dev_rcu(brdev, &iter); + } + return NOTIFY_DONE; +} + +static struct notifier_block qeth_l2_sw_notifier = { + .notifier_call = qeth_l2_switchdev_event, +}; + static refcount_t qeth_l2_switchdev_notify_refcnt; /* Called under rtnl_lock */ static void qeth_l2_br2dev_get(void) { + int rc; + if (!refcount_inc_not_zero(&qeth_l2_switchdev_notify_refcnt)) { - /* tbd: register_switchdev_notifier(&qeth_l2_sw_notifier); */ - refcount_set(&qeth_l2_switchdev_notify_refcnt, 1); - QETH_DBF_MESSAGE(2, "qeth_l2_sw_notifier registered\n"); + rc = register_switchdev_notifier(&qeth_l2_sw_notifier); + if (rc) { + QETH_DBF_MESSAGE(2, + "failed to register qeth_l2_sw_notifier: %d\n", + rc); + } else { + refcount_set(&qeth_l2_switchdev_notify_refcnt, 1); + QETH_DBF_MESSAGE(2, "qeth_l2_sw_notifier registered\n"); + } } QETH_DBF_TEXT_(SETUP, 2, "b2d+%04d", qeth_l2_switchdev_notify_refcnt.refs.counter); @@ -734,9 +798,18 @@ static void qeth_l2_br2dev_get(void) /* Called under rtnl_lock */ static void qeth_l2_br2dev_put(void) { + int rc; + if (refcount_dec_and_test(&qeth_l2_switchdev_notify_refcnt)) { - /* tbd: unregister_switchdev_notifier(&qeth_l2_sw_notifier); */ - QETH_DBF_MESSAGE(2, "qeth_l2_sw_notifier unregistered\n"); + rc = unregister_switchdev_notifier(&qeth_l2_sw_notifier); + if (rc) { + QETH_DBF_MESSAGE(2, + "failed to unregister qeth_l2_sw_notifier: %d\n", + rc); + } else { + QETH_DBF_MESSAGE(2, + "qeth_l2_sw_notifier unregistered\n"); + } } QETH_DBF_TEXT_(SETUP, 2, "b2d-%04d", qeth_l2_switchdev_notify_refcnt.refs.counter); |