Commit 22a8a230 authored by Jakub Kicinski's avatar Jakub Kicinski
Browse files

Merge branch 'net-dsa-link-aggregation-support'

Tobias Waldekranz says:

====================
net: dsa: Link aggregation support

Start of by adding an extra notification when adding a port to a bond,
this allows static LAGs to be offloaded using the bonding driver.

Then add the generic support required to offload link aggregates to
drivers built on top of the DSA subsystem.

Finally, implement offloading for the mv88e6xxx driver, i.e. Marvell's
LinkStreet family.

Supported LAG implementations:
- Bonding
- Team

Supported modes:
- Isolated. The LAG may be used as a regular interface outside of any
  bridge.
- Bridged. The LAG may be added to a bridge, in which case switching
  is offloaded between the LAG and any other switch ports. I.e. the
  LAG behaves just like a port from this perspective.

In bridged mode, the following is supported:
- STP filtering.
- VLAN filtering.
- Multicast filtering. The bridge correctly snoops IGMP and configures
  the proper groups if snooping is enabled. Static groups can also be
  configured. MLD seems to work, but has not been extensively tested.
- Unicast filtering. Automatic learning works. Static entries are
  _not_ supported. This will be added in a later series as it requires
  some more general refactoring in mv88e6xxx before I can test it.

v4 -> v5:
- Cleanup PVT configuration for LAGed ports in mv88e6xxx (Vladimir)
- Document dsa_lag_{map,unmap} (Vladimir)
====================

