Commit 968d7cb8 authored by David S. Miller's avatar David S. Miller

Merge branch 'mv88e6xxx-hw-vlan'

Vivien Didelot says:

====================
net: dsa: mv88e6xxx: add hardware VLAN support

This patchset brings support to access hardware VLAN entries in DSA and
mv88e6xxx, through switchdev VLAN objects.

In the following example, ports swp[0-2] belong to bridge br0, and ports
swp[3-4] belong to bridge br1. Here's an example of what can be achieved
after this patchset:

    # bridge vlan add dev swp1 vid 100 master
    # bridge vlan add dev swp2 vid 100 master
    # bridge vlan add dev swp3 vid 100 master
    # bridge vlan add dev swp4 vid 100 master
    # bridge vlan del dev swp1 vid 100 master

The above commands correctly programmed hardware VLAN 100 for port swp2,
while ports swp3 and swp4 use software VLAN 100, as shown with:

    # bridge vlan
    port	vlan ids
    swp0	None
    swp0
    swp1	None
    swp1
    swp2	 100

    swp2	 100

    swp3	 100

    swp3
    swp4	 100

    swp4
    br0	None
    br1	None

Assuming that port 5 is the CPU port, the hardware VLAN table would
contain the following data:

    VID  FID  SID  0  1  2  3  4  5  6
    100    8    0  x  x  t  x  x  t  x

Where 'x' means excluded, and 't' means tagged.

Also, adding an FDB entry to VLAN 100 for port swp2 like this:

    # bridge fdb add 3c:97:0e:11:6e:30 dev swp2 vlan 100

Would result in the following example output:

    # bridge fdb
    # 01:00:5e:00:00:01 dev eth0 self permanent
    # 01:00:5e:00:00:01 dev eth1 self permanent
    # 00:50:d2:10:78:15 dev swp0 master br0 permanent
    # 00:50:d2:10:78:15 dev swp2 vlan 100 master br0 permanent
    # 3c:97:0e:11:6e:30 dev swp2 vlan 100 self static
    # 00:50:d2:10:78:15 dev swp3 master br1 permanent
    # 00:50:d2:10:78:15 dev swp3 vlan 100 master br1 permanent

