@@ -3,5 +3,12 @@ Date: August 2018
KernelVersion: 4.20
Contact: netdev@vger.kernel.org
Description:
- String indicating the type of tagging protocol used by the
- DSA slave network device.
+ On read, this file returns a string indicating the type of
+ tagging protocol used by the DSA network devices that are
+ attached to this master interface.
+ On write, this file changes the tagging protocol of the
+ attached DSA switches, if this operation is supported by the
+ driver. Changing the tagging protocol must be done with the DSA
+ interfaces and the master interface all administratively down.
+ See the "name" field of each registered struct dsa_device_ops
+ for a list of valid values.
@@ -140,6 +140,12 @@ struct dsa_switch_tree {
/* Has this tree been applied to the hardware? */
bool setup;
+ /*
+ * Used to serialize concurrent attempts to change the tagging
+ * protocol via the "tagging" device attribute.
+ */
+ struct mutex tagger_lock;
+
/*
* Configuration data for the platform device that owns
* this dsa switch tree instance.
@@ -480,9 +486,24 @@ static inline bool dsa_port_is_vlan_filtering(const struct dsa_port *dp)
typedef int dsa_fdb_dump_cb_t(const unsigned char *addr, u16 vid,
bool is_static, void *data);
struct dsa_switch_ops {
+ /*
+ * Tagging protocol helpers.
+ * Switches that support a single tagging protocol should implement
+ * only @get_tag_protocol and hardcode the protocol that they support.
+ * Switches which can operate using multiple tagging protocols should
+ * report in @get_tag_protocol the tagger in current use. They can
+ * optionally set up the tagging protocol in @set_tag_protocol and
+ * perform teardown (memory deallocation, etc) in @del_tag_protocol.
+ * The framework guarantees paired calls to the last two functions.
+ * These helpers are called with @port as the CPU ports and DSA links.
+ */
enum dsa_tag_protocol (*get_tag_protocol)(struct dsa_switch *ds,
int port,
enum dsa_tag_protocol mprot);
+ int (*set_tag_protocol)(struct dsa_switch *ds, int port,
+ enum dsa_tag_protocol proto);
+ void (*del_tag_protocol)(struct dsa_switch *ds, int port,
+ enum dsa_tag_protocol proto);
int (*setup)(struct dsa_switch *ds);
void (*teardown)(struct dsa_switch *ds);
@@ -84,6 +84,26 @@ const char *dsa_tag_protocol_to_str(const struct dsa_device_ops *ops)
return ops->name;
};
+const struct dsa_device_ops *dsa_find_tagger_by_name(const char *buf)
+{
+ const struct dsa_device_ops *ops = NULL;
+ struct dsa_tag_driver *dsa_tag_driver;
+
+ mutex_lock(&dsa_tag_drivers_lock);
+ list_for_each_entry(dsa_tag_driver, &dsa_tag_drivers_list, list) {
+ const struct dsa_device_ops *tmp = dsa_tag_driver->ops;
+
+ if (!sysfs_streq(buf, tmp->name))
+ continue;
+
+ ops = tmp;
+ break;
+ }
+ mutex_unlock(&dsa_tag_drivers_lock);
+
+ return ops;
+}
+
const struct dsa_device_ops *dsa_tag_driver_get(int tag_protocol)
{
struct dsa_tag_driver *dsa_tag_driver;
@@ -717,6 +717,21 @@ static int dsa_switch_setup(struct dsa_switch *ds)
if (err < 0)
goto unregister_notifier;
+ /* Iterate through ports list again, so that we notify the switch of
+ * its tagging protocol after setup(), but before we start registering
+ * the user ports, whose MTU configuration will depend upon the tagger.
+ */
+ list_for_each_entry(dp, &ds->dst->ports, list) {
+ if (dp->ds == ds && ds->ops->set_tag_protocol &&
+ (dsa_is_cpu_port(ds, dp->index) ||
+ dsa_is_dsa_port(ds, dp->index))) {
+ err = ds->ops->set_tag_protocol(ds, dp->index,
+ dp->tag_ops->proto);
+ if (err)
+ goto teardown;
+ }
+ }
+
devlink_params_publish(ds->devlink);
if (!ds->slave_mii_bus && ds->ops->phy_read) {
@@ -737,6 +752,9 @@ static int dsa_switch_setup(struct dsa_switch *ds)
return 0;
+teardown:
+ if (ds->ops->teardown)
+ ds->ops->teardown(ds);
unregister_notifier:
dsa_switch_unregister_notifier(ds);
unregister_devlink_ports:
@@ -761,6 +779,15 @@ static void dsa_switch_teardown(struct dsa_switch *ds)
if (ds->slave_mii_bus && ds->ops->phy_read)
mdiobus_unregister(ds->slave_mii_bus);
+ list_for_each_entry(dp, &ds->dst->ports, list) {
+ if (dp->ds == ds && ds->ops->del_tag_protocol &&
+ (dsa_is_cpu_port(ds, dp->index) ||
+ dsa_is_dsa_port(ds, dp->index))) {
+ ds->ops->del_tag_protocol(ds, dp->index,
+ dp->tag_ops->proto);
+ }
+ }
+
dsa_switch_unregister_notifier(ds);
if (ds->ops->teardown)
@@ -880,6 +907,8 @@ static int dsa_tree_setup(struct dsa_switch_tree *dst)
return -EEXIST;
}
+ mutex_init(&dst->tagger_lock);
+
complete = dsa_tree_setup_routing_table(dst);
if (!complete)
return 0;
@@ -941,6 +970,56 @@ 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;
+
+ /* At the moment we don't allow changing the tag protocol under
+ * traffic. May revisit in the future.
+ */
+ if (master->flags & IFF_UP)
+ return -EBUSY;
+
+ list_for_each_entry(dp, &dst->ports, list) {
+ if (!dsa_is_user_port(dp->ds, dp->index))
+ continue;
+
+ if (dp->slave->flags & IFF_UP)
+ return -EBUSY;
+ }
+
+ mutex_lock(&dst->tagger_lock);
+
+ info.tag_ops = old_tag_ops;
+ err = dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO_DEL, &info);
+ if (err)
+ return err;
+
+ info.tag_ops = tag_ops;
+ err = dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO_SET, &info);
+ if (err)
+ goto out_unwind_tagger;
+
+ mutex_unlock(&dst->tagger_lock);
+
+ return 0;
+
+out_unwind_tagger:
+ info.tag_ops = old_tag_ops;
+ dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO_SET, &info);
+ mutex_unlock(&dst->tagger_lock);
+ return err;
+}
+
static struct dsa_port *dsa_port_touch(struct dsa_switch *ds, int index)
{
struct dsa_switch_tree *dst = ds->dst;
@@ -1026,10 +1105,8 @@ static int dsa_port_parse_cpu(struct dsa_port *dp, struct net_device *master)
dp->master = master;
dp->type = DSA_PORT_TYPE_CPU;
- dp->filter = tag_ops->filter;
- dp->rcv = tag_ops->rcv;
- dp->tag_ops = tag_ops;
dp->dst = dst;
+ dsa_port_set_tag_protocol(dp, tag_ops);
return 0;
}
@@ -28,6 +28,8 @@ enum {
DSA_NOTIFIER_VLAN_ADD,
DSA_NOTIFIER_VLAN_DEL,
DSA_NOTIFIER_MTU,
+ DSA_NOTIFIER_TAG_PROTO_SET,
+ DSA_NOTIFIER_TAG_PROTO_DEL,
};
/* DSA_NOTIFIER_AGEING_TIME */
@@ -82,6 +84,11 @@ struct dsa_notifier_mtu_info {
int mtu;
};
+/* DSA_NOTIFIER_TAG_PROTO_* */
+struct dsa_notifier_tag_proto_info {
+ const struct dsa_device_ops *tag_ops;
+};
+
struct dsa_switchdev_event_work {
struct dsa_switch *ds;
int port;
@@ -115,6 +122,7 @@ struct dsa_slave_priv {
/* dsa.c */
const struct dsa_device_ops *dsa_tag_driver_get(int tag_protocol);
void dsa_tag_driver_put(const struct dsa_device_ops *ops);
+const struct dsa_device_ops *dsa_find_tagger_by_name(const char *buf);
bool dsa_schedule_work(struct work_struct *work);
const char *dsa_tag_protocol_to_str(const struct dsa_device_ops *ops);
@@ -139,6 +147,8 @@ static inline struct net_device *dsa_master_find_slave(struct net_device *dev,
}
/* port.c */
+void dsa_port_set_tag_protocol(struct dsa_port *cpu_dp,
+ const struct dsa_device_ops *tag_ops);
int dsa_port_set_state(struct dsa_port *dp, u8 state);
int dsa_port_enable_rt(struct dsa_port *dp, struct phy_device *phy);
int dsa_port_enable(struct dsa_port *dp, struct phy_device *phy);
@@ -201,6 +211,8 @@ int dsa_slave_suspend(struct net_device *slave_dev);
int dsa_slave_resume(struct net_device *slave_dev);
int dsa_slave_register_notifier(void);
void dsa_slave_unregister_notifier(void);
+void dsa_slave_setup_tagger(struct net_device *slave);
+int dsa_slave_change_mtu(struct net_device *dev, int new_mtu);
static inline struct dsa_port *dsa_slave_to_port(const struct net_device *dev)
{
@@ -285,6 +297,10 @@ void dsa_lag_map(struct dsa_switch_tree *dst, struct net_device *lag);
void dsa_lag_unmap(struct dsa_switch_tree *dst, struct net_device *lag);
int dsa_tree_notify(struct dsa_switch_tree *dst, unsigned long e, void *v);
int dsa_broadcast(unsigned long e, void *v);
+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);
extern struct list_head dsa_tree_list;
@@ -280,7 +280,31 @@ static ssize_t tagging_show(struct device *d, struct device_attribute *attr,
return sprintf(buf, "%s\n",
dsa_tag_protocol_to_str(cpu_dp->tag_ops));
}
-static DEVICE_ATTR_RO(tagging);
+
+static ssize_t tagging_store(struct device *d, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct net_device *dev = to_net_dev(d);
+ struct dsa_port *cpu_dp = dev->dsa_ptr;
+ const struct dsa_device_ops *ops;
+ int err;
+
+ ops = dsa_find_tagger_by_name(buf);
+ /* Bad tagger name, or module is not loaded? */
+ if (!ops)
+ return -ENOENT;
+
+ if (ops == cpu_dp->tag_ops)
+ goto out;
+
+ err = dsa_tree_change_tag_proto(cpu_dp->ds->dst, dev, ops,
+ cpu_dp->tag_ops);
+ if (err)
+ return err;
+out:
+ return count;
+}
+static DEVICE_ATTR_RW(tagging);
static struct attribute *dsa_slave_attrs[] = {
&dev_attr_tagging.attr,
@@ -526,6 +526,14 @@ int dsa_port_vlan_del(struct dsa_port *dp,
return dsa_port_notify(dp, DSA_NOTIFIER_VLAN_DEL, &info);
}
+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;
+}
+
static struct phy_device *dsa_port_get_phy_device(struct dsa_port *dp)
{
struct device_node *phy_dn;
@@ -1430,7 +1430,7 @@ static void dsa_bridge_mtu_normalization(struct dsa_port *dp)
dsa_hw_port_list_free(&hw_port_list);
}
-static int dsa_slave_change_mtu(struct net_device *dev, int new_mtu)
+int dsa_slave_change_mtu(struct net_device *dev, int new_mtu)
{
struct net_device *master = dsa_slave_to_master(dev);
struct dsa_port *dp = dsa_slave_to_port(dev);
@@ -1708,6 +1708,27 @@ static int dsa_slave_phy_setup(struct net_device *slave_dev)
return ret;
}
+void dsa_slave_setup_tagger(struct net_device *slave)
+{
+ struct dsa_port *dp = dsa_slave_to_port(slave);
+ struct dsa_slave_priv *p = netdev_priv(slave);
+ const struct dsa_port *cpu_dp = dp->cpu_dp;
+ struct net_device *master = cpu_dp->master;
+
+ if (cpu_dp->tag_ops->tail_tag)
+ slave->needed_tailroom = cpu_dp->tag_ops->overhead;
+ else
+ slave->needed_headroom = cpu_dp->tag_ops->overhead;
+ /* Try to save one extra realloc later in the TX path (in the master)
+ * by also inheriting the master's needed headroom and tailroom.
+ * The 8021q driver also does this.
+ */
+ slave->needed_headroom += master->needed_headroom;
+ slave->needed_tailroom += master->needed_tailroom;
+
+ p->xmit = cpu_dp->tag_ops->xmit;
+}
+
static struct lock_class_key dsa_slave_netdev_xmit_lock_key;
static void dsa_slave_set_lockdep_class_one(struct net_device *dev,
struct netdev_queue *txq,
@@ -1782,16 +1803,6 @@ int dsa_slave_create(struct dsa_port *port)
slave_dev->netdev_ops = &dsa_slave_netdev_ops;
if (ds->ops->port_max_mtu)
slave_dev->max_mtu = ds->ops->port_max_mtu(ds, port->index);
- if (cpu_dp->tag_ops->tail_tag)
- slave_dev->needed_tailroom = cpu_dp->tag_ops->overhead;
- else
- slave_dev->needed_headroom = cpu_dp->tag_ops->overhead;
- /* Try to save one extra realloc later in the TX path (in the master)
- * by also inheriting the master's needed headroom and tailroom.
- * The 8021q driver also does this.
- */
- slave_dev->needed_headroom += master->needed_headroom;
- slave_dev->needed_tailroom += master->needed_tailroom;
SET_NETDEV_DEVTYPE(slave_dev, &dsa_type);
netdev_for_each_tx_queue(slave_dev, dsa_slave_set_lockdep_class_one,
@@ -1814,8 +1825,8 @@ int dsa_slave_create(struct dsa_port *port)
p->dp = port;
INIT_LIST_HEAD(&p->mall_tc_list);
- p->xmit = cpu_dp->tag_ops->xmit;
port->slave = slave_dev;
+ dsa_slave_setup_tagger(slave_dev);
rtnl_lock();
ret = dsa_slave_change_mtu(slave_dev, ETH_DATA_LEN);
@@ -297,6 +297,81 @@ static int dsa_switch_vlan_del(struct dsa_switch *ds,
return 0;
}
+static bool dsa_switch_tag_proto_match(struct dsa_switch *ds, int port,
+ struct dsa_notifier_tag_proto_info *info)
+{
+ if (dsa_is_cpu_port(ds, port) || dsa_is_dsa_port(ds, port))
+ return true;
+
+ return false;
+}
+
+static int dsa_switch_tag_proto_del(struct dsa_switch *ds,
+ struct dsa_notifier_tag_proto_info *info)
+{
+ int port;
+
+ /* Check early if we can replace it, so we don't delete it
+ * for nothing and leave the switch dangling.
+ */
+ if (!ds->ops->set_tag_protocol)
+ return -EOPNOTSUPP;
+
+ for (port = 0; port < ds->num_ports; port++) {
+ /* The delete method is optional, just the setter
+ * is mandatory
+ */
+ if (dsa_switch_tag_proto_match(ds, port, info) &&
+ ds->ops->del_tag_protocol) {
+ ds->ops->del_tag_protocol(ds, port,
+ info->tag_ops->proto);
+ }
+ }
+
+ return 0;
+}
+
+static int dsa_switch_tag_proto_set(struct dsa_switch *ds,
+ struct dsa_notifier_tag_proto_info *info)
+{
+ int port, err;
+
+ if (!ds->ops->set_tag_protocol)
+ return -EOPNOTSUPP;
+
+ for (port = 0; port < ds->num_ports; port++) {
+ if (dsa_switch_tag_proto_match(ds, port, info)) {
+ err = ds->ops->set_tag_protocol(ds, port,
+ info->tag_ops->proto);
+ if (err)
+ return err;
+
+ if (dsa_is_cpu_port(ds, port))
+ dsa_port_set_tag_protocol(dsa_to_port(ds, port),
+ info->tag_ops);
+ }
+ }
+
+ /* Now that changing the tag protocol can no longer fail, let's update
+ * the remaining bits which are "duplicated for faster access", and the
+ * bits that depend on the tagger, such as the MTU.
+ */
+ for (port = 0; port < ds->num_ports; port++) {
+ if (dsa_is_user_port(ds, port)) {
+ struct net_device *slave;
+
+ slave = dsa_to_port(ds, port)->slave;
+ dsa_slave_setup_tagger(slave);
+
+ rtnl_lock();
+ dsa_slave_change_mtu(slave, slave->mtu);
+ rtnl_unlock();
+ }
+ }
+
+ return 0;
+}
+
static int dsa_switch_event(struct notifier_block *nb,
unsigned long event, void *info)
{
@@ -343,6 +418,12 @@ static int dsa_switch_event(struct notifier_block *nb,
case DSA_NOTIFIER_MTU:
err = dsa_switch_mtu(ds, info);
break;
+ case DSA_NOTIFIER_TAG_PROTO_SET:
+ err = dsa_switch_tag_proto_set(ds, info);
+ break;
+ case DSA_NOTIFIER_TAG_PROTO_DEL:
+ err = dsa_switch_tag_proto_del(ds, info);
+ break;
default:
err = -EOPNOTSUPP;
break;