Link: https://lore.kernel.org/r/20210113084255.22675-1-tobias@waldekranz.com


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parents bb5c64c8 5b60dadb
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -1922,6 +1922,8 @@ int bond_enslave(struct net_device *bond_dev, struct net_device *slave_dev,
		goto err_unregister;
	}

	bond_lower_state_changed(new_slave);

	res = bond_sysfs_slave_add(new_slave);
	if (res) {
		slave_dbg(bond_dev, slave_dev, "Error %d calling bond_sysfs_slave_add\n", res);
+295 −1
Original line number Diff line number Diff line
@@ -1396,15 +1396,32 @@ static int mv88e6xxx_mac_setup(struct mv88e6xxx_chip *chip)

static int mv88e6xxx_pvt_map(struct mv88e6xxx_chip *chip, int dev, int port)
{
	struct dsa_switch_tree *dst = chip->ds->dst;
	struct dsa_switch *ds;
	struct dsa_port *dp;
	u16 pvlan = 0;

	if (!mv88e6xxx_has_pvt(chip))
		return 0;

	/* Skip the local source device, which uses in-chip port VLAN */
	if (dev != chip->ds->index)
	if (dev != chip->ds->index) {
		pvlan = mv88e6xxx_port_vlan(chip, dev, port);

		ds = dsa_switch_find(dst->index, dev);
		dp = ds ? dsa_to_port(ds, port) : NULL;
		if (dp && dp->lag_dev) {
			/* As the PVT is used to limit flooding of
			 * FORWARD frames, which use the LAG ID as the
			 * source port, we must translate dev/port to
			 * the special "LAG device" in the PVT, using
			 * the LAG ID as the port number.
			 */
			dev = MV88E6XXX_G2_PVT_ADRR_DEV_TRUNK;
			port = dsa_lag_id(dst, dp->lag_dev);
		}
	}

	return mv88e6xxx_g2_pvt_write(chip, dev, port, pvlan);
}

@@ -5364,6 +5381,271 @@ static int mv88e6xxx_port_egress_floods(struct dsa_switch *ds, int port,
	return err;
}

static bool mv88e6xxx_lag_can_offload(struct dsa_switch *ds,
				      struct net_device *lag,
				      struct netdev_lag_upper_info *info)
{
	struct dsa_port *dp;
	int id, members = 0;

	id = dsa_lag_id(ds->dst, lag);
	if (id < 0 || id >= ds->num_lag_ids)
		return false;

	dsa_lag_foreach_port(dp, ds->dst, lag)
		/* Includes the port joining the LAG */
		members++;

	if (members > 8)
		return false;

	/* We could potentially relax this to include active
	 * backup in the future.
	 */
	if (info->tx_type != NETDEV_LAG_TX_TYPE_HASH)
		return false;

	/* Ideally we would also validate that the hash type matches
	 * the hardware. Alas, this is always set to unknown on team
	 * interfaces.
	 */
	return true;
}

static int mv88e6xxx_lag_sync_map(struct dsa_switch *ds, struct net_device *lag)
{
	struct mv88e6xxx_chip *chip = ds->priv;
	struct dsa_port *dp;
	u16 map = 0;
	int id;

	id = dsa_lag_id(ds->dst, lag);

	/* Build the map of all ports to distribute flows destined for
	 * this LAG. This can be either a local user port, or a DSA
	 * port if the LAG port is on a remote chip.
	 */
	dsa_lag_foreach_port(dp, ds->dst, lag)
		map |= BIT(dsa_towards_port(ds, dp->ds->index, dp->index));

	return mv88e6xxx_g2_trunk_mapping_write(chip, id, map);
}

static const u8 mv88e6xxx_lag_mask_table[8][8] = {
	/* Row number corresponds to the number of active members in a
	 * LAG. Each column states which of the eight hash buckets are
	 * mapped to the column:th port in the LAG.
	 *
	 * Example: In a LAG with three active ports, the second port
	 * ([2][1]) would be selected for traffic mapped to buckets
	 * 3,4,5 (0x38).
	 */
	{ 0xff,    0,    0,    0,    0,    0,    0,    0 },
	{ 0x0f, 0xf0,    0,    0,    0,    0,    0,    0 },
	{ 0x07, 0x38, 0xc0,    0,    0,    0,    0,    0 },
	{ 0x03, 0x0c, 0x30, 0xc0,    0,    0,    0,    0 },
	{ 0x03, 0x0c, 0x30, 0x40, 0x80,    0,    0,    0 },
	{ 0x03, 0x0c, 0x10, 0x20, 0x40, 0x80,    0,    0 },
	{ 0x03, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80,    0 },
	{ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 },
};

static void mv88e6xxx_lag_set_port_mask(u16 *mask, int port,
					int num_tx, int nth)
{
	u8 active = 0;
	int i;

	num_tx = num_tx <= 8 ? num_tx : 8;
	if (nth < num_tx)
		active = mv88e6xxx_lag_mask_table[num_tx - 1][nth];

	for (i = 0; i < 8; i++) {
		if (BIT(i) & active)
			mask[i] |= BIT(port);
	}
}

static int mv88e6xxx_lag_sync_masks(struct dsa_switch *ds)
{
	struct mv88e6xxx_chip *chip = ds->priv;
	unsigned int id, num_tx;
	struct net_device *lag;
	struct dsa_port *dp;
	int i, err, nth;
	u16 mask[8];
	u16 ivec;

	/* Assume no port is a member of any LAG. */
	ivec = BIT(mv88e6xxx_num_ports(chip)) - 1;

	/* Disable all masks for ports that _are_ members of a LAG. */
	list_for_each_entry(dp, &ds->dst->ports, list) {
		if (!dp->lag_dev || dp->ds != ds)
			continue;

		ivec &= ~BIT(dp->index);
	}

	for (i = 0; i < 8; i++)
		mask[i] = ivec;

	/* Enable the correct subset of masks for all LAG ports that
	 * are in the Tx set.
	 */
	dsa_lags_foreach_id(id, ds->dst) {
		lag = dsa_lag_dev(ds->dst, id);
		if (!lag)
			continue;

		num_tx = 0;
		dsa_lag_foreach_port(dp, ds->dst, lag) {
			if (dp->lag_tx_enabled)
				num_tx++;
		}

		if (!num_tx)
			continue;

		nth = 0;
		dsa_lag_foreach_port(dp, ds->dst, lag) {
			if (!dp->lag_tx_enabled)
				continue;

			if (dp->ds == ds)
				mv88e6xxx_lag_set_port_mask(mask, dp->index,
							    num_tx, nth);

			nth++;
		}
	}

	for (i = 0; i < 8; i++) {
		err = mv88e6xxx_g2_trunk_mask_write(chip, i, true, mask[i]);
		if (err)
			return err;
	}

	return 0;
}

static int mv88e6xxx_lag_sync_masks_map(struct dsa_switch *ds,
					struct net_device *lag)
{
	int err;

	err = mv88e6xxx_lag_sync_masks(ds);

	if (!err)
		err = mv88e6xxx_lag_sync_map(ds, lag);

	return err;
}

static int mv88e6xxx_port_lag_change(struct dsa_switch *ds, int port)
{
	struct mv88e6xxx_chip *chip = ds->priv;
	int err;

	mv88e6xxx_reg_lock(chip);
	err = mv88e6xxx_lag_sync_masks(ds);
	mv88e6xxx_reg_unlock(chip);
	return err;
}

static int mv88e6xxx_port_lag_join(struct dsa_switch *ds, int port,
				   struct net_device *lag,
				   struct netdev_lag_upper_info *info)
{
	struct mv88e6xxx_chip *chip = ds->priv;
	int err, id;

	if (!mv88e6xxx_lag_can_offload(ds, lag, info))
		return -EOPNOTSUPP;

	id = dsa_lag_id(ds->dst, lag);

	mv88e6xxx_reg_lock(chip);

	err = mv88e6xxx_port_set_trunk(chip, port, true, id);
	if (err)
		goto err_unlock;

	err = mv88e6xxx_lag_sync_masks_map(ds, lag);
	if (err)
		goto err_clear_trunk;

	mv88e6xxx_reg_unlock(chip);
	return 0;

err_clear_trunk:
	mv88e6xxx_port_set_trunk(chip, port, false, 0);
err_unlock:
	mv88e6xxx_reg_unlock(chip);
	return err;
}

static int mv88e6xxx_port_lag_leave(struct dsa_switch *ds, int port,
				    struct net_device *lag)
{
	struct mv88e6xxx_chip *chip = ds->priv;
	int err_sync, err_trunk;

	mv88e6xxx_reg_lock(chip);
	err_sync = mv88e6xxx_lag_sync_masks_map(ds, lag);
	err_trunk = mv88e6xxx_port_set_trunk(chip, port, false, 0);
	mv88e6xxx_reg_unlock(chip);
	return err_sync ? : err_trunk;
}

static int mv88e6xxx_crosschip_lag_change(struct dsa_switch *ds, int sw_index,
					  int port)
{
	struct mv88e6xxx_chip *chip = ds->priv;
	int err;

	mv88e6xxx_reg_lock(chip);
	err = mv88e6xxx_lag_sync_masks(ds);
	mv88e6xxx_reg_unlock(chip);
	return err;
}

static int mv88e6xxx_crosschip_lag_join(struct dsa_switch *ds, int sw_index,
					int port, struct net_device *lag,
					struct netdev_lag_upper_info *info)
{
	struct mv88e6xxx_chip *chip = ds->priv;
	int err;

	if (!mv88e6xxx_lag_can_offload(ds, lag, info))
		return -EOPNOTSUPP;

	mv88e6xxx_reg_lock(chip);

	err = mv88e6xxx_lag_sync_masks_map(ds, lag);
	if (err)
		goto unlock;

	err = mv88e6xxx_pvt_map(chip, sw_index, port);

unlock:
	mv88e6xxx_reg_unlock(chip);
	return err;
}

static int mv88e6xxx_crosschip_lag_leave(struct dsa_switch *ds, int sw_index,
					 int port, struct net_device *lag)
{
	struct mv88e6xxx_chip *chip = ds->priv;
	int err_sync, err_pvt;

	mv88e6xxx_reg_lock(chip);
	err_sync = mv88e6xxx_lag_sync_masks_map(ds, lag);
	err_pvt = mv88e6xxx_pvt_map(chip, sw_index, port);
	mv88e6xxx_reg_unlock(chip);
	return err_sync ? : err_pvt;
}

static const struct dsa_switch_ops mv88e6xxx_switch_ops = {
	.get_tag_protocol	= mv88e6xxx_get_tag_protocol,
	.setup			= mv88e6xxx_setup,
@@ -5416,6 +5698,12 @@ static const struct dsa_switch_ops mv88e6xxx_switch_ops = {
	.devlink_param_get	= mv88e6xxx_devlink_param_get,
	.devlink_param_set	= mv88e6xxx_devlink_param_set,
	.devlink_info_get	= mv88e6xxx_devlink_info_get,
	.port_lag_change	= mv88e6xxx_port_lag_change,
	.port_lag_join		= mv88e6xxx_port_lag_join,
	.port_lag_leave		= mv88e6xxx_port_lag_leave,
	.crosschip_lag_change	= mv88e6xxx_crosschip_lag_change,
	.crosschip_lag_join	= mv88e6xxx_crosschip_lag_join,
	.crosschip_lag_leave	= mv88e6xxx_crosschip_lag_leave,
};

static int mv88e6xxx_register_switch(struct mv88e6xxx_chip *chip)
@@ -5435,6 +5723,12 @@ static int mv88e6xxx_register_switch(struct mv88e6xxx_chip *chip)
	ds->ageing_time_min = chip->info->age_time_coeff;
	ds->ageing_time_max = chip->info->age_time_coeff * U8_MAX;

	/* Some chips support up to 32, but that requires enabling the
	 * 5-bit port mode, which we do not support. 640k^W16 ought to
	 * be enough for anyone.
	 */
	ds->num_lag_ids = 16;

	dev_set_drvdata(dev, ds);

	return dsa_register_switch(ds);
+4 −4
Original line number Diff line number Diff line
@@ -126,7 +126,7 @@ int mv88e6xxx_g2_device_mapping_write(struct mv88e6xxx_chip *chip, int target,

/* Offset 0x07: Trunk Mask Table register */

static int mv88e6xxx_g2_trunk_mask_write(struct mv88e6xxx_chip *chip, int num,
int mv88e6xxx_g2_trunk_mask_write(struct mv88e6xxx_chip *chip, int num,
				  bool hash, u16 mask)
{
	u16 val = (num << 12) | (mask & mv88e6xxx_port_mask(chip));
@@ -140,7 +140,7 @@ static int mv88e6xxx_g2_trunk_mask_write(struct mv88e6xxx_chip *chip, int num,

/* Offset 0x08: Trunk Mapping Table register */

static int mv88e6xxx_g2_trunk_mapping_write(struct mv88e6xxx_chip *chip, int id,
int mv88e6xxx_g2_trunk_mapping_write(struct mv88e6xxx_chip *chip, int id,
				     u16 map)
{
	const u16 port_mask = BIT(mv88e6xxx_num_ports(chip)) - 1;
+5 −0
Original line number Diff line number Diff line
@@ -101,6 +101,7 @@
#define MV88E6XXX_G2_PVT_ADDR_OP_WRITE_PVLAN	0x3000
#define MV88E6XXX_G2_PVT_ADDR_OP_READ		0x4000
#define MV88E6XXX_G2_PVT_ADDR_PTR_MASK		0x01ff
#define MV88E6XXX_G2_PVT_ADRR_DEV_TRUNK		0x1f

/* Offset 0x0C: Cross-chip Port VLAN Data Register */
#define MV88E6XXX_G2_PVT_DATA		0x0c
@@ -345,6 +346,10 @@ int mv88e6352_g2_mgmt_rsvd2cpu(struct mv88e6xxx_chip *chip);

int mv88e6xxx_g2_pot_clear(struct mv88e6xxx_chip *chip);

int mv88e6xxx_g2_trunk_mask_write(struct mv88e6xxx_chip *chip, int num,
				  bool hash, u16 mask);
int mv88e6xxx_g2_trunk_mapping_write(struct mv88e6xxx_chip *chip, int id,
				     u16 map);
int mv88e6xxx_g2_trunk_clear(struct mv88e6xxx_chip *chip);

int mv88e6xxx_g2_device_mapping_write(struct mv88e6xxx_chip *chip, int target,
+21 −0
Original line number Diff line number Diff line
@@ -851,6 +851,27 @@ int mv88e6xxx_port_set_message_port(struct mv88e6xxx_chip *chip, int port,
	return mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_CTL1, val);
}

int mv88e6xxx_port_set_trunk(struct mv88e6xxx_chip *chip, int port,
			     bool trunk, u8 id)
{
	u16 val;
	int err;

	err = mv88e6xxx_port_read(chip, port, MV88E6XXX_PORT_CTL1, &val);
	if (err)
		return err;

	val &= ~MV88E6XXX_PORT_CTL1_TRUNK_ID_MASK;

	if (trunk)
		val |= MV88E6XXX_PORT_CTL1_TRUNK_PORT |
			(id << MV88E6XXX_PORT_CTL1_TRUNK_ID_SHIFT);
	else
		val &= ~MV88E6XXX_PORT_CTL1_TRUNK_PORT;

	return mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_CTL1, val);
}

/* Offset 0x06: Port Based VLAN Map */

int mv88e6xxx_port_set_vlan_map(struct mv88e6xxx_chip *chip, int port, u16 map)
Loading