Commit 82e94d41 authored by Jakub Kicinski's avatar Jakub Kicinski
Browse files

Merge branch 'net-bridge-multiple-spanning-trees'

Tobias Waldekranz says:

====================
net: bridge: Multiple Spanning Trees

The bridge has had per-VLAN STP support for a while now, since:

https://lore.kernel.org/netdev/20200124114022.10883-1-nikolay@cumulusnetworks.com/

The current implementation has some problems:

- The mapping from VLAN to STP state is fixed as 1:1, i.e. each VLAN
  is managed independently. This is awkward from an MSTP (802.1Q-2018,
  Clause 13.5) point of view, where the model is that multiple VLANs
  are grouped into MST instances.

  Because of the way that the standard is written, presumably, this is
  also reflected in hardware implementations. It is not uncommon for a
  switch to support the full 4k range of VIDs, but that the pool of
  MST instances is much smaller. Some examples:

  Marvell LinkStreet (mv88e6xxx): 4k VLANs, but only 64 MSTIs
  Marvell Prestera: 4k VLANs, but only 128 MSTIs
  Microchip SparX-5i: 4k VLANs, but only 128 MSTIs

- By default, the feature is enabled, and there is no way to disable
  it. This makes it hard to add offloading in a backwards compatible
  way, since any underlying switchdevs have no way to refuse the
  function if the hardware does not support it

- The port-global STP state has precedence over per-VLAN states. In
  MSTP, as far as I understand it, all VLANs will use the common
  spanning tree (CST) by default - through traffic engineering you can
  then optimize your network to group subsets of VLANs to use
  different trees (MSTI). To my understanding, the way this is
  typically managed in silicon is roughly:

  Incoming packet:
  .----.----.--------------.----.-------------
  | DA | SA | 802.1Q VID=X | ET | Payload ...
  '----'----'--------------'----'-------------
                        |
                        '->|\     .----------------------------.
                           | +--> | VID | Members | ... | MSTI |
                   PVID -->|/     |-----|---------|-----|------|
                                  |   1 | 0001001 | ... |    0 |
                                  |   2 | 0001010 | ... |   10 |
                                  |   3 | 0001100 | ... |   10 |
                                  '----------------------------'
                                                             |
                               .-----------------------------'
                               |  .------------------------.
                               '->| MSTI | Fwding | Lrning |
                                  |------|--------|--------|
                                  |    0 | 111110 | 111110 |
                                  |   10 | 110111 | 110111 |
                                  '------------------------'

  What this is trying to show is that the STP state (whether MSTP is
  used, or ye olde STP) is always accessed via the VLAN table. If STP
  is running, all MSTI pointers in that table will reference the same
  index in the STP stable - if MSTP is running, some VLANs may point
  to other trees (like in this example).

  The fact that in the Linux bridge, the global state (think: index 0
  in most hardware implementations) is supposed to override the
  per-VLAN state, is very awkward to offload. In effect, this means
  that when the global state changes to blocking, drivers will have to
  iterate over all MSTIs in use, and alter them all to match. This
  also means that you have to cache whether the hardware state is
  currently tracking the global state or the per-VLAN state. In the
  first case, you also have to cache the per-VLAN state so that you
  can restore it if the global state transitions back to forwarding.

This series adds a new mst_enable bridge setting (as suggested by Nik)
that can only be changed when no VLANs are configured on the
bridge. Enabling this mode has the following effect:

- The port-global STP state is used to represent the CST (Common
  Spanning Tree) (1/15)

- Ingress STP filtering is deferred until the frame's VLAN has been
  resolved (1/15)

- The preexisting per-VLAN states can no longer be controlled directly
  (1/15). They are instead placed under the MST module's control,
  which is managed using a new netlink interface (described in 3/15)

- VLANs can br mapped to MSTIs in an arbitrary M:N fashion, using a
  new global VLAN option (2/15)

Switchdev notifications are added so that a driver can track:
- MST enabled state
- VID to MSTI mappings
- MST port states

