net: ocelot: replay switchdev events when joining bridge
[linux-2.6-microblaze.git] / drivers / net / ethernet / mscc / ocelot_net.c
index 12cb686..36f32a4 100644 (file)
@@ -251,6 +251,12 @@ static int ocelot_setup_tc_cls_matchall(struct ocelot_port_private *priv,
                        return -EEXIST;
                }
 
+               if (action->police.rate_pkt_ps) {
+                       NL_SET_ERR_MSG_MOD(extack,
+                                          "QoS offload not support packets per second");
+                       return -EOPNOTSUPP;
+               }
+
                pol.rate = (u32)div_u64(action->police.rate_bytes_ps, 1000) * 8;
                pol.burst = action->police.burst;
 
@@ -1111,77 +1117,213 @@ static int ocelot_port_obj_del(struct net_device *dev,
        return ret;
 }
 
-static int ocelot_netdevice_bridge_join(struct ocelot *ocelot, int port,
-                                       struct net_device *bridge)
+static void ocelot_inherit_brport_flags(struct ocelot *ocelot, int port,
+                                       struct net_device *brport_dev)
+{
+       struct switchdev_brport_flags flags = {0};
+       int flag;
+
+       flags.mask = BR_LEARNING | BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD;
+
+       for_each_set_bit(flag, &flags.mask, 32)
+               if (br_port_flag_is_set(brport_dev, BIT(flag)))
+                       flags.val |= BIT(flag);
+
+       ocelot_port_bridge_flags(ocelot, port, flags);
+}
+
+static void ocelot_clear_brport_flags(struct ocelot *ocelot, int port)
 {
        struct switchdev_brport_flags flags;
-       int err;
 
        flags.mask = BR_LEARNING | BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD;
-       flags.val = flags.mask;
+       flags.val = flags.mask & ~BR_LEARNING;
+
+       ocelot_port_bridge_flags(ocelot, port, flags);
+}
+
+static int ocelot_switchdev_sync(struct ocelot *ocelot, int port,
+                                struct net_device *brport_dev,
+                                struct net_device *bridge_dev,
+                                struct netlink_ext_ack *extack)
+{
+       clock_t ageing_time;
+       u8 stp_state;
+       int err;
 
-       err = ocelot_port_bridge_join(ocelot, port, bridge);
+       ocelot_inherit_brport_flags(ocelot, port, brport_dev);
+
+       stp_state = br_port_get_stp_state(brport_dev);
+       ocelot_bridge_stp_state_set(ocelot, port, stp_state);
+
+       err = ocelot_port_vlan_filtering(ocelot, port,
+                                        br_vlan_enabled(bridge_dev));
        if (err)
                return err;
 
-       ocelot_port_bridge_flags(ocelot, port, flags);
+       ageing_time = br_get_ageing_time(bridge_dev);
+       ocelot_port_attr_ageing_set(ocelot, port, ageing_time);
+
+       err = br_mdb_replay(bridge_dev, brport_dev,
+                           &ocelot_switchdev_blocking_nb, extack);
+       if (err && err != -EOPNOTSUPP)
+               return err;
+
+       err = br_fdb_replay(bridge_dev, brport_dev, &ocelot_switchdev_nb);
+       if (err)
+               return err;
+
+       err = br_vlan_replay(bridge_dev, brport_dev,
+                            &ocelot_switchdev_blocking_nb, extack);
+       if (err && err != -EOPNOTSUPP)
+               return err;
+
+       return 0;
+}
+
+static int ocelot_switchdev_unsync(struct ocelot *ocelot, int port)
+{
+       int err;
+
+       err = ocelot_port_vlan_filtering(ocelot, port, false);
+       if (err)
+               return err;
+
+       ocelot_clear_brport_flags(ocelot, port);
+
+       ocelot_bridge_stp_state_set(ocelot, port, BR_STATE_FORWARDING);
+
+       return 0;
+}
+
+static int ocelot_netdevice_bridge_join(struct net_device *dev,
+                                       struct net_device *brport_dev,
+                                       struct net_device *bridge,
+                                       struct netlink_ext_ack *extack)
+{
+       struct ocelot_port_private *priv = netdev_priv(dev);
+       struct ocelot_port *ocelot_port = &priv->port;
+       struct ocelot *ocelot = ocelot_port->ocelot;
+       int port = priv->chip_port;
+       int err;
+
+       ocelot_port_bridge_join(ocelot, port, bridge);
+
+       err = ocelot_switchdev_sync(ocelot, port, brport_dev, bridge, extack);
+       if (err)
+               goto err_switchdev_sync;
 
        return 0;
+
+err_switchdev_sync:
+       ocelot_port_bridge_leave(ocelot, port, bridge);
+       return err;
 }
 
