From: Alexandra Winter <wintera@xxxxxxxxxxxxx> 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@xxxxxxxxxxxxx> Reviewed-by: Wenjia Zhang <wenjia@xxxxxxxxxxxxx> Signed-off-by: Karsten Graul <kgraul@xxxxxxxxxxxxx> --- drivers/s390/net/Kconfig | 1 + 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); -- 2.25.1