An offloading implementation is this provided for mv88e6xxx.
====================

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


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parents 54744510 acaf4d2e
Loading
Loading
Loading
Loading
+296 −8
Original line number Diff line number Diff line
@@ -1667,24 +1667,31 @@ static int mv88e6xxx_pvt_setup(struct mv88e6xxx_chip *chip)
	return 0;
}

static void mv88e6xxx_port_fast_age(struct dsa_switch *ds, int port)
static int mv88e6xxx_port_fast_age_fid(struct mv88e6xxx_chip *chip, int port,
				       u16 fid)
{
	struct mv88e6xxx_chip *chip = ds->priv;
	int err;

	if (dsa_to_port(ds, port)->lag)
	if (dsa_to_port(chip->ds, port)->lag)
		/* Hardware is incapable of fast-aging a LAG through a
		 * regular ATU move operation. Until we have something
		 * more fancy in place this is a no-op.
		 */
		return;
		return -EOPNOTSUPP;

	return mv88e6xxx_g1_atu_remove(chip, fid, port, false);
}

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

	mv88e6xxx_reg_lock(chip);
	err = mv88e6xxx_g1_atu_remove(chip, 0, port, false);
	err = mv88e6xxx_port_fast_age_fid(chip, port, 0);
	mv88e6xxx_reg_unlock(chip);

	if (err)
		dev_err(ds->dev, "p%d: failed to flush ATU\n", port);
		dev_err(chip->ds->dev, "p%d: failed to flush ATU: %d\n",
			port, err);
}

static int mv88e6xxx_vtu_setup(struct mv88e6xxx_chip *chip)
@@ -1791,6 +1798,187 @@ static int mv88e6xxx_atu_new(struct mv88e6xxx_chip *chip, u16 *fid)
	return mv88e6xxx_g1_atu_flush(chip, *fid, true);
}

static int mv88e6xxx_stu_loadpurge(struct mv88e6xxx_chip *chip,
				   struct mv88e6xxx_stu_entry *entry)
{
	if (!chip->info->ops->stu_loadpurge)
		return -EOPNOTSUPP;

	return chip->info->ops->stu_loadpurge(chip, entry);
}

static int mv88e6xxx_stu_setup(struct mv88e6xxx_chip *chip)
{
	struct mv88e6xxx_stu_entry stu = {
		.valid = true,
		.sid = 0
	};

	if (!mv88e6xxx_has_stu(chip))
		return 0;

	/* Make sure that SID 0 is always valid. This is used by VTU
	 * entries that do not make use of the STU, e.g. when creating
	 * a VLAN upper on a port that is also part of a VLAN
	 * filtering bridge.
	 */
	return mv88e6xxx_stu_loadpurge(chip, &stu);
}

static int mv88e6xxx_sid_get(struct mv88e6xxx_chip *chip, u8 *sid)
{
	DECLARE_BITMAP(busy, MV88E6XXX_N_SID) = { 0 };
	struct mv88e6xxx_mst *mst;

	__set_bit(0, busy);

	list_for_each_entry(mst, &chip->msts, node)
		__set_bit(mst->stu.sid, busy);

	*sid = find_first_zero_bit(busy, MV88E6XXX_N_SID);

	return (*sid >= mv88e6xxx_max_sid(chip)) ? -ENOSPC : 0;
}

static int mv88e6xxx_mst_put(struct mv88e6xxx_chip *chip, u8 sid)
{
	struct mv88e6xxx_mst *mst, *tmp;
	int err;

	if (!sid)
		return 0;

	list_for_each_entry_safe(mst, tmp, &chip->msts, node) {
		if (mst->stu.sid != sid)
			continue;

		if (!refcount_dec_and_test(&mst->refcnt))
			return 0;

		mst->stu.valid = false;
		err = mv88e6xxx_stu_loadpurge(chip, &mst->stu);
		if (err) {
			refcount_set(&mst->refcnt, 1);
			return err;
		}

		list_del(&mst->node);
		kfree(mst);
		return 0;
	}

	return -ENOENT;
}

