This allows for configurations where there are a few known senders in the system (e.g. multiple SoCs on the same board) along with unlimited external senders. The source mode devices represent the known senders while all external senders terminate on passthru device. Although you could still receive packets on the lower device without the need for the passthru vlan device, there are use cases where you need additional packet processing in the pipeline that hooks via rx_handler. But the rx_handler is already bound to the macvlan port. With this patch the rx_handler can be attached to the passthru device while macvlan itself remains attached to the lower device. We use this to use the share the only available high-speed interface for inter-SoC networking and external networking (user traffic). Inter-SoC packets are received on the source mode interface, identified by a well-known source address. Some of our chips have simply no other viable link for inter-SoC traffic. Signed-off-by: Thomas Martitz --- drivers/net/macvlan.c | 107 ++++++++++++++++++++++++++++-------------- 1 file changed, 73 insertions(+), 34 deletions(-) diff --git a/drivers/net/macvlan.c b/drivers/net/macvlan.c index 9a4bc99dbf53b..2505f5f2e9d30 100644 --- a/drivers/net/macvlan.c +++ b/drivers/net/macvlan.c @@ -83,6 +83,11 @@ static inline void macvlan_set_passthru(struct macvlan_port *port) port->flags |= MACVLAN_F_PASSTHRU; } +static inline void macvlan_clear_passthru(struct macvlan_port *port) +{ + port->flags &= ~MACVLAN_F_PASSTHRU; +} + static inline bool macvlan_addr_change(const struct macvlan_port *port) { return port->flags & MACVLAN_F_ADDRCHANGE; @@ -637,7 +642,7 @@ static int macvlan_open(struct net_device *dev) struct net_device *lowerdev = vlan->lowerdev; int err; - if (macvlan_passthru(vlan->port)) { + if (vlan->mode == MACVLAN_MODE_PASSTHRU) { if (!(vlan->flags & MACVLAN_FLAG_NOPROMISC)) { err = dev_set_promiscuity(lowerdev, 1); if (err < 0) @@ -712,7 +717,7 @@ static int macvlan_stop(struct net_device *dev) dev_uc_unsync(lowerdev, dev); dev_mc_unsync(lowerdev, dev); - if (macvlan_passthru(vlan->port)) { + if (vlan->mode == MACVLAN_MODE_PASSTHRU) { if (!(vlan->flags & MACVLAN_FLAG_NOPROMISC)) dev_set_promiscuity(lowerdev, -1); goto hash_del; @@ -792,6 +797,23 @@ static int macvlan_set_mac_address(struct net_device *dev, void *p) return macvlan_sync_address(dev, addr->__data); } +static void macvlan_port_release_mac(struct net_device *dev) +{ + struct macvlan_port *port = macvlan_port_get_rtnl(dev); + + /* If the lower device address has been changed by passthru + * macvlan, put it back. + */ + if (macvlan_passthru(port) && + !ether_addr_equal(port->dev->dev_addr, port->perm_addr)) { + struct sockaddr_storage ss; + + ss.ss_family = port->dev->type; + memcpy(&ss.__data, port->perm_addr, port->dev->addr_len); + dev_set_mac_address(port->dev, &ss, NULL); + } +} + static void macvlan_change_rx_flags(struct net_device *dev, int change) { struct macvlan_dev *vlan = netdev_priv(dev); @@ -977,8 +999,18 @@ static void macvlan_uninit(struct net_device *dev) macvlan_flush_sources(port, vlan); port->count -= 1; - if (!port->count) - macvlan_port_destroy(port->dev); + if (port->count) { + /* In case of remaining source interfaces undo + * passthru-specific properties. + */ + if (vlan->mode == MACVLAN_MODE_PASSTHRU) { + macvlan_port_release_mac(dev); + macvlan_clear_passthru(vlan->port); + } + return; + } + + macvlan_port_destroy(port->dev); } static void macvlan_dev_get_stats64(struct net_device *dev, @@ -1052,7 +1084,7 @@ static int macvlan_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], /* Support unicast filter only on passthru devices. * Multicast filter should be allowed on all devices. */ - if (!macvlan_passthru(vlan->port) && is_unicast_ether_addr(addr)) + if (vlan->mode != MACVLAN_MODE_PASSTHRU && is_unicast_ether_addr(addr)) return -EOPNOTSUPP; if (flags & NLM_F_REPLACE) @@ -1077,7 +1109,7 @@ static int macvlan_fdb_del(struct ndmsg *ndm, struct nlattr *tb[], /* Support unicast filter only on passthru devices. * Multicast filter should be allowed on all devices. */ - if (!macvlan_passthru(vlan->port) && is_unicast_ether_addr(addr)) + if (vlan->mode != MACVLAN_MODE_PASSTHRU && is_unicast_ether_addr(addr)) return -EOPNOTSUPP; if (is_unicast_ether_addr(addr)) @@ -1308,17 +1340,7 @@ static void macvlan_port_destroy(struct net_device *dev) kfree_skb(skb); } - /* If the lower device address has been changed by passthru - * macvlan, put it back. - */ - if (macvlan_passthru(port) && - !ether_addr_equal(port->dev->dev_addr, port->perm_addr)) { - struct sockaddr_storage ss; - - ss.ss_family = port->dev->type; - memcpy(&ss.__data, port->perm_addr, port->dev->addr_len); - dev_set_mac_address(port->dev, &ss, NULL); - } + macvlan_port_release_mac(dev); kfree(port); } @@ -1506,15 +1528,6 @@ int macvlan_common_newlink(struct net_device *dev, } port = macvlan_port_get_rtnl(lowerdev); - /* Only 1 macvlan device can be created in passthru mode */ - if (macvlan_passthru(port)) { - /* The macvlan port must be not created this time, - * still goto destroy_macvlan_port for readability. - */ - err = -EINVAL; - goto destroy_macvlan_port; - } - vlan->lowerdev = lowerdev; vlan->dev = dev; vlan->port = port; @@ -1527,10 +1540,30 @@ int macvlan_common_newlink(struct net_device *dev, if (data && data[IFLA_MACVLAN_FLAGS]) vlan->flags = nla_get_u16(data[IFLA_MACVLAN_FLAGS]); + /* Only 1 macvlan device can be created in passthru mode. There may be + * additional source mode devices but nothing else at the moment. + * + * First check if adding a source mode device to an existing passthru vlan. + */ + if (macvlan_passthru(port) && vlan->mode != MACVLAN_MODE_SOURCE) { + /* The macvlan port must be not created this time, + * still goto destroy_macvlan_port for readability. + */ + err = -EINVAL; + goto destroy_macvlan_port; + } + + /* Now check if adding a passthru device to an existing set of source mode + * devices. + */ if (vlan->mode == MACVLAN_MODE_PASSTHRU) { - if (port->count) { - err = -EINVAL; - goto destroy_macvlan_port; + struct macvlan_dev *p; + + list_for_each_entry(p, &port->vlans, list) { + if (p->mode != MACVLAN_MODE_SOURCE) { + err = -EINVAL; + goto destroy_macvlan_port; + } } macvlan_set_passthru(port); eth_hw_addr_inherit(dev, lowerdev); @@ -1564,7 +1597,11 @@ int macvlan_common_newlink(struct net_device *dev, if (err) goto unregister_netdev; - list_add_tail_rcu(&vlan->list, &port->vlans); + /* macvlan_handle_frame expects the (one and only) passthru device first. */ + if (vlan->mode == MACVLAN_MODE_PASSTHRU) + list_add_rcu(&vlan->list, &port->vlans); + else + list_add_tail_rcu(&vlan->list, &port->vlans); update_port_bc_queue_len(vlan->port); netif_stacked_transfer_operstate(lowerdev, dev); linkwatch_fire_event(dev); @@ -1627,9 +1664,11 @@ static int macvlan_changelink(struct net_device *dev, if (data && data[IFLA_MACVLAN_MODE]) { set_mode = true; mode = nla_get_u32(data[IFLA_MACVLAN_MODE]); - /* Passthrough mode can't be set or cleared dynamically */ - if ((mode == MACVLAN_MODE_PASSTHRU) != - (vlan->mode == MACVLAN_MODE_PASSTHRU)) + /* Passthrough mode can't be set or cleared dynamically, + * regardless of existing source interfaces. Furthermore, source + * interfaces can't switch modes within a passhtrough port. + */ + if (macvlan_passthru(vlan->port) && mode != vlan->mode) return -EINVAL; if (vlan->mode == MACVLAN_MODE_SOURCE && vlan->mode != mode) @@ -1639,7 +1678,7 @@ static int macvlan_changelink(struct net_device *dev, if (data && data[IFLA_MACVLAN_FLAGS]) { __u16 flags = nla_get_u16(data[IFLA_MACVLAN_FLAGS]); bool promisc = (flags ^ vlan->flags) & MACVLAN_FLAG_NOPROMISC; - if (macvlan_passthru(vlan->port) && promisc) { + if (vlan->mode == MACVLAN_MODE_PASSTHRU && promisc) { int err; if (flags & MACVLAN_FLAG_NOPROMISC) -- 2.54.0