Merge tag 'net-next-5.15' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev...
[linux-2.6-microblaze.git] / net / dsa / port.c
index b927d94..616330a 100644 (file)
@@ -30,7 +30,52 @@ static int dsa_port_notify(const struct dsa_port *dp, unsigned long e, void *v)
        return dsa_tree_notify(dp->ds->dst, e, v);
 }
 
-int dsa_port_set_state(struct dsa_port *dp, u8 state)
+static void dsa_port_notify_bridge_fdb_flush(const struct dsa_port *dp)
+{
+       struct net_device *brport_dev = dsa_port_to_bridge_port(dp);
+       struct switchdev_notifier_fdb_info info = {
+               /* flush all VLANs */
+               .vid = 0,
+       };
+
+       /* When the port becomes standalone it has already left the bridge.
+        * Don't notify the bridge in that case.
+        */
+       if (!brport_dev)
+               return;
+
+       call_switchdev_notifiers(SWITCHDEV_FDB_FLUSH_TO_BRIDGE,
+                                brport_dev, &info.info, NULL);
+}
+
+static void dsa_port_fast_age(const struct dsa_port *dp)
+{
+       struct dsa_switch *ds = dp->ds;
+
+       if (!ds->ops->port_fast_age)
+               return;
+
+       ds->ops->port_fast_age(ds, dp->index);
+
+       dsa_port_notify_bridge_fdb_flush(dp);
+}
+
+static bool dsa_port_can_configure_learning(struct dsa_port *dp)
+{
+       struct switchdev_brport_flags flags = {
+               .mask = BR_LEARNING,
+       };
+       struct dsa_switch *ds = dp->ds;
+       int err;
+
+       if (!ds->ops->port_bridge_flags || !ds->ops->port_pre_bridge_flags)
+               return false;
+
+       err = ds->ops->port_pre_bridge_flags(ds, dp->index, flags, NULL);
+       return !err;
+}
+
+int dsa_port_set_state(struct dsa_port *dp, u8 state, bool do_fast_age)
 {
        struct dsa_switch *ds = dp->ds;
        int port = dp->index;
@@ -40,10 +85,14 @@ int dsa_port_set_state(struct dsa_port *dp, u8 state)
 
        ds->ops->port_stp_state_set(ds, port, state);
 
-       if (ds->ops->port_fast_age) {
+       if (!dsa_port_can_configure_learning(dp) ||
+           (do_fast_age && dp->learning)) {
                /* Fast age FDB entries or flush appropriate forwarding database
                 * for the given port, if we are moving it from Learning or
                 * Forwarding state, to Disabled or Blocking or Listening state.
+                * Ports that were standalone before the STP state change don't
+                * need to fast age the FDB, since address learning is off in
+                * standalone mode.
                 */
 
                if ((dp->stp_state == BR_STATE_LEARNING ||
@@ -51,7 +100,7 @@ int dsa_port_set_state(struct dsa_port *dp, u8 state)
                    (state == BR_STATE_DISABLED ||
                     state == BR_STATE_BLOCKING ||
                     state == BR_STATE_LISTENING))
-                       ds->ops->port_fast_age(ds, port);
+                       dsa_port_fast_age(dp);
        }
 
        dp->stp_state = state;
@@ -59,11 +108,12 @@ int dsa_port_set_state(struct dsa_port *dp, u8 state)
        return 0;
 }
 
-static void dsa_port_set_state_now(struct dsa_port *dp, u8 state)
+static void dsa_port_set_state_now(struct dsa_port *dp, u8 state,
+                                  bool do_fast_age)
 {
        int err;
 
-       err = dsa_port_set_state(dp, state);
+       err = dsa_port_set_state(dp, state, do_fast_age);
        if (err)
                pr_err("DSA: failed to set STP state %u (%d)\n", state, err);
 }
@@ -81,7 +131,7 @@ int dsa_port_enable_rt(struct dsa_port *dp, struct phy_device *phy)
        }
 
        if (!dp->bridge_dev)
-               dsa_port_set_state_now(dp, BR_STATE_FORWARDING);
+               dsa_port_set_state_now(dp, BR_STATE_FORWARDING, false);
 
        if (dp->pl)
                phylink_start(dp->pl);
@@ -109,7 +159,7 @@ void dsa_port_disable_rt(struct dsa_port *dp)
                phylink_stop(dp->pl);
 
        if (!dp->bridge_dev)
-               dsa_port_set_state_now(dp, BR_STATE_DISABLED);
+               dsa_port_set_state_now(dp, BR_STATE_DISABLED, false);
 
        if (ds->ops->port_disable)
                ds->ops->port_disable(ds, port);
@@ -178,7 +228,7 @@ static int dsa_port_switchdev_sync_attrs(struct dsa_port *dp,
        if (err)
                return err;
 
-       err = dsa_port_set_state(dp, br_port_get_stp_state(brport_dev));
+       err = dsa_port_set_state(dp, br_port_get_stp_state(brport_dev), false);
        if (err && err != -EOPNOTSUPP)
                return err;
 
@@ -186,10 +236,6 @@ static int dsa_port_switchdev_sync_attrs(struct dsa_port *dp,
        if (err && err != -EOPNOTSUPP)
                return err;
 
-       err = dsa_port_mrouter(dp->cpu_dp, br_multicast_router(br), extack);
-       if (err && err != -EOPNOTSUPP)
-               return err;
-
        err = dsa_port_ageing_time(dp, br_get_ageing_time(br));
        if (err && err != -EOPNOTSUPP)
                return err;
@@ -215,42 +261,18 @@ static void dsa_port_switchdev_unsync_attrs(struct dsa_port *dp)
        /* Port left the bridge, put in BR_STATE_DISABLED by the bridge layer,
         * so allow it to be in BR_STATE_FORWARDING to be kept functional
         */
-       dsa_port_set_state_now(dp, BR_STATE_FORWARDING);
+       dsa_port_set_state_now(dp, BR_STATE_FORWARDING, true);
 
        /* VLAN filtering is handled by dsa_switch_bridge_leave */
 
-       /* Some drivers treat the notification for having a local multicast
-        * router by allowing multicast to be flooded to the CPU, so we should
-        * allow this in standalone mode too.
-        */
-       dsa_port_mrouter(dp->cpu_dp, true, NULL);
-
        /* Ageing time may be global to the switch chip, so don't change it
         * here because we have no good reason (or value) to change it to.
         */
 }
 
-static int dsa_tree_find_bridge_num(struct dsa_switch_tree *dst,
-                                   struct net_device *bridge_dev)
-{
-       struct dsa_port *dp;
-
-       /* When preparing the offload for a port, it will have a valid
-        * dp->bridge_dev pointer but a not yet valid dp->bridge_num.
-        * However there might be other ports having the same dp->bridge_dev
-        * and a valid dp->bridge_num, so just ignore this port.
-        */
-       list_for_each_entry(dp, &dst->ports, list)
-               if (dp->bridge_dev == bridge_dev && dp->bridge_num != -1)
-                       return dp->bridge_num;
-
-       return -1;
-}
-
 static void dsa_port_bridge_tx_fwd_unoffload(struct dsa_port *dp,
                                             struct net_device *bridge_dev)
 {
-       struct dsa_switch_tree *dst = dp->ds->dst;
        int bridge_num = dp->bridge_num;
        struct dsa_switch *ds = dp->ds;
 
@@ -260,11 +282,7 @@ static void dsa_port_bridge_tx_fwd_unoffload(struct dsa_port *dp,
 
        dp->bridge_num = -1;
 
-       /* Check if the bridge is still in use, otherwise it is time
-        * to clean it up so we can reuse this bridge_num later.
-        */
-       if (!dsa_tree_find_bridge_num(dst, bridge_dev))
-               clear_bit(bridge_num, &dst->fwd_offloading_bridges);
+       dsa_bridge_num_put(bridge_dev, bridge_num);
 
        /* Notify the chips only once the offload has been deactivated, so
         * that they can update their configuration accordingly.
@@ -276,23 +294,16 @@ static void dsa_port_bridge_tx_fwd_unoffload(struct dsa_port *dp,
 static bool dsa_port_bridge_tx_fwd_offload(struct dsa_port *dp,
                                           struct net_device *bridge_dev)
 {
-       struct dsa_switch_tree *dst = dp->ds->dst;
        struct dsa_switch *ds = dp->ds;
        int bridge_num, err;
 
        if (!ds->ops->port_bridge_tx_fwd_offload)
                return false;
 
-       bridge_num = dsa_tree_find_bridge_num(dst, bridge_dev);
-       if (bridge_num < 0) {
-               /* First port that offloads TX forwarding for this bridge */
-               bridge_num = find_first_zero_bit(&dst->fwd_offloading_bridges,
-                                                DSA_MAX_NUM_OFFLOADING_BRIDGES);
-               if (bridge_num >= ds->num_fwd_offloading_bridges)
-                       return false;
-
-               set_bit(bridge_num, &dst->fwd_offloading_bridges);
-       }
+       bridge_num = dsa_bridge_num_get(bridge_dev,
+                                       ds->num_fwd_offloading_bridges);
+       if (bridge_num < 0)
+               return false;
 
        dp->bridge_num = bridge_num;
 
@@ -362,6 +373,10 @@ void dsa_port_pre_bridge_leave(struct dsa_port *dp, struct net_device *br)
 {
        struct net_device *brport_dev = dsa_port_to_bridge_port(dp);
 
+       /* Don't try to unoffload something that is not offloaded */
+       if (!brport_dev)
+               return;
+
        switchdev_bridge_port_unoffload(brport_dev, dp,
                                        &dsa_slave_switchdev_notifier,
                                        &dsa_slave_switchdev_blocking_notifier);
@@ -386,7 +401,9 @@ void dsa_port_bridge_leave(struct dsa_port *dp, struct net_device *br)
 
        err = dsa_broadcast(DSA_NOTIFIER_BRIDGE_LEAVE, &info);
        if (err)
-               pr_err("DSA: failed to notify DSA_NOTIFIER_BRIDGE_LEAVE\n");
+               dev_err(dp->ds->dev,
+                       "port %d failed to notify DSA_NOTIFIER_BRIDGE_LEAVE: %pe\n",
+                       dp->index, ERR_PTR(err));
 
        dsa_port_switchdev_unsync_attrs(dp);
 }
@@ -485,8 +502,9 @@ void dsa_port_lag_leave(struct dsa_port *dp, struct net_device *lag)
 
        err = dsa_port_notify(dp, DSA_NOTIFIER_LAG_LEAVE, &info);
        if (err)
-               pr_err("DSA: failed to notify DSA_NOTIFIER_LAG_LEAVE: %d\n",
-                      err);
+               dev_err(dp->ds->dev,
+                       "port %d failed to notify DSA_NOTIFIER_LAG_LEAVE: %pe\n",
+                       dp->index, ERR_PTR(err));
 
        dsa_lag_unmap(dp->ds->dst, lag);
 }
@@ -562,6 +580,7 @@ static bool dsa_port_can_apply_vlan_filtering(struct dsa_port *dp,
 int dsa_port_vlan_filtering(struct dsa_port *dp, bool vlan_filtering,
                            struct netlink_ext_ack *extack)
 {
+       bool old_vlan_filtering = dsa_port_is_vlan_filtering(dp);
        struct dsa_switch *ds = dp->ds;
        bool apply;
        int err;
@@ -587,12 +606,49 @@ int dsa_port_vlan_filtering(struct dsa_port *dp, bool vlan_filtering,
        if (err)
                return err;
 
-       if (ds->vlan_filtering_is_global)
+       if (ds->vlan_filtering_is_global) {
+               int port;
+
                ds->vlan_filtering = vlan_filtering;
-       else
+
+               for (port = 0; port < ds->num_ports; port++) {
+                       struct net_device *slave;
+
+                       if (!dsa_is_user_port(ds, port))
+                               continue;
+
+                       /* We might be called in the unbind path, so not
+                        * all slave devices might still be registered.
+                        */
+                       slave = dsa_to_port(ds, port)->slave;
+                       if (!slave)
+                               continue;
+
+                       err = dsa_slave_manage_vlan_filtering(slave,
+                                                             vlan_filtering);
+                       if (err)
+                               goto restore;
+               }
+       } else {
                dp->vlan_filtering = vlan_filtering;
 
+               err = dsa_slave_manage_vlan_filtering(dp->slave,
+                                                     vlan_filtering);
+               if (err)
+                       goto restore;
+       }
+
        return 0;
+
+restore:
+       ds->ops->port_vlan_filtering(ds, dp->index, old_vlan_filtering, NULL);
+
+       if (ds->vlan_filtering_is_global)
+               ds->vlan_filtering = old_vlan_filtering;
+       else
+               dp->vlan_filtering = old_vlan_filtering;
+
+       return err;
 }
 
 /* This enforces legacy behavior for switch drivers which assume they can't
@@ -639,27 +695,35 @@ int dsa_port_pre_bridge_flags(const struct dsa_port *dp,
        return ds->ops->port_pre_bridge_flags(ds, dp->index, flags, extack);
 }
 
-int dsa_port_bridge_flags(const struct dsa_port *dp,
+int dsa_port_bridge_flags(struct dsa_port *dp,
                          struct switchdev_brport_flags flags,
                          struct netlink_ext_ack *extack)
 {
        struct dsa_switch *ds = dp->ds;
+       int err;
 
        if (!ds->ops->port_bridge_flags)
                return -EOPNOTSUPP;
 
-       return ds->ops->port_bridge_flags(ds, dp->index, flags, extack);
-}
+       err = ds->ops->port_bridge_flags(ds, dp->index, flags, extack);
+       if (err)
+               return err;
 
-int dsa_port_mrouter(struct dsa_port *dp, bool mrouter,
-                    struct netlink_ext_ack *extack)
-{
-       struct dsa_switch *ds = dp->ds;
+       if (flags.mask & BR_LEARNING) {
+               bool learning = flags.val & BR_LEARNING;
 
-       if (!ds->ops->port_set_mrouter)
-               return -EOPNOTSUPP;
+               if (learning == dp->learning)
+                       return 0;
 
-       return ds->ops->port_set_mrouter(ds, dp->index, mrouter, extack);
+               if ((dp->learning && !learning) &&
+                   (dp->stp_state == BR_STATE_LEARNING ||
+                    dp->stp_state == BR_STATE_FORWARDING))
+                       dsa_port_fast_age(dp);
+
+               dp->learning = learning;
+       }
+
+       return 0;
 }
 
 int dsa_port_mtu_change(struct dsa_port *dp, int new_mtu,
@@ -1258,10 +1322,12 @@ void dsa_port_hsr_leave(struct dsa_port *dp, struct net_device *hsr)
 
        err = dsa_port_notify(dp, DSA_NOTIFIER_HSR_LEAVE, &info);
        if (err)
-               pr_err("DSA: failed to notify DSA_NOTIFIER_HSR_LEAVE\n");
+               dev_err(dp->ds->dev,
+                       "port %d failed to notify DSA_NOTIFIER_HSR_LEAVE: %pe\n",
+                       dp->index, ERR_PTR(err));
 }
 
-int dsa_port_tag_8021q_vlan_add(struct dsa_port *dp, u16 vid)
+int dsa_port_tag_8021q_vlan_add(struct dsa_port *dp, u16 vid, bool broadcast)
 {
        struct dsa_notifier_tag_8021q_vlan_info info = {
                .tree_index = dp->ds->dst->index,
@@ -1270,10 +1336,13 @@ int dsa_port_tag_8021q_vlan_add(struct dsa_port *dp, u16 vid)
                .vid = vid,
        };
 
-       return dsa_broadcast(DSA_NOTIFIER_TAG_8021Q_VLAN_ADD, &info);
+       if (broadcast)
+               return dsa_broadcast(DSA_NOTIFIER_TAG_8021Q_VLAN_ADD, &info);
+
+       return dsa_port_notify(dp, DSA_NOTIFIER_TAG_8021Q_VLAN_ADD, &info);
 }
 
-void dsa_port_tag_8021q_vlan_del(struct dsa_port *dp, u16 vid)
+void dsa_port_tag_8021q_vlan_del(struct dsa_port *dp, u16 vid, bool broadcast)
 {
        struct dsa_notifier_tag_8021q_vlan_info info = {
                .tree_index = dp->ds->dst->index,
@@ -1283,8 +1352,12 @@ void dsa_port_tag_8021q_vlan_del(struct dsa_port *dp, u16 vid)
        };
        int err;
 
-       err = dsa_broadcast(DSA_NOTIFIER_TAG_8021Q_VLAN_DEL, &info);
+       if (broadcast)
+               err = dsa_broadcast(DSA_NOTIFIER_TAG_8021Q_VLAN_DEL, &info);
+       else
+               err = dsa_port_notify(dp, DSA_NOTIFIER_TAG_8021Q_VLAN_DEL, &info);
        if (err)
-               pr_err("DSA: failed to notify tag_8021q VLAN deletion: %pe\n",
-                      ERR_PTR(err));
+               dev_err(dp->ds->dev,
+                       "port %d failed to notify tag_8021q VLAN %d deletion: %pe\n",
+                       dp->index, vid, ERR_PTR(err));
 }