And the Address Translation Unit would contain:

    DB   T/P  Vec State Addr
    008  Port 004   e   3c:97:0e:11:6e:30
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 35103d11 8efdda4a
......@@ -343,6 +343,11 @@ struct dsa_switch_driver mv88e6352_switch_driver = {
.port_join_bridge = mv88e6xxx_join_bridge,
.port_leave_bridge = mv88e6xxx_leave_bridge,
.port_stp_update = mv88e6xxx_port_stp_update,
.port_pvid_get = mv88e6xxx_port_pvid_get,
.port_pvid_set = mv88e6xxx_port_pvid_set,
.port_vlan_add = mv88e6xxx_port_vlan_add,
.port_vlan_del = mv88e6xxx_port_vlan_del,
.vlan_getnext = mv88e6xxx_vlan_getnext,
.port_fdb_add = mv88e6xxx_port_fdb_add,
.port_fdb_del = mv88e6xxx_port_fdb_del,
.port_fdb_getnext = mv88e6xxx_port_fdb_getnext,
......
This diff is collapsed.
......@@ -131,6 +131,7 @@
#define PORT_CONTROL_1 0x05
#define PORT_BASE_VLAN 0x06
#define PORT_DEFAULT_VLAN 0x07
#define PORT_DEFAULT_VLAN_MASK 0xfff
#define PORT_CONTROL_2 0x08
#define PORT_CONTROL_2_IGNORE_FCS BIT(15)
#define PORT_CONTROL_2_VTU_PRI_OVERRIDE BIT(14)
......@@ -139,6 +140,11 @@
#define PORT_CONTROL_2_JUMBO_1522 (0x00 << 12)
#define PORT_CONTROL_2_JUMBO_2048 (0x01 << 12)
#define PORT_CONTROL_2_JUMBO_10240 (0x02 << 12)
#define PORT_CONTROL_2_8021Q_MASK (0x03 << 10)
#define PORT_CONTROL_2_8021Q_DISABLED (0x00 << 10)
#define PORT_CONTROL_2_8021Q_FALLBACK (0x01 << 10)
#define PORT_CONTROL_2_8021Q_CHECK (0x02 << 10)
#define PORT_CONTROL_2_8021Q_SECURE (0x03 << 10)
#define PORT_CONTROL_2_DISCARD_TAGGED BIT(9)
#define PORT_CONTROL_2_DISCARD_UNTAGGED BIT(8)
#define PORT_CONTROL_2_MAP_DA BIT(7)
......@@ -172,6 +178,10 @@
#define GLOBAL_MAC_23 0x02
#define GLOBAL_MAC_45 0x03
#define GLOBAL_ATU_FID 0x01 /* 6097 6165 6351 6352 */
#define GLOBAL_VTU_FID 0x02 /* 6097 6165 6351 6352 */
#define GLOBAL_VTU_FID_MASK 0xfff
#define GLOBAL_VTU_SID 0x03 /* 6097 6165 6351 6352 */
#define GLOBAL_VTU_SID_MASK 0x3f
#define GLOBAL_CONTROL 0x04
#define GLOBAL_CONTROL_SW_RESET BIT(15)
#define GLOBAL_CONTROL_PPU_ENABLE BIT(14)
......@@ -188,10 +198,27 @@
#define GLOBAL_CONTROL_TCAM_EN BIT(1)
#define GLOBAL_CONTROL_EEPROM_DONE_EN BIT(0)
#define GLOBAL_VTU_OP 0x05
#define GLOBAL_VTU_OP_BUSY BIT(15)
#define GLOBAL_VTU_OP_FLUSH_ALL ((0x01 << 12) | GLOBAL_VTU_OP_BUSY)
#define GLOBAL_VTU_OP_VTU_LOAD_PURGE ((0x03 << 12) | GLOBAL_VTU_OP_BUSY)
#define GLOBAL_VTU_OP_VTU_GET_NEXT ((0x04 << 12) | GLOBAL_VTU_OP_BUSY)
#define GLOBAL_VTU_OP_STU_LOAD_PURGE ((0x05 << 12) | GLOBAL_VTU_OP_BUSY)
#define GLOBAL_VTU_OP_STU_GET_NEXT ((0x06 << 12) | GLOBAL_VTU_OP_BUSY)
#define GLOBAL_VTU_VID 0x06
#define GLOBAL_VTU_VID_MASK 0xfff
#define GLOBAL_VTU_VID_VALID BIT(12)
#define GLOBAL_VTU_DATA_0_3 0x07
#define GLOBAL_VTU_DATA_4_7 0x08
#define GLOBAL_VTU_DATA_8_11 0x09
#define GLOBAL_VTU_STU_DATA_MASK 0x03
#define GLOBAL_VTU_DATA_MEMBER_TAG_UNMODIFIED 0x00
#define GLOBAL_VTU_DATA_MEMBER_TAG_UNTAGGED 0x01
#define GLOBAL_VTU_DATA_MEMBER_TAG_TAGGED 0x02
#define GLOBAL_VTU_DATA_MEMBER_TAG_NON_MEMBER 0x03
#define GLOBAL_STU_DATA_PORT_STATE_DISABLED 0x00
#define GLOBAL_STU_DATA_PORT_STATE_BLOCKING 0x01
#define GLOBAL_STU_DATA_PORT_STATE_LEARNING 0x02
#define GLOBAL_STU_DATA_PORT_STATE_FORWARDING 0x03
#define GLOBAL_ATU_CONTROL 0x0a
#define GLOBAL_ATU_CONTROL_LEARN2ALL BIT(3)
#define GLOBAL_ATU_OP 0x0b
......@@ -326,6 +353,17 @@ struct mv88e6xxx_atu_entry {
u8 mac[ETH_ALEN];
};
struct mv88e6xxx_vtu_stu_entry {
/* VTU only */
u16 vid;
u16 fid;
/* VTU and STU */
u8 sid;
bool valid;
u8 data[DSA_MAX_PORTS];
};
struct mv88e6xxx_priv_state {
/* When using multi-chip addressing, this mutex protects
* access to the indirect access registers. (In single-chip
......@@ -426,6 +464,13 @@ int mv88e6xxx_set_eee(struct dsa_switch *ds, int port,
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);
int mv88e6xxx_port_pvid_get(struct dsa_switch *ds, int port, u16 *vid);
int mv88e6xxx_port_pvid_set(struct dsa_switch *ds, int port, u16 vid);
int mv88e6xxx_port_vlan_add(struct dsa_switch *ds, int port, u16 vid,
bool untagged);
int mv88e6xxx_port_vlan_del(struct dsa_switch *ds, int port, u16 vid);
int mv88e6xxx_vlan_getnext(struct dsa_switch *ds, u16 *vid,
unsigned long *ports, unsigned long *untagged);
int mv88e6xxx_port_fdb_add(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid);
int mv88e6xxx_port_fdb_del(struct dsa_switch *ds, int port,
......
......@@ -297,6 +297,17 @@ struct dsa_switch_driver {
int (*port_stp_update)(struct dsa_switch *ds, int port,
u8 state);
/*
* VLAN support
*/
int (*port_pvid_get)(struct dsa_switch *ds, int port, u16 *pvid);
int (*port_pvid_set)(struct dsa_switch *ds, int port, u16 pvid);
int (*port_vlan_add)(struct dsa_switch *ds, int port, u16 vid,
bool untagged);
int (*port_vlan_del)(struct dsa_switch *ds, int port, u16 vid);
int (*vlan_getnext)(struct dsa_switch *ds, u16 *vid,
unsigned long *ports, unsigned long *untagged);
/*
* Forwarding database
*/
......
......@@ -200,6 +200,152 @@ static int dsa_slave_set_mac_address(struct net_device *dev, void *a)
return 0;
}
static int dsa_bridge_check_vlan_range(struct dsa_switch *ds,
const struct net_device *bridge,
u16 vid_begin, u16 vid_end)
{
struct dsa_slave_priv *p;
struct net_device *dev, *vlan_br;
DECLARE_BITMAP(members, DSA_MAX_PORTS);
DECLARE_BITMAP(untagged, DSA_MAX_PORTS);
u16 vid;
int member, err;
if (!ds->drv->vlan_getnext || !vid_begin)
return -EOPNOTSUPP;
vid = vid_begin - 1;
do {
err = ds->drv->vlan_getnext(ds, &vid, members, untagged);
if (err)
break;
if (vid > vid_end)
break;
member = find_first_bit(members, DSA_MAX_PORTS);
if (member == DSA_MAX_PORTS)
continue;
dev = ds->ports[member];
p = netdev_priv(dev);
vlan_br = p->bridge_dev;
if (vlan_br == bridge)
continue;
netdev_dbg(vlan_br, "hardware VLAN %d already in use\n", vid);
return -EOPNOTSUPP;
} while (vid < vid_end);
return err == -ENOENT ? 0 : err;
}
static int dsa_slave_port_vlan_add(struct net_device *dev,
struct switchdev_obj *obj)
{
struct switchdev_obj_vlan *vlan = &obj->u.vlan;
struct dsa_slave_priv *p = netdev_priv(dev);
struct dsa_switch *ds = p->parent;
u16 vid;
int err;
switch (obj->trans) {
case SWITCHDEV_TRANS_PREPARE:
if (!ds->drv->port_vlan_add || !ds->drv->port_pvid_set)
return -EOPNOTSUPP;
/* If the requested port doesn't belong to the same bridge as
* the VLAN members, fallback to software VLAN (hopefully).
*/
err = dsa_bridge_check_vlan_range(ds, p->bridge_dev,
vlan->vid_begin,
vlan->vid_end);
if (err)
return err;
break;
case SWITCHDEV_TRANS_COMMIT:
for (vid = vlan->vid_begin; vid <= vlan->vid_end; ++vid) {
err = ds->drv->port_vlan_add(ds, p->port, vid,
vlan->flags &
BRIDGE_VLAN_INFO_UNTAGGED);
if (!err && vlan->flags & BRIDGE_VLAN_INFO_PVID)
err = ds->drv->port_pvid_set(ds, p->port, vid);
if (err)
return err;
}
break;
default:
return -EOPNOTSUPP;
}
return 0;
}
static int dsa_slave_port_vlan_del(struct net_device *dev,
struct switchdev_obj *obj)
{
struct switchdev_obj_vlan *vlan = &obj->u.vlan;
struct dsa_slave_priv *p = netdev_priv(dev);
struct dsa_switch *ds = p->parent;
u16 vid;
int err;
if (!ds->drv->port_vlan_del)
return -EOPNOTSUPP;
for (vid = vlan->vid_begin; vid <= vlan->vid_end; ++vid) {
err = ds->drv->port_vlan_del(ds, p->port, vid);
if (err)
return err;
}
return 0;
}
static int dsa_slave_port_vlan_dump(struct net_device *dev,
struct switchdev_obj *obj)
{
struct switchdev_obj_vlan *vlan = &obj->u.vlan;
struct dsa_slave_priv *p = netdev_priv(dev);
struct dsa_switch *ds = p->parent;
DECLARE_BITMAP(members, DSA_MAX_PORTS);
DECLARE_BITMAP(untagged, DSA_MAX_PORTS);
u16 pvid, vid = 0;
int err;
if (!ds->drv->vlan_getnext || !ds->drv->port_pvid_get)
return -EOPNOTSUPP;
err = ds->drv->port_pvid_get(ds, p->port, &pvid);
if (err)
return err;
for (;;) {
err = ds->drv->vlan_getnext(ds, &vid, members, untagged);
if (err)
break;
if (!test_bit(p->port, members))
continue;
memset(vlan, 0, sizeof(*vlan));
vlan->vid_begin = vlan->vid_end = vid;
if (vid == pvid)
vlan->flags |= BRIDGE_VLAN_INFO_PVID;
if (test_bit(p->port, untagged))
vlan->flags |= BRIDGE_VLAN_INFO_UNTAGGED;
err = obj->cb(dev, obj);
if (err)
break;
}
return err == -ENOENT ? 0 : err;
}
static int dsa_slave_port_fdb_add(struct net_device *dev,
struct switchdev_obj *obj)
{
......@@ -341,6 +487,9 @@ static int dsa_slave_port_obj_add(struct net_device *dev,
case SWITCHDEV_OBJ_PORT_FDB:
err = dsa_slave_port_fdb_add(dev, obj);
break;
case SWITCHDEV_OBJ_PORT_VLAN:
err = dsa_slave_port_vlan_add(dev, obj);
break;
default:
err = -EOPNOTSUPP;
break;
......@@ -358,6 +507,9 @@ static int dsa_slave_port_obj_del(struct net_device *dev,
case SWITCHDEV_OBJ_PORT_FDB:
err = dsa_slave_port_fdb_del(dev, obj);
break;
case SWITCHDEV_OBJ_PORT_VLAN:
err = dsa_slave_port_vlan_del(dev, obj);
break;
default:
err = -EOPNOTSUPP;
break;
......@@ -375,6 +527,9 @@ static int dsa_slave_port_obj_dump(struct net_device *dev,
case SWITCHDEV_OBJ_PORT_FDB:
err = dsa_slave_port_fdb_dump(dev, obj);
break;
case SWITCHDEV_OBJ_PORT_VLAN:
err = dsa_slave_port_vlan_dump(dev, obj);
break;
default:
err = -EOPNOTSUPP;
break;
......@@ -794,6 +949,9 @@ static const struct net_device_ops dsa_slave_netdev_ops = {
.ndo_netpoll_cleanup = dsa_slave_netpoll_cleanup,
.ndo_poll_controller = dsa_slave_poll_controller,
#endif
.ndo_bridge_getlink = switchdev_port_bridge_getlink,
.ndo_bridge_setlink = switchdev_port_bridge_setlink,
.ndo_bridge_dellink = switchdev_port_bridge_dellink,
};
static const struct switchdev_ops dsa_slave_switchdev_ops = {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment