mirror of
https://github.com/torvalds/linux.git
synced 2024-11-25 13:41:51 +00:00
net: dsa: mv88e6xxx: Add Hardware bridging support
Bridge support is similar for all chips supported by the mv88e6xxx code, so add the code there. Reviewed-by: Andrew Lunn <andrew@lunn.ch> Tested-by: Andrew Lunn <andrew@lunn.ch> Signed-off-by: Guenter Roeck <linux@roeck-us.net> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
parent
b0019b70d0
commit
facd95b2e0
@ -9,6 +9,7 @@
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/if_bridge.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/module.h>
|
||||
@ -644,6 +645,31 @@ int mv88e6xxx_eeprom_busy_wait(struct dsa_switch *ds)
|
||||
return mv88e6xxx_wait(ds, REG_GLOBAL2, 0x14, 0x8000);
|
||||
}
|
||||
|
||||
/* Must be called with SMI lock held */
|
||||
static int _mv88e6xxx_wait(struct dsa_switch *ds, int reg, int offset, u16 mask)
|
||||
{
|
||||
unsigned long timeout = jiffies + HZ / 10;
|
||||
|
||||
while (time_before(jiffies, timeout)) {
|
||||
int ret;
|
||||
|
||||
ret = _mv88e6xxx_reg_read(ds, reg, offset);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (!(ret & mask))
|
||||
return 0;
|
||||
|
||||
usleep_range(1000, 2000);
|
||||
}
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
/* Must be called with SMI lock held */
|
||||
static int _mv88e6xxx_atu_wait(struct dsa_switch *ds)
|
||||
{
|
||||
return _mv88e6xxx_wait(ds, REG_GLOBAL, 0x0b, ATU_BUSY);
|
||||
}
|
||||
|
||||
int mv88e6xxx_phy_read_indirect(struct dsa_switch *ds, int addr, int regnum)
|
||||
{
|
||||
int ret;
|
||||
@ -717,10 +743,236 @@ int mv88e6xxx_set_eee(struct dsa_switch *ds, int port,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _mv88e6xxx_atu_cmd(struct dsa_switch *ds, int fid, u16 cmd)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = _mv88e6xxx_reg_write(ds, REG_GLOBAL, 0x01, fid);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = _mv88e6xxx_reg_write(ds, REG_GLOBAL, 0x0b, cmd);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return _mv88e6xxx_atu_wait(ds);
|
||||
}
|
||||
|
||||
static int _mv88e6xxx_flush_fid(struct dsa_switch *ds, int fid)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = _mv88e6xxx_atu_wait(ds);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return _mv88e6xxx_atu_cmd(ds, fid, ATU_CMD_FLUSH_NONSTATIC_FID);
|
||||
}
|
||||
|
||||
static int mv88e6xxx_set_port_state(struct dsa_switch *ds, int port, u8 state)
|
||||
{
|
||||
struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
|
||||
int reg, ret;
|
||||
u8 oldstate;
|
||||
|
||||
mutex_lock(&ps->smi_mutex);
|
||||
|
||||
reg = _mv88e6xxx_reg_read(ds, REG_PORT(port), 0x04);
|
||||
if (reg < 0)
|
||||
goto abort;
|
||||
|
||||
oldstate = reg & PSTATE_MASK;
|
||||
if (oldstate != state) {
|
||||
/* Flush forwarding database if we're moving a port
|
||||
* from Learning or Forwarding state to Disabled or
|
||||
* Blocking or Listening state.
|
||||
*/
|
||||
if (oldstate >= PSTATE_LEARNING && state <= PSTATE_BLOCKING) {
|
||||
ret = _mv88e6xxx_flush_fid(ds, ps->fid[port]);
|
||||
if (ret)
|
||||
goto abort;
|
||||
}
|
||||
reg = (reg & ~PSTATE_MASK) | state;
|
||||
ret = _mv88e6xxx_reg_write(ds, REG_PORT(port), 0x04, reg);
|
||||
}
|
||||
|
||||
abort:
|
||||
mutex_unlock(&ps->smi_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Must be called with smi lock held */
|
||||
static int _mv88e6xxx_update_port_config(struct dsa_switch *ds, int port)
|
||||
{
|
||||
struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
|
||||
u8 fid = ps->fid[port];
|
||||
u16 reg = fid << 12;
|
||||
|
||||
if (dsa_is_cpu_port(ds, port))
|
||||
reg |= ds->phys_port_mask;
|
||||
else
|
||||
reg |= (ps->bridge_mask[fid] |
|
||||
(1 << dsa_upstream_port(ds))) & ~(1 << port);
|
||||
|
||||
return _mv88e6xxx_reg_write(ds, REG_PORT(port), 0x06, reg);
|
||||
}
|
||||
|
||||
/* Must be called with smi lock held */
|
||||
static int _mv88e6xxx_update_bridge_config(struct dsa_switch *ds, int fid)
|
||||
{
|
||||
struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
|
||||
int port;
|
||||
u32 mask;
|
||||
int ret;
|
||||
|
||||
mask = ds->phys_port_mask;
|
||||
while (mask) {
|
||||
port = __ffs(mask);
|
||||
mask &= ~(1 << port);
|
||||
if (ps->fid[port] != fid)
|
||||
continue;
|
||||
|
||||
ret = _mv88e6xxx_update_port_config(ds, port);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return _mv88e6xxx_flush_fid(ds, fid);
|
||||
}
|
||||
|
||||
/* Bridge handling functions */
|
||||
|
||||
int mv88e6xxx_join_bridge(struct dsa_switch *ds, int port, u32 br_port_mask)
|
||||
{
|
||||
struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
|
||||
int ret = 0;
|
||||
u32 nmask;
|
||||
int fid;
|
||||
|
||||
/* If the bridge group is not empty, join that group.
|
||||
* Otherwise create a new group.
|
||||
*/
|
||||
fid = ps->fid[port];
|
||||
nmask = br_port_mask & ~(1 << port);
|
||||
if (nmask)
|
||||
fid = ps->fid[__ffs(nmask)];
|
||||
|
||||
nmask = ps->bridge_mask[fid] | (1 << port);
|
||||
if (nmask != br_port_mask) {
|
||||
netdev_err(ds->ports[port],
|
||||
"join: Bridge port mask mismatch fid=%d mask=0x%x expected 0x%x\n",
|
||||
fid, br_port_mask, nmask);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&ps->smi_mutex);
|
||||
|
||||
ps->bridge_mask[fid] = br_port_mask;
|
||||
|
||||
if (fid != ps->fid[port]) {
|
||||
ps->fid_mask |= 1 << ps->fid[port];
|
||||
ps->fid[port] = fid;
|
||||
ret = _mv88e6xxx_update_bridge_config(ds, fid);
|
||||
}
|
||||
|
||||
mutex_unlock(&ps->smi_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int mv88e6xxx_leave_bridge(struct dsa_switch *ds, int port, u32 br_port_mask)
|
||||
{
|
||||
struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
|
||||
u8 fid, newfid;
|
||||
int ret;
|
||||
|
||||
fid = ps->fid[port];
|
||||
|
||||
if (ps->bridge_mask[fid] != br_port_mask) {
|
||||
netdev_err(ds->ports[port],
|
||||
"leave: Bridge port mask mismatch fid=%d mask=0x%x expected 0x%x\n",
|
||||
fid, br_port_mask, ps->bridge_mask[fid]);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* If the port was the last port of a bridge, we are done.
|
||||
* Otherwise assign a new fid to the port, and fix up
|
||||
* the bridge configuration.
|
||||
*/
|
||||
if (br_port_mask == (1 << port))
|
||||
return 0;
|
||||
|
||||
mutex_lock(&ps->smi_mutex);
|
||||
|
||||
newfid = __ffs(ps->fid_mask);
|
||||
ps->fid[port] = newfid;
|
||||
ps->fid_mask &= (1 << newfid);
|
||||
ps->bridge_mask[fid] &= ~(1 << port);
|
||||
ps->bridge_mask[newfid] = 1 << port;
|
||||
|
||||
ret = _mv88e6xxx_update_bridge_config(ds, fid);
|
||||
if (!ret)
|
||||
ret = _mv88e6xxx_update_bridge_config(ds, newfid);
|
||||
|
||||
mutex_unlock(&ps->smi_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int mv88e6xxx_port_stp_update(struct dsa_switch *ds, int port, u8 state)
|
||||
{
|
||||
struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
|
||||
int stp_state;
|
||||
|
||||
switch (state) {
|
||||
case BR_STATE_DISABLED:
|
||||
stp_state = PSTATE_DISABLED;
|
||||
break;
|
||||
case BR_STATE_BLOCKING:
|
||||
case BR_STATE_LISTENING:
|
||||
stp_state = PSTATE_BLOCKING;
|
||||
break;
|
||||
case BR_STATE_LEARNING:
|
||||
stp_state = PSTATE_LEARNING;
|
||||
break;
|
||||
case BR_STATE_FORWARDING:
|
||||
default:
|
||||
stp_state = PSTATE_FORWARDING;
|
||||
break;
|
||||
}
|
||||
|
||||
netdev_dbg(ds->ports[port], "port state %d [%d]\n", state, stp_state);
|
||||
|
||||
/* mv88e6xxx_port_stp_update may be called with softirqs disabled,
|
||||
* so we can not update the port state directly but need to schedule it.
|
||||
*/
|
||||
ps->port_state[port] = stp_state;
|
||||
set_bit(port, &ps->port_state_update_mask);
|
||||
schedule_work(&ps->bridge_work);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mv88e6xxx_bridge_work(struct work_struct *work)
|
||||
{
|
||||
struct mv88e6xxx_priv_state *ps;
|
||||
struct dsa_switch *ds;
|
||||
int port;
|
||||
|
||||
ps = container_of(work, struct mv88e6xxx_priv_state, bridge_work);
|
||||
ds = ((struct dsa_switch *)ps) - 1;
|
||||
|
||||
while (ps->port_state_update_mask) {
|
||||
port = __ffs(ps->port_state_update_mask);
|
||||
clear_bit(port, &ps->port_state_update_mask);
|
||||
mv88e6xxx_set_port_state(ds, port, ps->port_state[port]);
|
||||
}
|
||||
}
|
||||
|
||||
int mv88e6xxx_setup_port_common(struct dsa_switch *ds, int port)
|
||||
{
|
||||
struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
|
||||
int ret, reg;
|
||||
int ret, fid;
|
||||
|
||||
mutex_lock(&ps->smi_mutex);
|
||||
|
||||
@ -736,13 +988,14 @@ int mv88e6xxx_setup_port_common(struct dsa_switch *ds, int port)
|
||||
* ports, and allow each of the 'real' ports to only talk to
|
||||
* the upstream port.
|
||||
*/
|
||||
reg = (port & 0xf) << 12;
|
||||
if (dsa_is_cpu_port(ds, port))
|
||||
reg |= ds->phys_port_mask;
|
||||
else
|
||||
reg |= 1 << dsa_upstream_port(ds);
|
||||
fid = __ffs(ps->fid_mask);
|
||||
ps->fid[port] = fid;
|
||||
ps->fid_mask &= ~(1 << fid);
|
||||
|
||||
ret = _mv88e6xxx_reg_write(ds, REG_PORT(port), 0x06, reg);
|
||||
if (!dsa_is_cpu_port(ds, port))
|
||||
ps->bridge_mask[fid] = 1 << port;
|
||||
|
||||
ret = _mv88e6xxx_update_port_config(ds, port);
|
||||
if (ret)
|
||||
goto abort;
|
||||
|
||||
@ -763,6 +1016,10 @@ int mv88e6xxx_setup_common(struct dsa_switch *ds)
|
||||
mutex_init(&ps->stats_mutex);
|
||||
mutex_init(&ps->phy_mutex);
|
||||
|
||||
ps->fid_mask = (1 << DSA_MAX_PORTS) - 1;
|
||||
|
||||
INIT_WORK(&ps->bridge_work, mv88e6xxx_bridge_work);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,20 @@
|
||||
#define REG_GLOBAL 0x1b
|
||||
#define REG_GLOBAL2 0x1c
|
||||
|
||||
/* ATU commands */
|
||||
|
||||
#define ATU_BUSY 0x8000
|
||||
|
||||
#define ATU_CMD_FLUSH_NONSTATIC_FID (ATU_BUSY | 0x6000)
|
||||
|
||||
/* port states */
|
||||
|
||||
#define PSTATE_MASK 0x03
|
||||
#define PSTATE_DISABLED 0x00
|
||||
#define PSTATE_BLOCKING 0x01
|
||||
#define PSTATE_LEARNING 0x02
|
||||
#define PSTATE_FORWARDING 0x03
|
||||
|
||||
struct mv88e6xxx_priv_state {
|
||||
/* When using multi-chip addressing, this mutex protects
|
||||
* access to the indirect access registers. (In single-chip
|
||||
@ -49,6 +63,17 @@ struct mv88e6xxx_priv_state {
|
||||
struct mutex eeprom_mutex;
|
||||
|
||||
int id; /* switch product id */
|
||||
|
||||
/* hw bridging */
|
||||
|
||||
u32 fid_mask;
|
||||
u8 fid[DSA_MAX_PORTS];
|
||||
u16 bridge_mask[DSA_MAX_PORTS];
|
||||
|
||||
unsigned long port_state_update_mask;
|
||||
u8 port_state[DSA_MAX_PORTS];
|
||||
|
||||
struct work_struct bridge_work;
|
||||
};
|
||||
|
||||
struct mv88e6xxx_hw_stat {
|
||||
@ -93,6 +118,9 @@ int mv88e6xxx_phy_write_indirect(struct dsa_switch *ds, int addr, int regnum,
|
||||
int mv88e6xxx_get_eee(struct dsa_switch *ds, int port, struct ethtool_eee *e);
|
||||
int mv88e6xxx_set_eee(struct dsa_switch *ds, int port,
|
||||
struct phy_device *phydev, struct ethtool_eee *e);
|
||||
int mv88e6xxx_join_bridge(struct dsa_switch *ds, int port, u32 br_port_mask);
|
||||
int mv88e6xxx_leave_bridge(struct dsa_switch *ds, int port, u32 br_port_mask);
|
||||
int mv88e6xxx_port_stp_update(struct dsa_switch *ds, int port, u8 state);
|
||||
|
||||
extern struct dsa_switch_driver mv88e6131_switch_driver;
|
||||
extern struct dsa_switch_driver mv88e6123_61_65_switch_driver;
|
||||
|
Loading…
Reference in New Issue
Block a user