ethtool: fix incorrect datatype in set_eee ops
[linux-2.6-microblaze.git] / net / dsa / dsa2.c
index a47e0f9..3c3e56a 100644 (file)
 static DEFINE_MUTEX(dsa2_mutex);
 LIST_HEAD(dsa_tree_list);
 
+/**
+ * dsa_tree_notify - Execute code for all switches in a DSA switch tree.
+ * @dst: collection of struct dsa_switch devices to notify.
+ * @e: event, must be of type DSA_NOTIFIER_*
+ * @v: event-specific value.
+ *
+ * Given a struct dsa_switch_tree, this can be used to run a function once for
+ * each member DSA switch. The other alternative of traversing the tree is only
+ * through its ports list, which does not uniquely list the switches.
+ */
+int dsa_tree_notify(struct dsa_switch_tree *dst, unsigned long e, void *v)
+{
+       struct raw_notifier_head *nh = &dst->nh;
+       int err;
+
+       err = raw_notifier_call_chain(nh, e, v);
+
+       return notifier_to_errno(err);
+}
+
+/**
+ * dsa_broadcast - Notify all DSA trees in the system.
+ * @e: event, must be of type DSA_NOTIFIER_*
+ * @v: event-specific value.
+ *
+ * Can be used to notify the switching fabric of events such as cross-chip
+ * bridging between disjoint trees (such as islands of tagger-compatible
+ * switches bridged by an incompatible middle switch).
+ */
+int dsa_broadcast(unsigned long e, void *v)
+{
+       struct dsa_switch_tree *dst;
+       int err = 0;
+
+       list_for_each_entry(dst, &dsa_tree_list, list) {
+               err = dsa_tree_notify(dst, e, v);
+               if (err)
+                       break;
+       }
+
+       return err;
+}
+
+/**
+ * dsa_lag_map() - Map LAG netdev to a linear LAG ID
+ * @dst: Tree in which to record the mapping.
+ * @lag: Netdev that is to be mapped to an ID.
+ *
+ * dsa_lag_id/dsa_lag_dev can then be used to translate between the
+ * two spaces. The size of the mapping space is determined by the
+ * driver by setting ds->num_lag_ids. It is perfectly legal to leave
+ * it unset if it is not needed, in which case these functions become
+ * no-ops.
+ */
+void dsa_lag_map(struct dsa_switch_tree *dst, struct net_device *lag)
+{
+       unsigned int id;
+
+       if (dsa_lag_id(dst, lag) >= 0)
+               /* Already mapped */
+               return;
+
+       for (id = 0; id < dst->lags_len; id++) {
+               if (!dsa_lag_dev(dst, id)) {
+                       dst->lags[id] = lag;
+                       return;
+               }
+       }
+
+       /* No IDs left, which is OK. Some drivers do not need it. The
+        * ones that do, e.g. mv88e6xxx, will discover that dsa_lag_id
+        * returns an error for this device when joining the LAG. The
+        * driver can then return -EOPNOTSUPP back to DSA, which will
+        * fall back to a software LAG.
+        */
+}
+
+/**
+ * dsa_lag_unmap() - Remove a LAG ID mapping
+ * @dst: Tree in which the mapping is recorded.
+ * @lag: Netdev that was mapped.
+ *
+ * As there may be multiple users of the mapping, it is only removed
+ * if there are no other references to it.
+ */
+void dsa_lag_unmap(struct dsa_switch_tree *dst, struct net_device *lag)
+{
+       struct dsa_port *dp;
+       unsigned int id;
+
+       dsa_lag_foreach_port(dp, dst, lag)
+               /* There are remaining users of this mapping */
+               return;
+
+       dsa_lags_foreach_id(id, dst) {
+               if (dsa_lag_dev(dst, id) == lag) {
+                       dst->lags[id] = NULL;
+                       break;
+               }
+       }
+}
+
 struct dsa_switch *dsa_switch_find(int tree_index, int sw_index)
 {
        struct dsa_switch_tree *dst;
@@ -77,6 +179,8 @@ static struct dsa_switch_tree *dsa_tree_alloc(int index)
 
 static void dsa_tree_free(struct dsa_switch_tree *dst)
 {
+       if (dst->tag_ops)
+               dsa_tag_driver_put(dst->tag_ops);
        list_del(&dst->list);
        kfree(dst);
 }
@@ -365,7 +469,6 @@ static void dsa_port_teardown(struct dsa_port *dp)
                break;
        case DSA_PORT_TYPE_CPU:
                dsa_port_disable(dp);
-               dsa_tag_driver_put(dp->tag_ops);
                dsa_port_link_unregister_of(dp);
                break;
        case DSA_PORT_TYPE_DSA:
@@ -404,8 +507,165 @@ static int dsa_devlink_info_get(struct devlink *dl,
        return -EOPNOTSUPP;
 }
 
+static int dsa_devlink_sb_pool_get(struct devlink *dl,
+                                  unsigned int sb_index, u16 pool_index,
+                                  struct devlink_sb_pool_info *pool_info)
+{
+       struct dsa_switch *ds = dsa_devlink_to_ds(dl);
+
+       if (!ds->ops->devlink_sb_pool_get)
+               return -EOPNOTSUPP;
+
+       return ds->ops->devlink_sb_pool_get(ds, sb_index, pool_index,
+                                           pool_info);
+}
+
+static int dsa_devlink_sb_pool_set(struct devlink *dl, unsigned int sb_index,
+                                  u16 pool_index, u32 size,
+                                  enum devlink_sb_threshold_type threshold_type,
+                                  struct netlink_ext_ack *extack)
+{
+       struct dsa_switch *ds = dsa_devlink_to_ds(dl);
+
+       if (!ds->ops->devlink_sb_pool_set)
+               return -EOPNOTSUPP;
+
+       return ds->ops->devlink_sb_pool_set(ds, sb_index, pool_index, size,
+                                           threshold_type, extack);
+}
+
+static int dsa_devlink_sb_port_pool_get(struct devlink_port *dlp,
+                                       unsigned int sb_index, u16 pool_index,
+                                       u32 *p_threshold)
+{
+       struct dsa_switch *ds = dsa_devlink_port_to_ds(dlp);
+       int port = dsa_devlink_port_to_port(dlp);
+
+       if (!ds->ops->devlink_sb_port_pool_get)
+               return -EOPNOTSUPP;
+
+       return ds->ops->devlink_sb_port_pool_get(ds, port, sb_index,
+                                                pool_index, p_threshold);
+}
+
+static int dsa_devlink_sb_port_pool_set(struct devlink_port *dlp,
+                                       unsigned int sb_index, u16 pool_index,
+                                       u32 threshold,
+                                       struct netlink_ext_ack *extack)
+{
+       struct dsa_switch *ds = dsa_devlink_port_to_ds(dlp);
+       int port = dsa_devlink_port_to_port(dlp);
+
+       if (!ds->ops->devlink_sb_port_pool_set)
+               return -EOPNOTSUPP;
+
+       return ds->ops->devlink_sb_port_pool_set(ds, port, sb_index,
+                                                pool_index, threshold, extack);
+}
+
+static int
+dsa_devlink_sb_tc_pool_bind_get(struct devlink_port *dlp,
+                               unsigned int sb_index, u16 tc_index,
+                               enum devlink_sb_pool_type pool_type,
+                               u16 *p_pool_index, u32 *p_threshold)
+{
+       struct dsa_switch *ds = dsa_devlink_port_to_ds(dlp);
+       int port = dsa_devlink_port_to_port(dlp);
+
+       if (!ds->ops->devlink_sb_tc_pool_bind_get)
+               return -EOPNOTSUPP;
+
+       return ds->ops->devlink_sb_tc_pool_bind_get(ds, port, sb_index,
+                                                   tc_index, pool_type,
+                                                   p_pool_index, p_threshold);
+}
+
+static int
+dsa_devlink_sb_tc_pool_bind_set(struct devlink_port *dlp,
+                               unsigned int sb_index, u16 tc_index,
+                               enum devlink_sb_pool_type pool_type,
+                               u16 pool_index, u32 threshold,
+                               struct netlink_ext_ack *extack)
+{
+       struct dsa_switch *ds = dsa_devlink_port_to_ds(dlp);
+       int port = dsa_devlink_port_to_port(dlp);
+
+       if (!ds->ops->devlink_sb_tc_pool_bind_set)
+               return -EOPNOTSUPP;
+
+       return ds->ops->devlink_sb_tc_pool_bind_set(ds, port, sb_index,
+                                                   tc_index, pool_type,
+                                                   pool_index, threshold,
+                                                   extack);
+}
+
+static int dsa_devlink_sb_occ_snapshot(struct devlink *dl,
+                                      unsigned int sb_index)
+{
+       struct dsa_switch *ds = dsa_devlink_to_ds(dl);
+
+       if (!ds->ops->devlink_sb_occ_snapshot)
+               return -EOPNOTSUPP;
+
+       return ds->ops->devlink_sb_occ_snapshot(ds, sb_index);
+}
+
+static int dsa_devlink_sb_occ_max_clear(struct devlink *dl,
+                                       unsigned int sb_index)
+{
+       struct dsa_switch *ds = dsa_devlink_to_ds(dl);
+
+       if (!ds->ops->devlink_sb_occ_max_clear)
+               return -EOPNOTSUPP;
+
+       return ds->ops->devlink_sb_occ_max_clear(ds, sb_index);
+}
+
+static int dsa_devlink_sb_occ_port_pool_get(struct devlink_port *dlp,
+                                           unsigned int sb_index,
+                                           u16 pool_index, u32 *p_cur,
+                                           u32 *p_max)
+{
+       struct dsa_switch *ds = dsa_devlink_port_to_ds(dlp);
+       int port = dsa_devlink_port_to_port(dlp);
+
+       if (!ds->ops->devlink_sb_occ_port_pool_get)
+               return -EOPNOTSUPP;
+
+       return ds->ops->devlink_sb_occ_port_pool_get(ds, port, sb_index,
+                                                    pool_index, p_cur, p_max);
+}
+
+static int
+dsa_devlink_sb_occ_tc_port_bind_get(struct devlink_port *dlp,
+                                   unsigned int sb_index, u16 tc_index,
+                                   enum devlink_sb_pool_type pool_type,
+                                   u32 *p_cur, u32 *p_max)
+{
+       struct dsa_switch *ds = dsa_devlink_port_to_ds(dlp);
+       int port = dsa_devlink_port_to_port(dlp);
+
+       if (!ds->ops->devlink_sb_occ_tc_port_bind_get)
+               return -EOPNOTSUPP;
+
+       return ds->ops->devlink_sb_occ_tc_port_bind_get(ds, port,
+                                                       sb_index, tc_index,
+                                                       pool_type, p_cur,
+                                                       p_max);
+}
+
 static const struct devlink_ops dsa_devlink_ops = {
-       .info_get = dsa_devlink_info_get,
+       .info_get                       = dsa_devlink_info_get,
+       .sb_pool_get                    = dsa_devlink_sb_pool_get,
+       .sb_pool_set                    = dsa_devlink_sb_pool_set,
+       .sb_port_pool_get               = dsa_devlink_sb_port_pool_get,
+       .sb_port_pool_set               = dsa_devlink_sb_port_pool_set,
+       .sb_tc_pool_bind_get            = dsa_devlink_sb_tc_pool_bind_get,
+       .sb_tc_pool_bind_set            = dsa_devlink_sb_tc_pool_bind_set,
+       .sb_occ_snapshot                = dsa_devlink_sb_occ_snapshot,
+       .sb_occ_max_clear               = dsa_devlink_sb_occ_max_clear,
+       .sb_occ_port_pool_get           = dsa_devlink_sb_occ_port_pool_get,
+       .sb_occ_tc_port_bind_get        = dsa_devlink_sb_occ_tc_port_bind_get,
 };
 
 static int dsa_switch_setup(struct dsa_switch *ds)
@@ -452,6 +712,8 @@ static int dsa_switch_setup(struct dsa_switch *ds)
        if (err)
                goto unregister_devlink_ports;
 
+       ds->configure_vlan_while_not_filtering = true;
+
        err = ds->ops->setup(ds);
        if (err < 0)
                goto unregister_notifier;
@@ -462,20 +724,23 @@ static int dsa_switch_setup(struct dsa_switch *ds)
                ds->slave_mii_bus = devm_mdiobus_alloc(ds->dev);
                if (!ds->slave_mii_bus) {
                        err = -ENOMEM;
-                       goto unregister_notifier;
+                       goto teardown;
                }
 
                dsa_slave_mii_bus_init(ds);
 
                err = mdiobus_register(ds->slave_mii_bus);
                if (err < 0)
-                       goto unregister_notifier;
+                       goto teardown;
        }
 
        ds->setup = true;
 
        return 0;
 