-static int ocelot_netdevice_bridge_leave(struct ocelot *ocelot, int port,
+static int ocelot_netdevice_bridge_leave(struct net_device *dev,
+                                        struct net_device *brport_dev,
                                         struct net_device *bridge)
 {
-       struct switchdev_brport_flags flags;
+       struct ocelot_port_private *priv = netdev_priv(dev);
+       struct ocelot_port *ocelot_port = &priv->port;
+       struct ocelot *ocelot = ocelot_port->ocelot;
+       int port = priv->chip_port;
        int err;
 
-       flags.mask = BR_LEARNING | BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD;
-       flags.val = flags.mask & ~BR_LEARNING;
+       err = ocelot_switchdev_unsync(ocelot, port);
+       if (err)
+               return err;
 
-       err = ocelot_port_bridge_leave(ocelot, port, bridge);
+       ocelot_port_bridge_leave(ocelot, port, bridge);
 
-       ocelot_port_bridge_flags(ocelot, port, flags);
+       return 0;
+}
 
+static int ocelot_netdevice_lag_join(struct net_device *dev,
+                                    struct net_device *bond,
+                                    struct netdev_lag_upper_info *info,
+                                    struct netlink_ext_ack *extack)
+{
+       struct ocelot_port_private *priv = netdev_priv(dev);
+       struct ocelot_port *ocelot_port = &priv->port;
+       struct ocelot *ocelot = ocelot_port->ocelot;
+       struct net_device *bridge_dev;
+       int port = priv->chip_port;
+       int err;
+
+       err = ocelot_port_lag_join(ocelot, port, bond, info);
+       if (err == -EOPNOTSUPP) {
+               NL_SET_ERR_MSG_MOD(extack, "Offloading not supported");
+               return 0;
+       }
+
+       bridge_dev = netdev_master_upper_dev_get(bond);
+       if (!bridge_dev || !netif_is_bridge_master(bridge_dev))
+               return 0;
+
+       err = ocelot_netdevice_bridge_join(dev, bond, bridge_dev, extack);
+       if (err)
+               goto err_bridge_join;
+
+       return 0;
+
+err_bridge_join:
+       ocelot_port_lag_leave(ocelot, port, bond);
        return err;
 }
 
-static int ocelot_netdevice_changeupper(struct net_device *dev,
-                                       struct netdev_notifier_changeupper_info *info)
+static int ocelot_netdevice_lag_leave(struct net_device *dev,
+                                     struct net_device *bond)
 {
        struct ocelot_port_private *priv = netdev_priv(dev);
        struct ocelot_port *ocelot_port = &priv->port;
        struct ocelot *ocelot = ocelot_port->ocelot;
+       struct net_device *bridge_dev;
        int port = priv->chip_port;
+
+       ocelot_port_lag_leave(ocelot, port, bond);
+
+       bridge_dev = netdev_master_upper_dev_get(bond);
+       if (!bridge_dev || !netif_is_bridge_master(bridge_dev))
+               return 0;
+
+       return ocelot_netdevice_bridge_leave(dev, bond, bridge_dev);
+}
+
+static int ocelot_netdevice_changeupper(struct net_device *dev,
+                                       struct netdev_notifier_changeupper_info *info)
+{
+       struct netlink_ext_ack *extack;
        int err = 0;
 
+       extack = netdev_notifier_info_to_extack(&info->info);
+
        if (netif_is_bridge_master(info->upper_dev)) {
-               if (info->linking) {
-                       err = ocelot_netdevice_bridge_join(ocelot, port,
-                                                          info->upper_dev);
-               } else {
-                       err = ocelot_netdevice_bridge_leave(ocelot, port,
+               if (info->linking)
+                       err = ocelot_netdevice_bridge_join(dev, dev,
+                                                          info->upper_dev,
+                                                          extack);
+               else
+                       err = ocelot_netdevice_bridge_leave(dev, dev,
                                                            info->upper_dev);
-               }
        }
        if (netif_is_lag_master(info->upper_dev)) {
-               if (info->linking) {
-                       err = ocelot_port_lag_join(ocelot, port,
-                                                  info->upper_dev,
-                                                  info->upper_info);
-                       if (err == -EOPNOTSUPP) {
-                               NL_SET_ERR_MSG_MOD(info->info.extack,
-                                                  "Offloading not supported");
-                               err = 0;
-                       }
-               } else {
-                       ocelot_port_lag_leave(ocelot, port,
-                                             info->upper_dev);
-               }
+               if (info->linking)
+                       err = ocelot_netdevice_lag_join(dev, info->upper_dev,
+                                                       info->upper_info, extack);
+               else
+                       ocelot_netdevice_lag_leave(dev, info->upper_dev);
        }
 
        return notifier_from_errno(err);
 }
 
+/* Treat CHANGEUPPER events on an offloaded LAG as individual CHANGEUPPER
+ * events for the lower physical ports of the LAG.
+ * If the LAG upper isn't offloaded, ignore its CHANGEUPPER events.
+ * In case the LAG joined a bridge, notify that we are offloading it and can do
+ * forwarding in hardware towards it.
+ */
 static int
 ocelot_netdevice_lag_changeupper(struct net_device *dev,
                                 struct netdev_notifier_changeupper_info *info)
@@ -1191,6 +1333,12 @@ ocelot_netdevice_lag_changeupper(struct net_device *dev,
        int err = NOTIFY_DONE;
 
        netdev_for_each_lower_dev(dev, lower, iter) {
+               struct ocelot_port_private *priv = netdev_priv(lower);
+               struct ocelot_port *ocelot_port = &priv->port;
+
+               if (ocelot_port->bond != dev)
+                       return NOTIFY_OK;
+
                err = ocelot_netdevice_changeupper(lower, info);
                if (err)
                        return notifier_from_errno(err);