Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bb66be1c authored by David S. Miller's avatar David S. Miller
Browse files

Merge branch 'sf2_hwbridge'



Florian Fainelli says:

====================
net: dsa: integration with SWITCHDEV for HW bridging

This patch set provides the DSA and SWITCHDEV integration bits together and
modifies the bcm_sf2 driver accordingly such that it works properly with HW
bridging.

Changes in v3:

- add back the null pointer check in dsa_slave_br_port_mask from Guenter
- slightly rework patch 1 commit message not to mention the function name
  we add in patch 2

Changes in v2:

- avoid a race condition in how DSA network devices are created, patch from
  Guenter Roeck
- provide a consistent and work STP state once a port leaves the bridge
- retain a bridge device pointer to properly flag port/bridge membership
- properly flush the ARL (Address Resolution Logic) in bcm_sf2.c
- properly retain port membership when individually bringing devices up/down
  while they are members of a bridge

We discussed on the mailing-list the possibility of standardizing a "fdb_flush"
operation for DSA switch drivers, looking at the Marvell and Broadcom switches,
I am not convinced this is practical or diserable as the terminologies vary
here, but there is nothing preventing us from doing it later.

Many thanks to Guenter and Andrew for both testing and providing feedback.
====================

Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 92bf2008 12f460f2
Loading
Loading
Loading
Loading
+154 −1
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@
#include <linux/of_address.h>
#include <net/dsa.h>
#include <linux/ethtool.h>
#include <linux/if_bridge.h>