+teardown:
+       if (ds->ops->teardown)
+               ds->ops->teardown(ds);
 unregister_notifier:
        dsa_switch_unregister_notifier(ds);
 unregister_devlink_ports:
@@ -530,8 +795,14 @@ static int dsa_tree_setup_switches(struct dsa_switch_tree *dst)
 
        list_for_each_entry(dp, &dst->ports, list) {
                err = dsa_port_setup(dp);
-               if (err)
+               if (err) {
+                       dsa_port_devlink_teardown(dp);
+                       dp->type = DSA_PORT_TYPE_UNUSED;
+                       err = dsa_port_devlink_setup(dp);
+                       if (err)
+                               goto teardown;
                        continue;
+               }
        }
 
        return 0;
@@ -582,6 +853,32 @@ static void dsa_tree_teardown_master(struct dsa_switch_tree *dst)
                        dsa_master_teardown(dp->master);
 }
 
+static int dsa_tree_setup_lags(struct dsa_switch_tree *dst)
+{
+       unsigned int len = 0;
+       struct dsa_port *dp;
+
+       list_for_each_entry(dp, &dst->ports, list) {
+               if (dp->ds->num_lag_ids > len)
+                       len = dp->ds->num_lag_ids;
+       }
+
+       if (!len)
+               return 0;
+
+       dst->lags = kcalloc(len, sizeof(*dst->lags), GFP_KERNEL);
+       if (!dst->lags)
+               return -ENOMEM;
+
+       dst->lags_len = len;
+       return 0;
+}
+
+static void dsa_tree_teardown_lags(struct dsa_switch_tree *dst)
+{
+       kfree(dst->lags);
+}
+
 static int dsa_tree_setup(struct dsa_switch_tree *dst)
 {
        bool complete;
@@ -609,12 +906,18 @@ static int dsa_tree_setup(struct dsa_switch_tree *dst)
        if (err)
                goto teardown_switches;
 
+       err = dsa_tree_setup_lags(dst);
+       if (err)
+               goto teardown_master;
+
        dst->setup = true;
 
        pr_info("DSA: tree %d setup\n", dst->index);
 
        return 0;
 
+teardown_master:
+       dsa_tree_teardown_master(dst);
 teardown_switches:
        dsa_tree_teardown_switches(dst);
 teardown_default_cpu:
@@ -630,6 +933,8 @@ static void dsa_tree_teardown(struct dsa_switch_tree *dst)
        if (!dst->setup)
                return;
 
+       dsa_tree_teardown_lags(dst);
+
        dsa_tree_teardown_master(dst);
 
        dsa_tree_teardown_switches(dst);
@@ -646,6 +951,57 @@ static void dsa_tree_teardown(struct dsa_switch_tree *dst)
        dst->setup = false;
 }
 