static int mv88e6xxx_mst_get(struct mv88e6xxx_chip *chip, struct net_device *br,
			     u16 msti, u8 *sid)
{
	struct mv88e6xxx_mst *mst;
	int err, i;

	if (!mv88e6xxx_has_stu(chip)) {
		err = -EOPNOTSUPP;
		goto err;
	}

	if (!msti) {
		*sid = 0;
		return 0;
	}

	list_for_each_entry(mst, &chip->msts, node) {
		if (mst->br == br && mst->msti == msti) {
			refcount_inc(&mst->refcnt);
			*sid = mst->stu.sid;
			return 0;
		}
	}

	err = mv88e6xxx_sid_get(chip, sid);
	if (err)
		goto err;

	mst = kzalloc(sizeof(*mst), GFP_KERNEL);
	if (!mst) {
		err = -ENOMEM;
		goto err;
	}

	INIT_LIST_HEAD(&mst->node);
	refcount_set(&mst->refcnt, 1);
	mst->br = br;
	mst->msti = msti;
	mst->stu.valid = true;
	mst->stu.sid = *sid;

	/* The bridge starts out all ports in the disabled state. But
	 * a STU state of disabled means to go by the port-global
	 * state. So we set all user port's initial state to blocking,
	 * to match the bridge's behavior.
	 */
	for (i = 0; i < mv88e6xxx_num_ports(chip); i++)
		mst->stu.state[i] = dsa_is_user_port(chip->ds, i) ?
			MV88E6XXX_PORT_CTL0_STATE_BLOCKING :
			MV88E6XXX_PORT_CTL0_STATE_DISABLED;

	err = mv88e6xxx_stu_loadpurge(chip, &mst->stu);
	if (err)
		goto err_free;

	list_add_tail(&mst->node, &chip->msts);
	return 0;

err_free:
	kfree(mst);
err:
	return err;
}

static int mv88e6xxx_port_mst_state_set(struct dsa_switch *ds, int port,
					const struct switchdev_mst_state *st)
{
	struct dsa_port *dp = dsa_to_port(ds, port);
	struct mv88e6xxx_chip *chip = ds->priv;
	struct mv88e6xxx_mst *mst;
	u8 state;
	int err;

	if (!mv88e6xxx_has_stu(chip))
		return -EOPNOTSUPP;

	switch (st->state) {
	case BR_STATE_DISABLED:
	case BR_STATE_BLOCKING:
	case BR_STATE_LISTENING:
		state = MV88E6XXX_PORT_CTL0_STATE_BLOCKING;
		break;
	case BR_STATE_LEARNING:
		state = MV88E6XXX_PORT_CTL0_STATE_LEARNING;
		break;
	case BR_STATE_FORWARDING:
		state = MV88E6XXX_PORT_CTL0_STATE_FORWARDING;
		break;
	default:
		return -EINVAL;
	}

	list_for_each_entry(mst, &chip->msts, node) {
		if (mst->br == dsa_port_bridge_dev_get(dp) &&
		    mst->msti == st->msti) {
			if (mst->stu.state[port] == state)
				return 0;

			mst->stu.state[port] = state;
			mv88e6xxx_reg_lock(chip);
			err = mv88e6xxx_stu_loadpurge(chip, &mst->stu);
			mv88e6xxx_reg_unlock(chip);
			return err;
		}
	}

	return -ENOENT;
}

