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 28b45b7..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);
@@ -167,8 +217,8 @@ static void dsa_port_clear_brport_flags(struct dsa_port *dp)
        }
 }
 
-static int dsa_port_switchdev_sync(struct dsa_port *dp,
-                                  struct netlink_ext_ack *extack)
+static int dsa_port_switchdev_sync_attrs(struct dsa_port *dp,
+                                        struct netlink_ext_ack *extack)
 {
        struct net_device *brport_dev = dsa_port_to_bridge_port(dp);
        struct net_device *br = dp->bridge_dev;
@@ -178,7 +228,7 @@ static int dsa_port_switchdev_sync(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,67 +236,10 @@ static int dsa_port_switchdev_sync(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;
 
-       err = br_mdb_replay(br, brport_dev, dp, true,
-                           &dsa_slave_switchdev_blocking_notifier, extack);
-       if (err && err != -EOPNOTSUPP)
-               return err;
-
-       /* Forwarding and termination FDB entries on the port */
-       err = br_fdb_replay(br, brport_dev, dp, true,
-                           &dsa_slave_switchdev_notifier);
-       if (err && err != -EOPNOTSUPP)
-               return err;
-
-       /* Termination FDB entries on the bridge itself */
-       err = br_fdb_replay(br, br, dp, true, &dsa_slave_switchdev_notifier);
-       if (err && err != -EOPNOTSUPP)
-               return err;
-
-       err = br_vlan_replay(br, brport_dev, dp, true,
-                            &dsa_slave_switchdev_blocking_notifier, extack);
-       if (err && err != -EOPNOTSUPP)
-               return err;
-
-       return 0;
-}
-
-static int dsa_port_switchdev_unsync_objs(struct dsa_port *dp,
-                                         struct net_device *br,
-                                         struct netlink_ext_ack *extack)
-{
-       struct net_device *brport_dev = dsa_port_to_bridge_port(dp);
-       int err;
-
-       /* Delete the switchdev objects left on this port */
-       err = br_mdb_replay(br, brport_dev, dp, false,
-                           &dsa_slave_switchdev_blocking_notifier, extack);
-       if (err && err != -EOPNOTSUPP)
-               return err;
-
-       /* Forwarding and termination FDB entries on the port */
-       err = br_fdb_replay(br, brport_dev, dp, false,
-                           &dsa_slave_switchdev_notifier);
-       if (err && err != -EOPNOTSUPP)
-               return err;
-
-       /* Termination FDB entries on the bridge itself */
-       err = br_fdb_replay(br, br, dp, false, &dsa_slave_switchdev_notifier);
-       if (err && err != -EOPNOTSUPP)
-               return err;
-
-       err = br_vlan_replay(br, brport_dev, dp, false,
-                            &dsa_slave_switchdev_blocking_notifier, extack);
-       if (err && err != -EOPNOTSUPP)
-               return err;
-
        return 0;
 }
 
@@ -268,21 +261,63 @@ 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 void dsa_port_bridge_tx_fwd_unoffload(struct dsa_port *dp,
+                                            struct net_device *bridge_dev)
+{
+       int bridge_num = dp->bridge_num;
+       struct dsa_switch *ds = dp->ds;
+
+       /* No bridge TX forwarding offload => do nothing */
+       if (!ds->ops->port_bridge_tx_fwd_unoffload || dp->bridge_num == -1)
+               return;
+
+       dp->bridge_num = -1;
+
+       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.
+        */
+       ds->ops->port_bridge_tx_fwd_unoffload(ds, dp->index, bridge_dev,
+                                             bridge_num);
+}
+
+static bool dsa_port_bridge_tx_fwd_offload(struct dsa_port *dp,
+                                          struct net_device *bridge_dev)
+{
+       struct dsa_switch *ds = dp->ds;
+       int bridge_num, err;
+
+       if (!ds->ops->port_bridge_tx_fwd_offload)
+               return false;
+
+       bridge_num = dsa_bridge_num_get(bridge_dev,
+                                       ds->num_fwd_offloading_bridges);
+       if (bridge_num < 0)
+               return false;
+
+       dp->bridge_num = bridge_num;
+
+       /* Notify the driver */
+       err = ds->ops->port_bridge_tx_fwd_offload(ds, dp->index, bridge_dev,
+                                                 bridge_num);
+       if (err) {
+               dsa_port_bridge_tx_fwd_unoffload(dp, bridge_dev);
+               return false;
+       }
+
+       return true;
+}
+
 int dsa_port_bridge_join(struct dsa_port *dp, struct net_device *br,
                         struct netlink_ext_ack *extack)
 {
@@ -292,6 +327,9 @@ int dsa_port_bridge_join(struct dsa_port *dp, struct net_device *br,
                .port = dp->index,
                .br = br,
        };
+       struct net_device *dev = dp->slave;
+       struct net_device *brport_dev;
+       bool tx_fwd_offload;
        int err;
 
        /* Here the interface is already bridged. Reflect the current
@@ -299,16 +337,31 @@ int dsa_port_bridge_join(struct dsa_port *dp, struct net_device *br,
         */
        dp->bridge_dev = br;
 
+       brport_dev = dsa_port_to_bridge_port(dp);
+
        err = dsa_broadcast(DSA_NOTIFIER_BRIDGE_JOIN, &info);
        if (err)
                goto out_rollback;
 
-       err = dsa_port_switchdev_sync(dp, extack);
+       tx_fwd_offload = dsa_port_bridge_tx_fwd_offload(dp, br);
+
+       err = switchdev_bridge_port_offload(brport_dev, dev, dp,
+                                           &dsa_slave_switchdev_notifier,
+                                           &dsa_slave_switchdev_blocking_notifier,
+                                           tx_fwd_offload, extack);
        if (err)
                goto out_rollback_unbridge;
 
+       err = dsa_port_switchdev_sync_attrs(dp, extack);
+       if (err)
+               goto out_rollback_unoffload;
+
        return 0;
 
+out_rollback_unoffload:
+       switchdev_bridge_port_unoffload(brport_dev, dp,
+                                       &dsa_slave_switchdev_notifier,
+                                       &dsa_slave_switchdev_blocking_notifier);
 out_rollback_unbridge:
        dsa_broadcast(DSA_NOTIFIER_BRIDGE_LEAVE, &info);
 out_rollback:
@@ -316,10 +369,17 @@ out_rollback:
        return err;
 }
 
-int dsa_port_pre_bridge_leave(struct dsa_port *dp, struct net_device *br,
-                             struct netlink_ext_ack *extack)
+void dsa_port_pre_bridge_leave(struct dsa_port *dp, struct net_device *br)
 {
-       return dsa_port_switchdev_unsync_objs(dp, br, extack);
+       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);
 }
 
 void dsa_port_bridge_leave(struct dsa_port *dp, struct net_device *br)
@@ -337,9 +397,13 @@ void dsa_port_bridge_leave(struct dsa_port *dp, struct net_device *br)
         */
        dp->bridge_dev = NULL;
 
+       dsa_port_bridge_tx_fwd_unoffload(dp, 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);
 }