+/* Since the dsa/tagging sysfs device attribute is per master, the assumption
+ * is that all DSA switches within a tree share the same tagger, otherwise
+ * they would have formed disjoint trees (different "dsa,member" values).
+ */
+int dsa_tree_change_tag_proto(struct dsa_switch_tree *dst,
+                             struct net_device *master,
+                             const struct dsa_device_ops *tag_ops,
+                             const struct dsa_device_ops *old_tag_ops)
+{
+       struct dsa_notifier_tag_proto_info info;
+       struct dsa_port *dp;
+       int err = -EBUSY;
+
+       if (!rtnl_trylock())
+               return restart_syscall();
+
+       /* At the moment we don't allow changing the tag protocol under
+        * traffic. The rtnl_mutex also happens to serialize concurrent
+        * attempts to change the tagging protocol. If we ever lift the IFF_UP
+        * restriction, there needs to be another mutex which serializes this.
+        */
+       if (master->flags & IFF_UP)
+               goto out_unlock;
+
+       list_for_each_entry(dp, &dst->ports, list) {
+               if (!dsa_is_user_port(dp->ds, dp->index))
+                       continue;
+
+               if (dp->slave->flags & IFF_UP)
+                       goto out_unlock;
+       }
+
+       info.tag_ops = tag_ops;
+       err = dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO, &info);
+       if (err)
+               goto out_unwind_tagger;
+
+       dst->tag_ops = tag_ops;
+
+       rtnl_unlock();
+
+       return 0;
+
+out_unwind_tagger:
+       info.tag_ops = old_tag_ops;
+       dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO, &info);
+out_unlock:
+       rtnl_unlock();
+       return err;
+}
+
 static struct dsa_port *dsa_port_touch(struct dsa_switch *ds, int index)
 {
        struct dsa_switch_tree *dst = ds->dst;
@@ -720,20 +1076,32 @@ static int dsa_port_parse_cpu(struct dsa_port *dp, struct net_device *master)
        enum dsa_tag_protocol tag_protocol;
 
        tag_protocol = dsa_get_tag_protocol(dp, master);
-       tag_ops = dsa_tag_driver_get(tag_protocol);
-       if (IS_ERR(tag_ops)) {
-               if (PTR_ERR(tag_ops) == -ENOPROTOOPT)
-                       return -EPROBE_DEFER;
-               dev_warn(ds->dev, "No tagger for this switch\n");
-               dp->master = NULL;
-               return PTR_ERR(tag_ops);
+       if (dst->tag_ops) {
+               if (dst->tag_ops->proto != tag_protocol) {
+                       dev_err(ds->dev,
+                               "A DSA switch tree can have only one tagging protocol\n");
+                       return -EINVAL;
+               }
+               /* In the case of multiple CPU ports per switch, the tagging
+                * protocol is still reference-counted only per switch tree, so
+                * nothing to do here.
+                */
+       } else {
+               tag_ops = dsa_tag_driver_get(tag_protocol);
+               if (IS_ERR(tag_ops)) {
+                       if (PTR_ERR(tag_ops) == -ENOPROTOOPT)
+                               return -EPROBE_DEFER;
+                       dev_warn(ds->dev, "No tagger for this switch\n");
+                       dp->master = NULL;
+                       return PTR_ERR(tag_ops);
+               }
+
+               dst->tag_ops = tag_ops;
        }
 
        dp->master = master;
        dp->type = DSA_PORT_TYPE_CPU;
-       dp->filter = tag_ops->filter;
-       dp->rcv = tag_ops->rcv;
-       dp->tag_ops = tag_ops;
+       dsa_port_set_tag_protocol(dp, dst->tag_ops);
        dp->dst = dst;
 
        return 0;
@@ -787,6 +1155,8 @@ static int dsa_switch_parse_ports_of(struct dsa_switch *ds,
                        goto out_put_node;
 
                if (reg >= ds->num_ports) {
+                       dev_err(ds->dev, "port %pOF index %u exceeds num_ports (%zu)\n",
+                               port, reg, ds->num_ports);
                        err = -EINVAL;
                        goto out_put_node;
                }