diff options
Diffstat (limited to 'net/dsa/dsa2.c')
| -rw-r--r-- | net/dsa/dsa2.c | 201 | 
1 files changed, 155 insertions, 46 deletions
| diff --git a/net/dsa/dsa2.c b/net/dsa/dsa2.c index 826957b6442b..3d21521453fe 100644 --- a/net/dsa/dsa2.c +++ b/net/dsa/dsa2.c @@ -129,35 +129,52 @@ void dsa_lag_unmap(struct dsa_switch_tree *dst, struct net_device *lag)  	}  } +struct dsa_bridge *dsa_tree_bridge_find(struct dsa_switch_tree *dst, +					const struct net_device *br) +{ +	struct dsa_port *dp; + +	list_for_each_entry(dp, &dst->ports, list) +		if (dsa_port_bridge_dev_get(dp) == br) +			return dp->bridge; + +	return NULL; +} +  static int dsa_bridge_num_find(const struct net_device *bridge_dev)  {  	struct dsa_switch_tree *dst; -	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(dst, &dsa_tree_list, list) -		list_for_each_entry(dp, &dst->ports, list) -			if (dp->bridge_dev == bridge_dev && -			    dp->bridge_num != -1) -				return dp->bridge_num; +	list_for_each_entry(dst, &dsa_tree_list, list) { +		struct dsa_bridge *bridge; + +		bridge = dsa_tree_bridge_find(dst, bridge_dev); +		if (bridge) +			return bridge->num; +	} -	return -1; +	return 0;  } -int dsa_bridge_num_get(const struct net_device *bridge_dev, int max) +unsigned int dsa_bridge_num_get(const struct net_device *bridge_dev, int max)  { -	int bridge_num = dsa_bridge_num_find(bridge_dev); +	unsigned int bridge_num = dsa_bridge_num_find(bridge_dev); -	if (bridge_num < 0) { -		/* First port that offloads TX forwarding for this bridge */ -		bridge_num = find_first_zero_bit(&dsa_fwd_offloading_bridges, -						 DSA_MAX_NUM_OFFLOADING_BRIDGES); +	/* Switches without FDB isolation support don't get unique +	 * bridge numbering +	 */ +	if (!max) +		return 0; + +	if (!bridge_num) { +		/* First port that requests FDB isolation or TX forwarding +		 * offload for this bridge +		 */ +		bridge_num = find_next_zero_bit(&dsa_fwd_offloading_bridges, +						DSA_MAX_NUM_OFFLOADING_BRIDGES, +						1);  		if (bridge_num >= max) -			return -1; +			return 0;  		set_bit(bridge_num, &dsa_fwd_offloading_bridges);  	} @@ -165,13 +182,14 @@ int dsa_bridge_num_get(const struct net_device *bridge_dev, int max)  	return bridge_num;  } -void dsa_bridge_num_put(const struct net_device *bridge_dev, int bridge_num) +void dsa_bridge_num_put(const struct net_device *bridge_dev, +			unsigned int bridge_num)  { -	/* Check if the bridge is still in use, otherwise it is time -	 * to clean it up so we can reuse this bridge_num later. +	/* Since we refcount bridges, we know that when we call this function +	 * it is no longer in use, so we can just go ahead and remove it from +	 * the bit mask.  	 */ -	if (dsa_bridge_num_find(bridge_dev) < 0) -		clear_bit(bridge_num, &dsa_fwd_offloading_bridges); +	clear_bit(bridge_num, &dsa_fwd_offloading_bridges);  }  struct dsa_switch *dsa_switch_find(int tree_index, int sw_index) @@ -543,6 +561,7 @@ static void dsa_port_teardown(struct dsa_port *dp)  	struct devlink_port *dlp = &dp->devlink_port;  	struct dsa_switch *ds = dp->ds;  	struct dsa_mac_addr *a, *tmp; +	struct net_device *slave;  	if (!dp->setup)  		return; @@ -564,9 +583,11 @@ static void dsa_port_teardown(struct dsa_port *dp)  		dsa_port_link_unregister_of(dp);  		break;  	case DSA_PORT_TYPE_USER: -		if (dp->slave) { -			dsa_slave_destroy(dp->slave); +		slave = dp->slave; + +		if (slave) {  			dp->slave = NULL; +			dsa_slave_destroy(slave);  		}  		break;  	} @@ -804,7 +825,7 @@ static int dsa_switch_setup_tag_protocol(struct dsa_switch *ds)  	int err;  	if (tag_ops->proto == dst->default_proto) -		return 0; +		goto connect;  	dsa_switch_for_each_cpu_port(cpu_dp, ds) {  		rtnl_lock(); @@ -818,7 +839,30 @@ static int dsa_switch_setup_tag_protocol(struct dsa_switch *ds)  		}  	} +connect: +	if (tag_ops->connect) { +		err = tag_ops->connect(ds); +		if (err) +			return err; +	} + +	if (ds->ops->connect_tag_protocol) { +		err = ds->ops->connect_tag_protocol(ds, tag_ops->proto); +		if (err) { +			dev_err(ds->dev, +				"Unable to connect to tag protocol \"%s\": %pe\n", +				tag_ops->name, ERR_PTR(err)); +			goto disconnect; +		} +	} +  	return 0; + +disconnect: +	if (tag_ops->disconnect) +		tag_ops->disconnect(ds); + +	return err;  }  static int dsa_switch_setup(struct dsa_switch *ds) @@ -962,23 +1006,28 @@ static void dsa_tree_teardown_switches(struct dsa_switch_tree *dst)  		dsa_switch_teardown(dp->ds);  } -static int dsa_tree_setup_switches(struct dsa_switch_tree *dst) +/* Bring shared ports up first, then non-shared ports */ +static int dsa_tree_setup_ports(struct dsa_switch_tree *dst)  {  	struct dsa_port *dp; -	int err; +	int err = 0;  	list_for_each_entry(dp, &dst->ports, list) { -		err = dsa_switch_setup(dp->ds); -		if (err) -			goto teardown; +		if (dsa_port_is_dsa(dp) || dsa_port_is_cpu(dp)) { +			err = dsa_port_setup(dp); +			if (err) +				goto teardown; +		}  	}  	list_for_each_entry(dp, &dst->ports, list) { -		err = dsa_port_setup(dp); -		if (err) { -			err = dsa_port_reinit_as_unused(dp); -			if (err) -				goto teardown; +		if (dsa_port_is_user(dp) || dsa_port_is_unused(dp)) { +			err = dsa_port_setup(dp); +			if (err) { +				err = dsa_port_reinit_as_unused(dp); +				if (err) +					goto teardown; +			}  		}  	} @@ -987,7 +1036,21 @@ static int dsa_tree_setup_switches(struct dsa_switch_tree *dst)  teardown:  	dsa_tree_teardown_ports(dst); -	dsa_tree_teardown_switches(dst); +	return err; +} + +static int dsa_tree_setup_switches(struct dsa_switch_tree *dst) +{ +	struct dsa_port *dp; +	int err = 0; + +	list_for_each_entry(dp, &dst->ports, list) { +		err = dsa_switch_setup(dp->ds); +		if (err) { +			dsa_tree_teardown_switches(dst); +			break; +		} +	}  	return err;  } @@ -997,6 +1060,8 @@ static int dsa_tree_setup_master(struct dsa_switch_tree *dst)  	struct dsa_port *dp;  	int err; +	rtnl_lock(); +  	list_for_each_entry(dp, &dst->ports, list) {  		if (dsa_port_is_cpu(dp)) {  			err = dsa_master_setup(dp->master, dp); @@ -1005,6 +1070,8 @@ static int dsa_tree_setup_master(struct dsa_switch_tree *dst)  		}  	} +	rtnl_unlock(); +  	return 0;  } @@ -1012,9 +1079,13 @@ static void dsa_tree_teardown_master(struct dsa_switch_tree *dst)  {  	struct dsa_port *dp; +	rtnl_lock(); +  	list_for_each_entry(dp, &dst->ports, list)  		if (dsa_port_is_cpu(dp))  			dsa_master_teardown(dp->master); + +	rtnl_unlock();  }  static int dsa_tree_setup_lags(struct dsa_switch_tree *dst) @@ -1070,20 +1141,25 @@ static int dsa_tree_setup(struct dsa_switch_tree *dst)  	if (err)  		goto teardown_switches; -	err = dsa_tree_setup_lags(dst); +	err = dsa_tree_setup_ports(dst);  	if (err)  		goto teardown_master; +	err = dsa_tree_setup_lags(dst); +	if (err) +		goto teardown_ports; +  	dst->setup = true;  	pr_info("DSA: tree %d setup\n", dst->index);  	return 0; +teardown_ports: +	dsa_tree_teardown_ports(dst);  teardown_master:  	dsa_tree_teardown_master(dst);  teardown_switches: -	dsa_tree_teardown_ports(dst);  	dsa_tree_teardown_switches(dst);  teardown_cpu_ports:  	dsa_tree_teardown_cpu_ports(dst); @@ -1100,10 +1176,10 @@ static void dsa_tree_teardown(struct dsa_switch_tree *dst)  	dsa_tree_teardown_lags(dst); -	dsa_tree_teardown_master(dst); -  	dsa_tree_teardown_ports(dst); +	dsa_tree_teardown_master(dst); +  	dsa_tree_teardown_switches(dst);  	dsa_tree_teardown_cpu_ports(dst); @@ -1118,6 +1194,37 @@ static void dsa_tree_teardown(struct dsa_switch_tree *dst)  	dst->setup = false;  } +static int dsa_tree_bind_tag_proto(struct dsa_switch_tree *dst, +				   const struct dsa_device_ops *tag_ops) +{ +	const struct dsa_device_ops *old_tag_ops = dst->tag_ops; +	struct dsa_notifier_tag_proto_info info; +	int err; + +	dst->tag_ops = tag_ops; + +	/* Notify the switches from this tree about the connection +	 * to the new tagger +	 */ +	info.tag_ops = tag_ops; +	err = dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO_CONNECT, &info); +	if (err && err != -EOPNOTSUPP) +		goto out_disconnect; + +	/* Notify the old tagger about the disconnection from this tree */ +	info.tag_ops = old_tag_ops; +	dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO_DISCONNECT, &info); + +	return 0; + +out_disconnect: +	info.tag_ops = tag_ops; +	dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO_DISCONNECT, &info); +	dst->tag_ops = old_tag_ops; + +	return err; +} +  /* 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). @@ -1150,12 +1257,15 @@ int dsa_tree_change_tag_proto(struct dsa_switch_tree *dst,  			goto out_unlock;  	} +	/* Notify the tag protocol change */  	info.tag_ops = tag_ops;  	err = dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO, &info);  	if (err) -		goto out_unwind_tagger; +		return err; -	dst->tag_ops = tag_ops; +	err = dsa_tree_bind_tag_proto(dst, tag_ops); +	if (err) +		goto out_unwind_tagger;  	rtnl_unlock(); @@ -1184,7 +1294,6 @@ static struct dsa_port *dsa_port_touch(struct dsa_switch *ds, int index)  	dp->ds = ds;  	dp->index = index; -	dp->bridge_num = -1;  	INIT_LIST_HEAD(&dp->list);  	list_add_tail(&dp->list, &dst->ports); @@ -1366,7 +1475,7 @@ static int dsa_switch_parse_ports_of(struct dsa_switch *ds,  		}  		if (reg >= ds->num_ports) { -			dev_err(ds->dev, "port %pOF index %u exceeds num_ports (%zu)\n", +			dev_err(ds->dev, "port %pOF index %u exceeds num_ports (%u)\n",  				port, reg, ds->num_ports);  			of_node_put(port);  			err = -EINVAL; |