@@ -409,13 +473,10 @@ err_lag_join:
        return err;
 }
 
-int dsa_port_pre_lag_leave(struct dsa_port *dp, struct net_device *lag,
-                          struct netlink_ext_ack *extack)
+void dsa_port_pre_lag_leave(struct dsa_port *dp, struct net_device *lag)
 {
        if (dp->bridge_dev)
-               return dsa_port_pre_bridge_leave(dp, dp->bridge_dev, extack);
-
-       return 0;
+               dsa_port_pre_bridge_leave(dp, dp->bridge_dev);
 }
 
 void dsa_port_lag_leave(struct dsa_port *dp, struct net_device *lag)
@@ -441,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);
 }
@@ -518,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;
@@ -543,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
@@ -595,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;
+
+               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 ds->ops->port_set_mrouter(ds, dp->index, mrouter, extack);
+       return 0;
 }
 
 int dsa_port_mtu_change(struct dsa_port *dp, int new_mtu,
@@ -844,7 +952,6 @@ int dsa_port_mrp_del_ring_role(const struct dsa_port *dp,
 void dsa_port_set_tag_protocol(struct dsa_port *cpu_dp,
                               const struct dsa_device_ops *tag_ops)
 {
-       cpu_dp->filter = tag_ops->filter;
        cpu_dp->rcv = tag_ops->rcv;
        cpu_dp->tag_ops = tag_ops;
 }
@@ -1215,5 +1322,42 @@ 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, bool broadcast)
+{
+       struct dsa_notifier_tag_8021q_vlan_info info = {
+               .tree_index = dp->ds->dst->index,
+               .sw_index = dp->ds->index,
+               .port = dp->index,
+               .vid = vid,
+       };
+
+       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, bool broadcast)
+{
+       struct dsa_notifier_tag_8021q_vlan_info info = {
+               .tree_index = dp->ds->dst->index,
+               .sw_index = dp->ds->index,
+               .port = dp->index,
+               .vid = vid,
+       };
+       int err;
+
+       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)
+               dev_err(dp->ds->dev,
+                       "port %d failed to notify tag_8021q VLAN %d deletion: %pe\n",
+                       dp->index, vid, ERR_PTR(err));
 }