static int mv88e6xxx_port_check_hw_vlan(struct dsa_switch *ds, int port,
					u16 vid)
{
@@ -2410,6 +2598,12 @@ static int mv88e6xxx_port_vlan_leave(struct mv88e6xxx_chip *chip,
	if (err)
		return err;

	if (!vlan.valid) {
		err = mv88e6xxx_mst_put(chip, vlan.sid);
		if (err)
			return err;
	}

	return mv88e6xxx_g1_atu_remove(chip, vlan.fid, port, false);
}

@@ -2455,6 +2649,69 @@ static int mv88e6xxx_port_vlan_del(struct dsa_switch *ds, int port,
	return err;
}

static int mv88e6xxx_port_vlan_fast_age(struct dsa_switch *ds, int port, u16 vid)
{
	struct mv88e6xxx_chip *chip = ds->priv;
	struct mv88e6xxx_vtu_entry vlan;
	int err;

	mv88e6xxx_reg_lock(chip);

	err = mv88e6xxx_vtu_get(chip, vid, &vlan);
	if (err)
		goto unlock;

	err = mv88e6xxx_port_fast_age_fid(chip, port, vlan.fid);

unlock:
	mv88e6xxx_reg_unlock(chip);

	return err;
}

static int mv88e6xxx_vlan_msti_set(struct dsa_switch *ds,
				   struct dsa_bridge bridge,
				   const struct switchdev_vlan_msti *msti)
{
	struct mv88e6xxx_chip *chip = ds->priv;
	struct mv88e6xxx_vtu_entry vlan;
	u8 old_sid, new_sid;
	int err;

	mv88e6xxx_reg_lock(chip);

	err = mv88e6xxx_vtu_get(chip, msti->vid, &vlan);
	if (err)
		goto unlock;

	if (!vlan.valid) {
		err = -EINVAL;
		goto unlock;
	}

	old_sid = vlan.sid;

	err = mv88e6xxx_mst_get(chip, bridge.dev, msti->msti, &new_sid);
	if (err)
		goto unlock;

	if (new_sid != old_sid) {
		vlan.sid = new_sid;

		err = mv88e6xxx_vtu_loadpurge(chip, &vlan);
		if (err) {
			mv88e6xxx_mst_put(chip, new_sid);
			goto unlock;
		}
	}

	err = mv88e6xxx_mst_put(chip, old_sid);

unlock:
	mv88e6xxx_reg_unlock(chip);
	return err;
}

static int mv88e6xxx_port_fdb_add(struct dsa_switch *ds, int port,
				  const unsigned char *addr, u16 vid,
				  struct dsa_db db)
@@ -3427,6 +3684,13 @@ static int mv88e6xxx_setup(struct dsa_switch *ds)
	if (err)
		goto unlock;

	/* Must be called after mv88e6xxx_vtu_setup (which flushes the
	 * VTU, thereby also flushing the STU).
	 */
	err = mv88e6xxx_stu_setup(chip);
	if (err)
		goto unlock;

	/* Setup Switch Port Registers */
	for (i = 0; i < mv88e6xxx_num_ports(chip); i++) {
		if (dsa_is_unused_port(ds, i))
@@ -3882,6 +4146,8 @@ static const struct mv88e6xxx_ops mv88e6097_ops = {
	.vtu_getnext = mv88e6352_g1_vtu_getnext,
	.vtu_loadpurge = mv88e6352_g1_vtu_loadpurge,
	.phylink_get_caps = mv88e6095_phylink_get_caps,
	.stu_getnext = mv88e6352_g1_stu_getnext,
	.stu_loadpurge = mv88e6352_g1_stu_loadpurge,
	.set_max_frame_size = mv88e6185_g1_set_max_frame_size,
};

@@ -4968,6 +5234,8 @@ static const struct mv88e6xxx_ops mv88e6352_ops = {
	.atu_set_hash = mv88e6165_g1_atu_set_hash,
	.vtu_getnext = mv88e6352_g1_vtu_getnext,
	.vtu_loadpurge = mv88e6352_g1_vtu_loadpurge,
	.stu_getnext = mv88e6352_g1_stu_getnext,
	.stu_loadpurge = mv88e6352_g1_stu_loadpurge,
	.serdes_get_lane = mv88e6352_serdes_get_lane,
	.serdes_pcs_get_state = mv88e6352_serdes_pcs_get_state,
	.serdes_pcs_config = mv88e6352_serdes_pcs_config,
@@ -5033,6 +5301,8 @@ static const struct mv88e6xxx_ops mv88e6390_ops = {
	.atu_set_hash = mv88e6165_g1_atu_set_hash,
	.vtu_getnext = mv88e6390_g1_vtu_getnext,
	.vtu_loadpurge = mv88e6390_g1_vtu_loadpurge,
	.stu_getnext = mv88e6390_g1_stu_getnext,
	.stu_loadpurge = mv88e6390_g1_stu_loadpurge,
	.serdes_power = mv88e6390_serdes_power,
	.serdes_get_lane = mv88e6390_serdes_get_lane,
	/* Check status register pause & lpa register */
@@ -5098,6 +5368,8 @@ static const struct mv88e6xxx_ops mv88e6390x_ops = {
	.atu_set_hash = mv88e6165_g1_atu_set_hash,
	.vtu_getnext = mv88e6390_g1_vtu_getnext,
	.vtu_loadpurge = mv88e6390_g1_vtu_loadpurge,
	.stu_getnext = mv88e6390_g1_stu_getnext,
	.stu_loadpurge = mv88e6390_g1_stu_loadpurge,
	.serdes_power = mv88e6390_serdes_power,
	.serdes_get_lane = mv88e6390x_serdes_get_lane,
	.serdes_pcs_get_state = mv88e6390_serdes_pcs_get_state,
@@ -5166,6 +5438,8 @@ static const struct mv88e6xxx_ops mv88e6393x_ops = {
	.atu_set_hash = mv88e6165_g1_atu_set_hash,
	.vtu_getnext = mv88e6390_g1_vtu_getnext,
	.vtu_loadpurge = mv88e6390_g1_vtu_loadpurge,
	.stu_getnext = mv88e6390_g1_stu_getnext,
	.stu_loadpurge = mv88e6390_g1_stu_loadpurge,
	.serdes_power = mv88e6393x_serdes_power,
	.serdes_get_lane = mv88e6393x_serdes_get_lane,
	.serdes_pcs_get_state = mv88e6393x_serdes_pcs_get_state,
@@ -5234,6 +5508,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
		.num_ports = 11,
		.num_internal_phys = 8,
		.max_vid = 4095,
		.max_sid = 63,
		.port_base_addr = 0x10,
		.phy_base_addr = 0x0,
		.global1_addr = 0x1b,
@@ -5487,6 +5762,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
		.num_internal_phys = 9,
		.num_gpio = 16,
		.max_vid = 8191,
		.max_sid = 63,
		.port_base_addr = 0x0,
		.phy_base_addr = 0x0,
		.global1_addr = 0x1b,
@@ -5510,6 +5786,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
		.num_internal_phys = 9,
		.num_gpio = 16,
		.max_vid = 8191,
		.max_sid = 63,
		.port_base_addr = 0x0,
		.phy_base_addr = 0x0,
		.global1_addr = 0x1b,
@@ -5532,6 +5809,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
		.num_ports = 11,	/* 10 + Z80 */
		.num_internal_phys = 9,
		.max_vid = 8191,
		.max_sid = 63,
		.port_base_addr = 0x0,
		.phy_base_addr = 0x0,
		.global1_addr = 0x1b,
@@ -5554,6 +5832,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
		.num_ports = 11,	/* 10 + Z80 */
		.num_internal_phys = 9,
		.max_vid = 8191,
		.max_sid = 63,
		.port_base_addr = 0x0,
		.phy_base_addr = 0x0,
		.global1_addr = 0x1b,
@@ -5576,6 +5855,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
		.num_ports = 11,	/* 10 + Z80 */
		.num_internal_phys = 9,
		.max_vid = 8191,
		.max_sid = 63,
		.port_base_addr = 0x0,
		.phy_base_addr = 0x0,
		.global1_addr = 0x1b,
@@ -5815,6 +6095,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
		.num_internal_phys = 5,
		.num_gpio = 15,
		.max_vid = 4095,
		.max_sid = 63,
		.port_base_addr = 0x10,
		.phy_base_addr = 0x0,
		.global1_addr = 0x1b,
@@ -5839,6 +6120,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
		.num_internal_phys = 9,
		.num_gpio = 16,
		.max_vid = 8191,
		.max_sid = 63,
		.port_base_addr = 0x0,
		.phy_base_addr = 0x0,
		.global1_addr = 0x1b,
@@ -5863,6 +6145,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
		.num_internal_phys = 9,
		.num_gpio = 16,
		.max_vid = 8191,
		.max_sid = 63,
		.port_base_addr = 0x0,
		.phy_base_addr = 0x0,
		.global1_addr = 0x1b,
@@ -5886,6 +6169,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = {
		.num_ports = 11,	/* 10 + Z80 */
		.num_internal_phys = 9,
		.max_vid = 8191,
		.max_sid = 63,
		.port_base_addr = 0x0,
		.phy_base_addr = 0x0,
		.global1_addr = 0x1b,
@@ -5954,6 +6238,7 @@ static struct mv88e6xxx_chip *mv88e6xxx_alloc_chip(struct device *dev)
	mutex_init(&chip->reg_lock);
	INIT_LIST_HEAD(&chip->mdios);
	idr_init(&chip->policies);
	INIT_LIST_HEAD(&chip->msts);

	return chip;
}
@@ -6486,10 +6771,13 @@ static const struct dsa_switch_ops mv88e6xxx_switch_ops = {
	.port_pre_bridge_flags	= mv88e6xxx_port_pre_bridge_flags,
	.port_bridge_flags	= mv88e6xxx_port_bridge_flags,
	.port_stp_state_set	= mv88e6xxx_port_stp_state_set,
	.port_mst_state_set	= mv88e6xxx_port_mst_state_set,
	.port_fast_age		= mv88e6xxx_port_fast_age,
	.port_vlan_fast_age	= mv88e6xxx_port_vlan_fast_age,
	.port_vlan_filtering	= mv88e6xxx_port_vlan_filtering,
	.port_vlan_add		= mv88e6xxx_port_vlan_add,
	.port_vlan_del		= mv88e6xxx_port_vlan_del,
	.vlan_msti_set		= mv88e6xxx_vlan_msti_set,
	.port_fdb_add           = mv88e6xxx_port_fdb_add,
	.port_fdb_del           = mv88e6xxx_port_fdb_del,
	.port_fdb_dump          = mv88e6xxx_port_fdb_dump,
+38 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@

#define EDSA_HLEN		8
#define MV88E6XXX_N_FID		4096
#define MV88E6XXX_N_SID		64

#define MV88E6XXX_FID_STANDALONE	0
#define MV88E6XXX_FID_BRIDGED		1
@@ -130,6 +131,7 @@ struct mv88e6xxx_info {
	unsigned int num_internal_phys;
	unsigned int num_gpio;
	unsigned int max_vid;
	unsigned int max_sid;
	unsigned int port_base_addr;
	unsigned int phy_base_addr;
	unsigned int global1_addr;
@@ -181,6 +183,12 @@ struct mv88e6xxx_vtu_entry {
	bool	valid;
	bool	policy;
	u8	member[DSA_MAX_PORTS];
	u8	state[DSA_MAX_PORTS];	/* Older silicon has no STU */
};

struct mv88e6xxx_stu_entry {
	u8	sid;
	bool	valid;
	u8	state[DSA_MAX_PORTS];
};

@@ -279,6 +287,7 @@ enum mv88e6xxx_region_id {
	MV88E6XXX_REGION_GLOBAL2,
	MV88E6XXX_REGION_ATU,
	MV88E6XXX_REGION_VTU,
	MV88E6XXX_REGION_STU,
	MV88E6XXX_REGION_PVT,

	_MV88E6XXX_REGION_MAX,
@@ -288,6 +297,16 @@ struct mv88e6xxx_region_priv {
	enum mv88e6xxx_region_id id;
};

struct mv88e6xxx_mst {
	struct list_head node;

	refcount_t refcnt;
	struct net_device *br;
	u16 msti;

	struct mv88e6xxx_stu_entry stu;
};

struct mv88e6xxx_chip {
	const struct mv88e6xxx_info *info;

@@ -388,6 +407,9 @@ struct mv88e6xxx_chip {

	/* devlink regions */
	struct devlink_region *regions[_MV88E6XXX_REGION_MAX];

	/* Bridge MST to SID mappings */
	struct list_head msts;
};

struct mv88e6xxx_bus_ops {
@@ -602,6 +624,12 @@ struct mv88e6xxx_ops {
	int (*vtu_loadpurge)(struct mv88e6xxx_chip *chip,
			     struct mv88e6xxx_vtu_entry *entry);

	/* Spanning Tree Unit operations */
	int (*stu_getnext)(struct mv88e6xxx_chip *chip,
			   struct mv88e6xxx_stu_entry *entry);
	int (*stu_loadpurge)(struct mv88e6xxx_chip *chip,
			     struct mv88e6xxx_stu_entry *entry);

	/* GPIO operations */
	const struct mv88e6xxx_gpio_ops *gpio_ops;

@@ -700,6 +728,11 @@ struct mv88e6xxx_hw_stat {
	int type;
};

static inline bool mv88e6xxx_has_stu(struct mv88e6xxx_chip *chip)
{
	return chip->info->max_sid > 0;
}

static inline bool mv88e6xxx_has_pvt(struct mv88e6xxx_chip *chip)
{
	return chip->info->pvt;
@@ -730,6 +763,11 @@ static inline unsigned int mv88e6xxx_max_vid(struct mv88e6xxx_chip *chip)
	return chip->info->max_vid;
}

static inline unsigned int mv88e6xxx_max_sid(struct mv88e6xxx_chip *chip)
{
	return chip->info->max_sid;
}

static inline u16 mv88e6xxx_port_mask(struct mv88e6xxx_chip *chip)
{
	return GENMASK((s32)mv88e6xxx_num_ports(chip) - 1, 0);
+94 −0
Original line number Diff line number Diff line
@@ -503,6 +503,85 @@ static int mv88e6xxx_region_vtu_snapshot(struct devlink *dl,
	return 0;
}

/**
 * struct mv88e6xxx_devlink_stu_entry - Devlink STU entry
 * @sid:   Global1/3:   SID, unknown filters and learning.
 * @vid:   Global1/6:   Valid bit.
 * @data:  Global1/7-9: Membership data and priority override.
 * @resvd: Reserved. In case we forgot something.
 *
 * The STU entry format varies between chipset generations. Peridot
 * and Amethyst packs the STU data into Global1/7-8. Older silicon
 * spreads the information across all three VTU data registers -
 * inheriting the layout of even older hardware that had no STU at
 * all. Since this is a low-level debug interface, copy all data
 * verbatim and defer parsing to the consumer.
 */
struct mv88e6xxx_devlink_stu_entry {
	u16 sid;
	u16 vid;
	u16 data[3];
	u16 resvd;
};

static int mv88e6xxx_region_stu_snapshot(struct devlink *dl,
					 const struct devlink_region_ops *ops,
					 struct netlink_ext_ack *extack,
					 u8 **data)
{
	struct mv88e6xxx_devlink_stu_entry *table, *entry;
	struct dsa_switch *ds = dsa_devlink_to_ds(dl);
	struct mv88e6xxx_chip *chip = ds->priv;
	struct mv88e6xxx_stu_entry stu;
	int err;

	table = kcalloc(mv88e6xxx_max_sid(chip) + 1,
			sizeof(struct mv88e6xxx_devlink_stu_entry),
			GFP_KERNEL);
	if (!table)
		return -ENOMEM;

	entry = table;
	stu.sid = mv88e6xxx_max_sid(chip);
	stu.valid = false;

	mv88e6xxx_reg_lock(chip);

	do {
		err = mv88e6xxx_g1_stu_getnext(chip, &stu);
		if (err)
			break;

		if (!stu.valid)
			break;

		err = err ? : mv88e6xxx_g1_read(chip, MV88E6352_G1_VTU_SID,
						&entry->sid);
		err = err ? : mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_VID,
						&entry->vid);
		err = err ? : mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_DATA1,
						&entry->data[0]);
		err = err ? : mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_DATA2,
						&entry->data[1]);
		err = err ? : mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_DATA3,
						&entry->data[2]);
		if (err)
			break;

		entry++;
	} while (stu.sid < mv88e6xxx_max_sid(chip));

	mv88e6xxx_reg_unlock(chip);

	if (err) {
		kfree(table);
		return err;
	}

	*data = (u8 *)table;
	return 0;
}

static int mv88e6xxx_region_pvt_snapshot(struct devlink *dl,
					 const struct devlink_region_ops *ops,
					 struct netlink_ext_ack *extack,
@@ -605,6 +684,12 @@ static struct devlink_region_ops mv88e6xxx_region_vtu_ops = {
	.destructor = kfree,
};

static struct devlink_region_ops mv88e6xxx_region_stu_ops = {
	.name = "stu",
	.snapshot = mv88e6xxx_region_stu_snapshot,
	.destructor = kfree,
};

static struct devlink_region_ops mv88e6xxx_region_pvt_ops = {
	.name = "pvt",
	.snapshot = mv88e6xxx_region_pvt_snapshot,
@@ -640,6 +725,11 @@ static struct mv88e6xxx_region mv88e6xxx_regions[] = {
		.ops = &mv88e6xxx_region_vtu_ops
	  /* calculated at runtime */
	},
	[MV88E6XXX_REGION_STU] = {
		.ops = &mv88e6xxx_region_stu_ops,
		.cond = mv88e6xxx_has_stu,
	  /* calculated at runtime */
	},
	[MV88E6XXX_REGION_PVT] = {
		.ops = &mv88e6xxx_region_pvt_ops,
		.size = MV88E6XXX_MAX_PVT_ENTRIES * sizeof(u16),
@@ -706,6 +796,10 @@ int mv88e6xxx_setup_devlink_regions_global(struct dsa_switch *ds)
			size = (mv88e6xxx_max_vid(chip) + 1) *
				sizeof(struct mv88e6xxx_devlink_vtu_entry);
			break;
		case MV88E6XXX_REGION_STU:
			size = (mv88e6xxx_max_sid(chip) + 1) *
				sizeof(struct mv88e6xxx_devlink_stu_entry);
			break;
		}

		region = dsa_devlink_region_create(ds, ops, 1, size);
+10 −0
Original line number Diff line number Diff line
@@ -348,6 +348,16 @@ int mv88e6390_g1_vtu_getnext(struct mv88e6xxx_chip *chip,
int mv88e6390_g1_vtu_loadpurge(struct mv88e6xxx_chip *chip,
			       struct mv88e6xxx_vtu_entry *entry);
int mv88e6xxx_g1_vtu_flush(struct mv88e6xxx_chip *chip);
int mv88e6xxx_g1_stu_getnext(struct mv88e6xxx_chip *chip,
			     struct mv88e6xxx_stu_entry *entry);
int mv88e6352_g1_stu_getnext(struct mv88e6xxx_chip *chip,
			     struct mv88e6xxx_stu_entry *entry);
int mv88e6352_g1_stu_loadpurge(struct mv88e6xxx_chip *chip,
			       struct mv88e6xxx_stu_entry *entry);
int mv88e6390_g1_stu_getnext(struct mv88e6xxx_chip *chip,
			     struct mv88e6xxx_stu_entry *entry);
int mv88e6390_g1_stu_loadpurge(struct mv88e6xxx_chip *chip,
			       struct mv88e6xxx_stu_entry *entry);
int mv88e6xxx_g1_vtu_prob_irq_setup(struct mv88e6xxx_chip *chip);
void mv88e6xxx_g1_vtu_prob_irq_free(struct mv88e6xxx_chip *chip);
int mv88e6xxx_g1_atu_get_next(struct mv88e6xxx_chip *chip, u16 fid);
+176 −135

File changed.

Preview size limit exceeded, changes collapsed.

Loading