• Vladimir Oltean's avatar
    net: switchdev: introduce switchdev_handle_port_obj_{add,del} for foreign interfaces · c4076cdd
    Vladimir Oltean authored
    The switchdev_handle_port_obj_add() helper is good for replicating a
    port object on the lower interfaces of @dev, if that object was emitted
    on a bridge, or on a bridge port that is a LAG.
    
    However, drivers that use this helper limit themselves to a box from
    which they can no longer intercept port objects notified on neighbor
    ports ("foreign interfaces").
    
    One such driver is DSA, where software bridging with foreign interfaces
    such as standalone NICs or Wi-Fi APs is an important use case. There, a
    VLAN installed on a neighbor bridge port roughly corresponds to a
    forwarding VLAN installed on the DSA switch's CPU port.
    
    To support this use case while also making use of the benefits of the
    switchdev_handle_* replication helper for port objects, introduce a new
    variant of these functions that crawls through the neighbor ports of
    @dev, in search of potentially compatible switchdev ports that are
    interested in the event.
    
    The strategy is identical to switchdev_handle_fdb_event_to_device():
    if @dev wasn't a switchdev interface, then go one step upper, and
    recursively call this function on the bridge that this port belongs to.
    At the next recursion step, __switchdev_handle_port_obj_add() will
    iterate through the bridge's lower interfaces. Among those, some will be
    switchdev interfaces, and one will be the original @dev that we came
    from. To prevent infinite recursion, we must suppress reentry into the
    original @dev, and just call the @add_cb for the switchdev_interfaces.
    
    It looks like this:
    
                    br0
                   / | \
                  /  |  \
                 /   |   \
               swp0 swp1 eth0
    
    1. __switchdev_handle_port_obj_add(eth0)
       -> check_cb(eth0) returns false
       -> eth0 has no lower interfaces
       -> eth0's bridge is br0
       -> switchdev_lower_dev_find(br0, check_cb, foreign_dev_check_cb))
          finds br0
    
    2. __switchdev_handle_port_obj_add(br0)
       -> check_cb(br0) returns false
       -> netdev_for_each_lower_dev
          -> check_cb(swp0) returns true, so we don't skip this interface
    
    3. __switchdev_handle_port_obj_add(swp0)
       -> check_cb(swp0) returns true, so we call add_cb(swp0)
    
    (back to netdev_for_each_lower_dev from 2)
          -> check_cb(swp1) returns true, so we don't skip this interface
    
    4. __switchdev_handle_port_obj_add(swp1)
       -> check_cb(swp1) returns true, so we call add_cb(swp1)
    
    (back to netdev_for_each_lower_dev from 2)
          -> check_cb(eth0) returns false, so we skip this interface to
             avoid infinite recursion
    
    Note: eth0 could have been a LAG, and we don't want to suppress the
    recursion through its lowers if those exist, so when check_cb() returns
    false, we still call switchdev_lower_dev_find() to estimate whether
    there's anything worth a recursion beneath that LAG. Using check_cb()
    and foreign_dev_check_cb(), switchdev_lower_dev_find() not only figures
    out whether the lowers of the LAG are switchdev, but also whether they
    actively offload the LAG or not (whether the LAG is "foreign" to the
    switchdev interface or not).
    
    The port_obj_info->orig_dev is preserved across recursive calls, so
    switchdev drivers still know on which device was this notification
    originally emitted.
    Signed-off-by: default avatarVladimir Oltean <vladimir.oltean@nxp.com>
    Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
    c4076cdd
switchdev.c 25.6 KB