#include "bcm_sf2.h"
#include "bcm_sf2_regs.h"
@@ -299,10 +300,14 @@ static int bcm_sf2_port_setup(struct dsa_switch *ds, int port,
	if (port == 7)
		intrl2_1_mask_clear(priv, P_IRQ_MASK(P7_IRQ_OFF));

	/* Set this port, and only this one to be in the default VLAN */
	/* Set this port, and only this one to be in the default VLAN,
	 * if member of a bridge, restore its membership prior to
	 * bringing down this port.
	 */
	reg = core_readl(priv, CORE_PORT_VLAN_CTL_PORT(port));
	reg &= ~PORT_VLAN_CTRL_MASK;
	reg |= (1 << port);
	reg |= priv->port_sts[port].vlan_ctl_mask;
	core_writel(priv, reg, CORE_PORT_VLAN_CTL_PORT(port));

	bcm_sf2_imp_vlan_setup(ds, cpu_port);
@@ -400,6 +405,151 @@ static int bcm_sf2_sw_set_eee(struct dsa_switch *ds, int port,
	return 0;
}

/* Fast-ageing of ARL entries for a given port, equivalent to an ARL
 * flush for that port.
 */
static int bcm_sf2_sw_fast_age_port(struct dsa_switch  *ds, int port)
{
	struct bcm_sf2_priv *priv = ds_to_priv(ds);
	unsigned int timeout = 1000;
	u32 reg;

	core_writel(priv, port, CORE_FAST_AGE_PORT);

	reg = core_readl(priv, CORE_FAST_AGE_CTRL);
	reg |= EN_AGE_PORT | FAST_AGE_STR_DONE;
	core_writel(priv, reg, CORE_FAST_AGE_CTRL);

	do {
		reg = core_readl(priv, CORE_FAST_AGE_CTRL);
		if (!(reg & FAST_AGE_STR_DONE))
			break;

		cpu_relax();
	} while (timeout--);

	if (!timeout)
		return -ETIMEDOUT;

	return 0;
}

static int bcm_sf2_sw_br_join(struct dsa_switch *ds, int port,
			      u32 br_port_mask)
{
	struct bcm_sf2_priv *priv = ds_to_priv(ds);
	unsigned int i;
	u32 reg, p_ctl;

	p_ctl = core_readl(priv, CORE_PORT_VLAN_CTL_PORT(port));

	for (i = 0; i < priv->hw_params.num_ports; i++) {
		if (!((1 << i) & br_port_mask))
			continue;

		/* Add this local port to the remote port VLAN control
		 * membership and update the remote port bitmask
		 */
		reg = core_readl(priv, CORE_PORT_VLAN_CTL_PORT(i));
		reg |= 1 << port;
		core_writel(priv, reg, CORE_PORT_VLAN_CTL_PORT(i));
		priv->port_sts[i].vlan_ctl_mask = reg;

		p_ctl |= 1 << i;
	}

	/* Configure the local port VLAN control membership to include
	 * remote ports and update the local port bitmask
	 */
	core_writel(priv, p_ctl, CORE_PORT_VLAN_CTL_PORT(port));
	priv->port_sts[port].vlan_ctl_mask = p_ctl;

	return 0;
}

static int bcm_sf2_sw_br_leave(struct dsa_switch *ds, int port,
			       u32 br_port_mask)
{
	struct bcm_sf2_priv *priv = ds_to_priv(ds);
	unsigned int i;
	u32 reg, p_ctl;

	p_ctl = core_readl(priv, CORE_PORT_VLAN_CTL_PORT(port));

	for (i = 0; i < priv->hw_params.num_ports; i++) {
		/* Don't touch the remaining ports */
		if (!((1 << i) & br_port_mask))
			continue;

		reg = core_readl(priv, CORE_PORT_VLAN_CTL_PORT(i));
		reg &= ~(1 << port);
		core_writel(priv, reg, CORE_PORT_VLAN_CTL_PORT(i));
		priv->port_sts[port].vlan_ctl_mask = reg;

		/* Prevent self removal to preserve isolation */
		if (port != i)
			p_ctl &= ~(1 << i);
	}

	core_writel(priv, p_ctl, CORE_PORT_VLAN_CTL_PORT(port));
	priv->port_sts[port].vlan_ctl_mask = p_ctl;

	return 0;
}

static int bcm_sf2_sw_br_set_stp_state(struct dsa_switch *ds, int port,
				       u8 state)
{
	struct bcm_sf2_priv *priv = ds_to_priv(ds);
	u8 hw_state, cur_hw_state;
	int ret = 0;
	u32 reg;

	reg = core_readl(priv, CORE_G_PCTL_PORT(port));
	cur_hw_state = reg >> G_MISTP_STATE_SHIFT;

	switch (state) {
	case BR_STATE_DISABLED:
		hw_state = G_MISTP_DIS_STATE;
		break;
	case BR_STATE_LISTENING:
		hw_state = G_MISTP_LISTEN_STATE;
		break;
	case BR_STATE_LEARNING:
		hw_state = G_MISTP_LEARN_STATE;
		break;
	case BR_STATE_FORWARDING:
		hw_state = G_MISTP_FWD_STATE;
		break;
	case BR_STATE_BLOCKING:
		hw_state = G_MISTP_BLOCK_STATE;
		break;
	default:
		pr_err("%s: invalid STP state: %d\n", __func__, state);
		return -EINVAL;
	}

	/* Fast-age ARL entries if we are moving a port from Learning or
	 * Forwarding state to Disabled, Blocking or Listening state
	 */
	if (cur_hw_state != hw_state) {
		if (cur_hw_state & 4 && !(hw_state & 4)) {
			ret = bcm_sf2_sw_fast_age_port(ds, port);
			if (ret) {
				pr_err("%s: fast-ageing failed\n", __func__);
				return ret;
			}
		}
	}

	reg = core_readl(priv, CORE_G_PCTL_PORT(port));
	reg &= ~(G_MISTP_STATE_MASK << G_MISTP_STATE_SHIFT);
	reg |= hw_state;
	core_writel(priv, reg, CORE_G_PCTL_PORT(port));

	return 0;
}

static irqreturn_t bcm_sf2_switch_0_isr(int irq, void *dev_id)
{
	struct bcm_sf2_priv *priv = dev_id;
@@ -916,6 +1066,9 @@ static struct dsa_switch_driver bcm_sf2_switch_driver = {
	.port_disable		= bcm_sf2_port_disable,
	.get_eee		= bcm_sf2_sw_get_eee,
	.set_eee		= bcm_sf2_sw_set_eee,
	.port_join_bridge	= bcm_sf2_sw_br_join,
	.port_leave_bridge	= bcm_sf2_sw_br_leave,
	.port_stp_update	= bcm_sf2_sw_br_set_stp_state,
};

static int __init bcm_sf2_init(void)
+2 −0
Original line number Diff line number Diff line
@@ -46,6 +46,8 @@ struct bcm_sf2_port_status {
	unsigned int link;

	struct ethtool_eee eee;

	u32 vlan_ctl_mask;
};

struct bcm_sf2_priv {
+15 −0
Original line number Diff line number Diff line
@@ -163,6 +163,21 @@
#define  EN_CHIP_RST			(1 << 6)
#define  EN_SW_RESET			(1 << 4)

#define CORE_FAST_AGE_CTRL		0x00220
#define  EN_FAST_AGE_STATIC		(1 << 0)
#define  EN_AGE_DYNAMIC			(1 << 1)
#define  EN_AGE_PORT			(1 << 2)
#define  EN_AGE_VLAN			(1 << 3)
#define  EN_AGE_SPT			(1 << 4)
#define  EN_AGE_MCAST			(1 << 5)
#define  FAST_AGE_STR_DONE		(1 << 7)

#define CORE_FAST_AGE_PORT		0x00224
#define  AGE_PORT_MASK			0xf

#define CORE_FAST_AGE_VID		0x00228
#define  AGE_VID_MASK			0x3fff

#define CORE_LNKSTS			0x00400
#define  LNK_STS_MASK			0x1ff

+10 −0
Original line number Diff line number Diff line
@@ -275,6 +275,16 @@ struct dsa_switch_driver {
	int	(*get_regs_len)(struct dsa_switch *ds, int port);
	void	(*get_regs)(struct dsa_switch *ds, int port,
			    struct ethtool_regs *regs, void *p);

	/*
	 * Bridge integration
	 */
	int	(*port_join_bridge)(struct dsa_switch *ds, int port,
				    u32 br_port_mask);
	int	(*port_leave_bridge)(struct dsa_switch *ds, int port,
				     u32 br_port_mask);
	int	(*port_stp_update)(struct dsa_switch *ds, int port,
				   u8 state);
};

void register_switch_driver(struct dsa_switch_driver *type);
+1 −0
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@ config NET_DSA
	tristate
	depends on HAVE_NET_DSA
	select PHYLIB
	select NET_SWITCHDEV

if NET_DSA

Loading