Skip to content

Commit

Permalink
Merge branch 'sf2_hwbridge'
Browse files Browse the repository at this point in the history
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: David S. Miller <davem@davemloft.net>
  • Loading branch information
David S. Miller committed Feb 25, 2015
2 parents 92bf200 + 12f460f commit bb66be1
Show file tree
Hide file tree
Showing 8 changed files with 355 additions and 18 deletions.
155 changes: 154 additions & 1 deletion drivers/net/dsa/bcm_sf2.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions drivers/net/dsa/bcm_sf2.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ struct bcm_sf2_port_status {
unsigned int link;

struct ethtool_eee eee;

u32 vlan_ctl_mask;
};

struct bcm_sf2_priv {
Expand Down
15 changes: 15 additions & 0 deletions drivers/net/dsa/bcm_sf2_regs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
10 changes: 10 additions & 0 deletions include/net/dsa.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions net/dsa/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ config NET_DSA
tristate
depends on HAVE_NET_DSA
select PHYLIB
select NET_SWITCHDEV

if NET_DSA

Expand Down
17 changes: 10 additions & 7 deletions net/dsa/dsa.c
Original file line number Diff line number Diff line change
Expand Up @@ -314,19 +314,15 @@ dsa_switch_setup(struct dsa_switch_tree *dst, int index,
* Create network devices for physical switch ports.
*/
for (i = 0; i < DSA_MAX_PORTS; i++) {
struct net_device *slave_dev;

if (!(ds->phys_port_mask & (1 << i)))
continue;

slave_dev = dsa_slave_create(ds, parent, i, pd->port_names[i]);
if (slave_dev == NULL) {
ret = dsa_slave_create(ds, parent, i, pd->port_names[i]);
if (ret < 0) {
netdev_err(dst->master_netdev, "[%d]: can't create dsa slave device for port %d(%s)\n",
index, i, pd->port_names[i]);
continue;
ret = 0;
}

ds->ports[i] = slave_dev;
}

#ifdef CONFIG_NET_DSA_HWMON
Expand Down Expand Up @@ -830,6 +826,10 @@ static struct packet_type dsa_pack_type __read_mostly = {
.func = dsa_switch_rcv,
};

static struct notifier_block dsa_netdevice_nb __read_mostly = {
.notifier_call = dsa_slave_netdevice_event,
};

#ifdef CONFIG_PM_SLEEP
static int dsa_suspend(struct device *d)
{
Expand Down Expand Up @@ -888,6 +888,8 @@ static int __init dsa_init_module(void)
{
int rc;

register_netdevice_notifier(&dsa_netdevice_nb);

rc = platform_driver_register(&dsa_driver);
if (rc)
return rc;
Expand All @@ -900,6 +902,7 @@ module_init(dsa_init_module);

static void __exit dsa_cleanup_module(void)
{
unregister_netdevice_notifier(&dsa_netdevice_nb);
dev_remove_pack(&dsa_pack_type);
platform_driver_unregister(&dsa_driver);
}
Expand Down
9 changes: 6 additions & 3 deletions net/dsa/dsa_priv.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ struct dsa_slave_priv {
int old_link;
int old_pause;
int old_duplex;

struct net_device *bridge_dev;
};

/* dsa.c */
Expand All @@ -53,11 +55,12 @@ extern char dsa_driver_version[];
/* slave.c */
extern const struct dsa_device_ops notag_netdev_ops;
void dsa_slave_mii_bus_init(struct dsa_switch *ds);
struct net_device *dsa_slave_create(struct dsa_switch *ds,
struct device *parent,
int port, char *name);
int dsa_slave_create(struct dsa_switch *ds, struct device *parent,
int port, char *name);
int dsa_slave_suspend(struct net_device *slave_dev);
int dsa_slave_resume(struct net_device *slave_dev);
int dsa_slave_netdevice_event(struct notifier_block *unused,
unsigned long event, void *ptr);

/* tag_dsa.c */
extern const struct dsa_device_ops dsa_netdev_ops;
Expand Down
Loading

0 comments on commit bb66be1

Please sign in to comment.