Commit ec1606c0 authored by David S. Miller's avatar David S. Miller

Merge branch 'mv88e6xxx-vlan-filtering'

Vivien Didelot says:

====================
net: dsa: mv88e6xxx: implement VLAN filtering

This patchset fixes hardware bridging for non 802.1Q aware systems.

The mv88e6xxx DSA driver currently depends on CONFIG_VLAN_8021Q and
CONFIG_BRIDGE_VLAN_FILTERING enabled for correct bridging between switch ports.

Patch 1/9 adds support for the VLAN filtering switchdev attribute in DSA.

Patchs 2/9 and 3/9 add helper functions for the following patches.

Patchs 4/9 to 6/9 assign dynamic address databases to VLANs, ports, and
bridge groups (the lowest available FID is cleared and assigned), and thus
restore support for per-port FDB operations.

Patchs 7/9 to 9/9 refine ports isolation and setup 802.1Q on user demand.

With this patchset, ports get correctly bridged and the driver behaves as
expected, with or without 802.1Q support.

With CONFIG_VLAN_8021Q enabled, setting a default PVID to the bridge correctly
propagates the corresponding VLAN, in addition to the hardware bridging:

    # echo 42 > /sys/class/net/<bridge>/bridge/default_pvid

But considering CONFIG_BRIDGE_VLAN_FILTERING enabled, the hardware VLAN
filtering is enabled on all bridge members only when the user requests it:

    # echo 1 > /sys/class/net/<bridge>/bridge/vlan_filtering
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 7f66ee41 214cdb99
...@@ -106,6 +106,7 @@ struct dsa_switch_driver mv88e6171_switch_driver = { ...@@ -106,6 +106,7 @@ struct dsa_switch_driver mv88e6171_switch_driver = {
.port_join_bridge = mv88e6xxx_port_bridge_join, .port_join_bridge = mv88e6xxx_port_bridge_join,
.port_leave_bridge = mv88e6xxx_port_bridge_leave, .port_leave_bridge = mv88e6xxx_port_bridge_leave,
.port_stp_update = mv88e6xxx_port_stp_update, .port_stp_update = mv88e6xxx_port_stp_update,
.port_vlan_filtering = mv88e6xxx_port_vlan_filtering,
.port_vlan_prepare = mv88e6xxx_port_vlan_prepare, .port_vlan_prepare = mv88e6xxx_port_vlan_prepare,
.port_vlan_add = mv88e6xxx_port_vlan_add, .port_vlan_add = mv88e6xxx_port_vlan_add,
.port_vlan_del = mv88e6xxx_port_vlan_del, .port_vlan_del = mv88e6xxx_port_vlan_del,
......
...@@ -327,6 +327,7 @@ struct dsa_switch_driver mv88e6352_switch_driver = { ...@@ -327,6 +327,7 @@ struct dsa_switch_driver mv88e6352_switch_driver = {
.port_join_bridge = mv88e6xxx_port_bridge_join, .port_join_bridge = mv88e6xxx_port_bridge_join,
.port_leave_bridge = mv88e6xxx_port_bridge_leave, .port_leave_bridge = mv88e6xxx_port_bridge_leave,
.port_stp_update = mv88e6xxx_port_stp_update, .port_stp_update = mv88e6xxx_port_stp_update,
.port_vlan_filtering = mv88e6xxx_port_vlan_filtering,
.port_vlan_prepare = mv88e6xxx_port_vlan_prepare, .port_vlan_prepare = mv88e6xxx_port_vlan_prepare,
.port_vlan_add = mv88e6xxx_port_vlan_add, .port_vlan_add = mv88e6xxx_port_vlan_add,
.port_vlan_del = mv88e6xxx_port_vlan_del, .port_vlan_del = mv88e6xxx_port_vlan_del,
......
...@@ -1087,12 +1087,32 @@ static int mv88e6xxx_set_port_state(struct dsa_switch *ds, int port, u8 state) ...@@ -1087,12 +1087,32 @@ static int mv88e6xxx_set_port_state(struct dsa_switch *ds, int port, u8 state)
return ret; return ret;
} }
static int _mv88e6xxx_port_vlan_map_set(struct dsa_switch *ds, int port, static int _mv88e6xxx_port_based_vlan_map(struct dsa_switch *ds, int port)
u16 output_ports)
{ {
struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
struct net_device *bridge = ps->ports[port].bridge_dev;
const u16 mask = (1 << ps->num_ports) - 1; const u16 mask = (1 << ps->num_ports) - 1;
u16 output_ports = 0;
int reg; int reg;
int i;
/* allow CPU port or DSA link(s) to send frames to every port */
if (dsa_is_cpu_port(ds, port) || dsa_is_dsa_port(ds, port)) {
output_ports = mask;
} else {
for (i = 0; i < ps->num_ports; ++i) {
/* allow sending frames to every group member */
if (bridge && ps->ports[i].bridge_dev == bridge)
output_ports |= BIT(i);
/* allow sending frames to CPU port and DSA link(s) */
if (dsa_is_cpu_port(ds, i) || dsa_is_dsa_port(ds, i))
output_ports |= BIT(i);
}
}
/* prevent frames from going back out of the port they came in on */
output_ports &= ~BIT(port);
reg = _mv88e6xxx_reg_read(ds, REG_PORT(port), PORT_BASE_VLAN); reg = _mv88e6xxx_reg_read(ds, REG_PORT(port), PORT_BASE_VLAN);
if (reg < 0) if (reg < 0)
...@@ -1458,16 +1478,122 @@ static int _mv88e6xxx_stu_loadpurge(struct dsa_switch *ds, ...@@ -1458,16 +1478,122 @@ static int _mv88e6xxx_stu_loadpurge(struct dsa_switch *ds,
return _mv88e6xxx_vtu_cmd(ds, GLOBAL_VTU_OP_STU_LOAD_PURGE); return _mv88e6xxx_vtu_cmd(ds, GLOBAL_VTU_OP_STU_LOAD_PURGE);
} }
static int _mv88e6xxx_vlan_init(struct dsa_switch *ds, u16 vid, static int _mv88e6xxx_port_fid(struct dsa_switch *ds, int port, u16 *new,
struct mv88e6xxx_vtu_stu_entry *entry) u16 *old)
{
u16 fid;
int ret;
/* Port's default FID bits 3:0 are located in reg 0x06, offset 12 */
ret = _mv88e6xxx_reg_read(ds, REG_PORT(port), PORT_BASE_VLAN);
if (ret < 0)
return ret;
fid = (ret & PORT_BASE_VLAN_FID_3_0_MASK) >> 12;
if (new) {
ret &= ~PORT_BASE_VLAN_FID_3_0_MASK;
ret |= (*new << 12) & PORT_BASE_VLAN_FID_3_0_MASK;
ret = _mv88e6xxx_reg_write(ds, REG_PORT(port), PORT_BASE_VLAN,
ret);
if (ret < 0)
return ret;
}
/* Port's default FID bits 11:4 are located in reg 0x05, offset 0 */
ret = _mv88e6xxx_reg_read(ds, REG_PORT(port), PORT_CONTROL_1);
if (ret < 0)
return ret;
fid |= (ret & PORT_CONTROL_1_FID_11_4_MASK) << 4;
if (new) {
ret &= ~PORT_CONTROL_1_FID_11_4_MASK;
ret |= (*new >> 4) & PORT_CONTROL_1_FID_11_4_MASK;
ret = _mv88e6xxx_reg_write(ds, REG_PORT(port), PORT_CONTROL_1,
ret);
if (ret < 0)
return ret;
netdev_dbg(ds->ports[port], "FID %d (was %d)\n", *new, fid);
}
if (old)
*old = fid;
return 0;
}
static int _mv88e6xxx_port_fid_get(struct dsa_switch *ds, int port, u16 *fid)
{
return _mv88e6xxx_port_fid(ds, port, NULL, fid);
}
static int _mv88e6xxx_port_fid_set(struct dsa_switch *ds, int port, u16 fid)
{
return _mv88e6xxx_port_fid(ds, port, &fid, NULL);
}
static int _mv88e6xxx_fid_new(struct dsa_switch *ds, u16 *fid)
{
struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
DECLARE_BITMAP(fid_bitmap, MV88E6XXX_N_FID);
struct mv88e6xxx_vtu_stu_entry vlan;
int i, err;
bitmap_zero(fid_bitmap, MV88E6XXX_N_FID);
/* Set every FID bit used by the (un)bridged ports */
for (i = 0; i < ps->num_ports; ++i) {
err = _mv88e6xxx_port_fid_get(ds, i, fid);
if (err)
return err;
set_bit(*fid, fid_bitmap);
}
/* Set every FID bit used by the VLAN entries */
err = _mv88e6xxx_vtu_vid_write(ds, GLOBAL_VTU_VID_MASK);
if (err)
return err;
do {
err = _mv88e6xxx_vtu_getnext(ds, &vlan);
if (err)
return err;
if (!vlan.valid)
break;
set_bit(vlan.fid, fid_bitmap);
} while (vlan.vid < GLOBAL_VTU_VID_MASK);
/* The reset value 0x000 is used to indicate that multiple address
* databases are not needed. Return the next positive available.
*/
*fid = find_next_zero_bit(fid_bitmap, MV88E6XXX_N_FID, 1);
if (unlikely(*fid == MV88E6XXX_N_FID))
return -ENOSPC;
/* Clear the database */
return _mv88e6xxx_atu_flush(ds, *fid, true);
}
static int _mv88e6xxx_vtu_new(struct dsa_switch *ds, u16 vid,
struct mv88e6xxx_vtu_stu_entry *entry)
{ {
struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
struct mv88e6xxx_vtu_stu_entry vlan = { struct mv88e6xxx_vtu_stu_entry vlan = {
.valid = true, .valid = true,
.vid = vid, .vid = vid,
.fid = vid, /* We use one FID per VLAN */
}; };
int i; int i, err;
err = _mv88e6xxx_fid_new(ds, &vlan.fid);
if (err)
return err;
/* exclude all ports except the CPU and DSA ports */ /* exclude all ports except the CPU and DSA ports */
for (i = 0; i < ps->num_ports; ++i) for (i = 0; i < ps->num_ports; ++i)
...@@ -1478,7 +1604,6 @@ static int _mv88e6xxx_vlan_init(struct dsa_switch *ds, u16 vid, ...@@ -1478,7 +1604,6 @@ static int _mv88e6xxx_vlan_init(struct dsa_switch *ds, u16 vid,
if (mv88e6xxx_6097_family(ds) || mv88e6xxx_6165_family(ds) || if (mv88e6xxx_6097_family(ds) || mv88e6xxx_6165_family(ds) ||
mv88e6xxx_6351_family(ds) || mv88e6xxx_6352_family(ds)) { mv88e6xxx_6351_family(ds) || mv88e6xxx_6352_family(ds)) {
struct mv88e6xxx_vtu_stu_entry vstp; struct mv88e6xxx_vtu_stu_entry vstp;
int err;
/* Adding a VTU entry requires a valid STU entry. As VSTP is not /* Adding a VTU entry requires a valid STU entry. As VSTP is not
* implemented, only one STU entry is needed to cover all VTU * implemented, only one STU entry is needed to cover all VTU
...@@ -1498,17 +1623,41 @@ static int _mv88e6xxx_vlan_init(struct dsa_switch *ds, u16 vid, ...@@ -1498,17 +1623,41 @@ static int _mv88e6xxx_vlan_init(struct dsa_switch *ds, u16 vid,
if (err) if (err)
return err; return err;
} }
/* Clear all MAC addresses from the new database */
err = _mv88e6xxx_atu_flush(ds, vlan.fid, true);
if (err)
return err;
} }
*entry = vlan; *entry = vlan;
return 0; return 0;
} }
static int _mv88e6xxx_vtu_get(struct dsa_switch *ds, u16 vid,
struct mv88e6xxx_vtu_stu_entry *entry, bool creat)
{
int err;
if (!vid)
return -EINVAL;
err = _mv88e6xxx_vtu_vid_write(ds, vid - 1);
if (err)
return err;
err = _mv88e6xxx_vtu_getnext(ds, entry);
if (err)
return err;
if (entry->vid != vid || !entry->valid) {
if (!creat)
return -EOPNOTSUPP;
/* -ENOENT would've been more appropriate, but switchdev expects
* -EOPNOTSUPP to inform bridge about an eventual software VLAN.
*/
err = _mv88e6xxx_vtu_new(ds, vid, entry);
}
return err;
}
static int mv88e6xxx_port_check_hw_vlan(struct dsa_switch *ds, int port, static int mv88e6xxx_port_check_hw_vlan(struct dsa_switch *ds, int port,
u16 vid_begin, u16 vid_end) u16 vid_begin, u16 vid_end)
{ {
...@@ -1563,16 +1712,51 @@ static int mv88e6xxx_port_check_hw_vlan(struct dsa_switch *ds, int port, ...@@ -1563,16 +1712,51 @@ static int mv88e6xxx_port_check_hw_vlan(struct dsa_switch *ds, int port,
return err; return err;
} }
static const char * const mv88e6xxx_port_8021q_mode_names[] = {
[PORT_CONTROL_2_8021Q_DISABLED] = "Disabled",
[PORT_CONTROL_2_8021Q_FALLBACK] = "Fallback",
[PORT_CONTROL_2_8021Q_CHECK] = "Check",
[PORT_CONTROL_2_8021Q_SECURE] = "Secure",
};
int mv88e6xxx_port_vlan_filtering(struct dsa_switch *ds, int port,
bool vlan_filtering)
{
struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
u16 old, new = vlan_filtering ? PORT_CONTROL_2_8021Q_SECURE :
PORT_CONTROL_2_8021Q_DISABLED;
int ret;
mutex_lock(&ps->smi_mutex);
ret = _mv88e6xxx_reg_read(ds, REG_PORT(port), PORT_CONTROL_2);
if (ret < 0)
goto unlock;
old = ret & PORT_CONTROL_2_8021Q_MASK;
ret &= ~PORT_CONTROL_2_8021Q_MASK;
ret |= new & PORT_CONTROL_2_8021Q_MASK;
ret = _mv88e6xxx_reg_write(ds, REG_PORT(port), PORT_CONTROL_2, ret);
if (ret < 0)
goto unlock;
netdev_dbg(ds->ports[port], "802.1Q Mode: %s (was %s)\n",
mv88e6xxx_port_8021q_mode_names[new],
mv88e6xxx_port_8021q_mode_names[old]);
unlock:
mutex_unlock(&ps->smi_mutex);
return ret;
}
int mv88e6xxx_port_vlan_prepare(struct dsa_switch *ds, int port, int mv88e6xxx_port_vlan_prepare(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_vlan *vlan, const struct switchdev_obj_port_vlan *vlan,
struct switchdev_trans *trans) struct switchdev_trans *trans)
{ {
int err; int err;
/* We reserve a few VLANs to isolate unbridged ports */
if (vlan->vid_end >= 4000)
return -EOPNOTSUPP;
/* If the requested port doesn't belong to the same bridge as the VLAN /* If the requested port doesn't belong to the same bridge as the VLAN
* members, do not support it (yet) and fallback to software VLAN. * members, do not support it (yet) and fallback to software VLAN.
*/ */
...@@ -1593,20 +1777,10 @@ static int _mv88e6xxx_port_vlan_add(struct dsa_switch *ds, int port, u16 vid, ...@@ -1593,20 +1777,10 @@ static int _mv88e6xxx_port_vlan_add(struct dsa_switch *ds, int port, u16 vid,
struct mv88e6xxx_vtu_stu_entry vlan; struct mv88e6xxx_vtu_stu_entry vlan;
int err; int err;
err = _mv88e6xxx_vtu_vid_write(ds, vid - 1); err = _mv88e6xxx_vtu_get(ds, vid, &vlan, true);
if (err)
return err;
err = _mv88e6xxx_vtu_getnext(ds, &vlan);
if (err) if (err)
return err; return err;
if (vlan.vid != vid || !vlan.valid) {
err = _mv88e6xxx_vlan_init(ds, vid, &vlan);
if (err)
return err;
}
vlan.data[port] = untagged ? vlan.data[port] = untagged ?
GLOBAL_VTU_DATA_MEMBER_TAG_UNTAGGED : GLOBAL_VTU_DATA_MEMBER_TAG_UNTAGGED :
GLOBAL_VTU_DATA_MEMBER_TAG_TAGGED; GLOBAL_VTU_DATA_MEMBER_TAG_TAGGED;
...@@ -1647,16 +1821,12 @@ static int _mv88e6xxx_port_vlan_del(struct dsa_switch *ds, int port, u16 vid) ...@@ -1647,16 +1821,12 @@ static int _mv88e6xxx_port_vlan_del(struct dsa_switch *ds, int port, u16 vid)
struct mv88e6xxx_vtu_stu_entry vlan; struct mv88e6xxx_vtu_stu_entry vlan;
int i, err; int i, err;
err = _mv88e6xxx_vtu_vid_write(ds, vid - 1); err = _mv88e6xxx_vtu_get(ds, vid, &vlan, false);
if (err) if (err)
return err; return err;
err = _mv88e6xxx_vtu_getnext(ds, &vlan); /* Tell switchdev if this VLAN is handled in software */
if (err) if (vlan.data[port] == GLOBAL_VTU_DATA_MEMBER_TAG_NON_MEMBER)
return err;
if (vlan.vid != vid || !vlan.valid ||
vlan.data[port] == GLOBAL_VTU_DATA_MEMBER_TAG_NON_MEMBER)
return -EOPNOTSUPP; return -EOPNOTSUPP;
vlan.data[port] = GLOBAL_VTU_DATA_MEMBER_TAG_NON_MEMBER; vlan.data[port] = GLOBAL_VTU_DATA_MEMBER_TAG_NON_MEMBER;
...@@ -1684,7 +1854,6 @@ int mv88e6xxx_port_vlan_del(struct dsa_switch *ds, int port, ...@@ -1684,7 +1854,6 @@ int mv88e6xxx_port_vlan_del(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_vlan *vlan) const struct switchdev_obj_port_vlan *vlan)
{ {
struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
const u16 defpvid = 4000 + ds->index * DSA_MAX_PORTS + port;
u16 pvid, vid; u16 pvid, vid;
int err = 0; int err = 0;
...@@ -1700,8 +1869,7 @@ int mv88e6xxx_port_vlan_del(struct dsa_switch *ds, int port, ...@@ -1700,8 +1869,7 @@ int mv88e6xxx_port_vlan_del(struct dsa_switch *ds, int port,
goto unlock; goto unlock;
if (vid == pvid) { if (vid == pvid) {
/* restore reserved VLAN ID */ err = _mv88e6xxx_port_pvid_set(ds, port, 0);
err = _mv88e6xxx_port_pvid_set(ds, port, defpvid);
if (err) if (err)
goto unlock; goto unlock;
} }
...@@ -1774,8 +1942,18 @@ static int _mv88e6xxx_port_fdb_load(struct dsa_switch *ds, int port, ...@@ -1774,8 +1942,18 @@ static int _mv88e6xxx_port_fdb_load(struct dsa_switch *ds, int port,
u8 state) u8 state)
{ {
struct mv88e6xxx_atu_entry entry = { 0 }; struct mv88e6xxx_atu_entry entry = { 0 };
struct mv88e6xxx_vtu_stu_entry vlan;
int err;
entry.fid = vid; /* We use one FID per VLAN */ /* Null VLAN ID corresponds to the port private database */
if (vid == 0)
err = _mv88e6xxx_port_fid_get(ds, port, &vlan.fid);
else
err = _mv88e6xxx_vtu_get(ds, vid, &vlan, false);
if (err)
return err;
entry.fid = vlan.fid;
entry.state = state; entry.state = state;
ether_addr_copy(entry.mac, addr); ether_addr_copy(entry.mac, addr);
if (state != GLOBAL_ATU_DATA_STATE_UNUSED) { if (state != GLOBAL_ATU_DATA_STATE_UNUSED) {
...@@ -1790,10 +1968,6 @@ int mv88e6xxx_port_fdb_prepare(struct dsa_switch *ds, int port, ...@@ -1790,10 +1968,6 @@ int mv88e6xxx_port_fdb_prepare(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_fdb *fdb, const struct switchdev_obj_port_fdb *fdb,
struct switchdev_trans *trans) struct switchdev_trans *trans)
{ {
/* We don't use per-port FDB */
if (fdb->vid == 0)
return -EOPNOTSUPP;
/* We don't need any dynamic resource from the kernel (yet), /* We don't need any dynamic resource from the kernel (yet),
* so skip the prepare phase. * so skip the prepare phase.
*/ */
...@@ -1880,6 +2054,47 @@ static int _mv88e6xxx_atu_getnext(struct dsa_switch *ds, u16 fid, ...@@ -1880,6 +2054,47 @@ static int _mv88e6xxx_atu_getnext(struct dsa_switch *ds, u16 fid,
return 0; return 0;
} }
static int _mv88e6xxx_port_fdb_dump_one(struct dsa_switch *ds, u16 fid, u16 vid,
int port,
struct switchdev_obj_port_fdb *fdb,
int (*cb)(struct switchdev_obj *obj))
{
struct mv88e6xxx_atu_entry addr = {
.mac = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },
};
int err;
err = _mv88e6xxx_atu_mac_write(ds, addr.mac);
if (err)
return err;
do {
err = _mv88e6xxx_atu_getnext(ds, fid, &addr);
if (err)
break;
if (addr.state == GLOBAL_ATU_DATA_STATE_UNUSED)
break;
if (!addr.trunk && addr.portv_trunkid & BIT(port)) {
bool is_static = addr.state ==
(is_multicast_ether_addr(addr.mac) ?
GLOBAL_ATU_DATA_STATE_MC_STATIC :
GLOBAL_ATU_DATA_STATE_UC_STATIC);
fdb->vid = vid;
ether_addr_copy(fdb->addr, addr.mac);
fdb->ndm_state = is_static ? NUD_NOARP : NUD_REACHABLE;
err = cb(&fdb->obj);
if (err)
break;
}
} while (!is_broadcast_ether_addr(addr.mac));
return err;
}
int mv88e6xxx_port_fdb_dump(struct dsa_switch *ds, int port, int mv88e6xxx_port_fdb_dump(struct dsa_switch *ds, int port,
struct switchdev_obj_port_fdb *fdb, struct switchdev_obj_port_fdb *fdb,
int (*cb)(struct switchdev_obj *obj)) int (*cb)(struct switchdev_obj *obj))
...@@ -1888,55 +2103,37 @@ int mv88e6xxx_port_fdb_dump(struct dsa_switch *ds, int port, ...@@ -1888,55 +2103,37 @@ int mv88e6xxx_port_fdb_dump(struct dsa_switch *ds, int port,
struct mv88e6xxx_vtu_stu_entry vlan = { struct mv88e6xxx_vtu_stu_entry vlan = {
.vid = GLOBAL_VTU_VID_MASK, /* all ones */ .vid = GLOBAL_VTU_VID_MASK, /* all ones */
}; };
u16 fid;
int err; int err;
mutex_lock(&ps->smi_mutex); mutex_lock(&ps->smi_mutex);
/* Dump port's default Filtering Information Database (VLAN ID 0) */
err = _mv88e6xxx_port_fid_get(ds, port, &fid);
if (err)
goto unlock;
err = _mv88e6xxx_port_fdb_dump_one(ds, fid, 0, port, fdb, cb);
if (err)
goto unlock;
/* Dump VLANs' Filtering Information Databases */
err = _mv88e6xxx_vtu_vid_write(ds, vlan.vid); err = _mv88e6xxx_vtu_vid_write(ds, vlan.vid);
if (err) if (err)
goto unlock; goto unlock;
do { do {
struct mv88e6xxx_atu_entry addr = {
.mac = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },
};
err = _mv88e6xxx_vtu_getnext(ds, &vlan); err = _mv88e6xxx_vtu_getnext(ds, &vlan);
if (err) if (err)
goto unlock; break;
if (!vlan.valid) if (!vlan.valid)
break; break;
err = _mv88e6xxx_atu_mac_write(ds, addr.mac); err = _mv88e6xxx_port_fdb_dump_one(ds, vlan.fid, vlan.vid, port,
fdb, cb);
if (err) if (err)
goto unlock; break;
do {
err = _mv88e6xxx_atu_getnext(ds, vlan.fid, &addr);
if (err)
goto unlock;
if (addr.state == GLOBAL_ATU_DATA_STATE_UNUSED)
break;
if (!addr.trunk && addr.portv_trunkid & BIT(port)) {
bool is_static = addr.state ==
(is_multicast_ether_addr(addr.mac) ?
GLOBAL_ATU_DATA_STATE_MC_STATIC :
GLOBAL_ATU_DATA_STATE_UC_STATIC);
fdb->vid = vlan.vid;
ether_addr_copy(fdb->addr, addr.mac);
fdb->ndm_state = is_static ? NUD_NOARP :
NUD_REACHABLE;
err = cb(&fdb->obj);
if (err)
goto unlock;
}
} while (!is_broadcast_ether_addr(addr.mac));
} while (vlan.vid < GLOBAL_VTU_VID_MASK); } while (vlan.vid < GLOBAL_VTU_VID_MASK);
unlock: unlock:
...@@ -1949,32 +2146,76 @@ int mv88e6xxx_port_bridge_join(struct dsa_switch *ds, int port, ...@@ -1949,32 +2146,76 @@ int mv88e6xxx_port_bridge_join(struct dsa_switch *ds, int port,
struct net_device *bridge) struct net_device *bridge)
{ {
struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
u16 fid;
int i, err;
mutex_lock(&ps->smi_mutex);
/* Get or create the bridge FID and assign it to the port */
for (i = 0; i < ps->num_ports; ++i)
if (ps->ports[i].bridge_dev == bridge)
break;
if (i < ps->num_ports)
err = _mv88e6xxx_port_fid_get(ds, i, &fid);
else
err = _mv88e6xxx_fid_new(ds, &fid);
if (err)
goto unlock;
err = _mv88e6xxx_port_fid_set(ds, port, fid);
if (err)
goto unlock;
/* Assign the bridge and remap each port's VLANTable */
ps->ports[port].bridge_dev = bridge; ps->ports[port].bridge_dev = bridge;
return 0; for (i = 0; i < ps->num_ports; ++i) {
if (ps->ports[i].bridge_dev == bridge) {
err = _mv88e6xxx_port_based_vlan_map(ds, i);
if (err)
break;
}
}
unlock:
mutex_unlock(&ps->smi_mutex);
return err;
} }
int mv88e6xxx_port_bridge_leave(struct dsa_switch *ds, int port) int mv88e6xxx_port_bridge_leave(struct dsa_switch *ds, int port)
{ {
struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
struct net_device *bridge = ps->ports[port].bridge_dev;
u16 fid;
int i, err;
ps->ports[port].bridge_dev = NULL; mutex_lock(&ps->smi_mutex);
return 0; /* Give the port a fresh Filtering Information Database */
} err = _mv88e6xxx_fid_new(ds, &fid);
if (err)
goto unlock;
static int mv88e6xxx_setup_port_default_vlan(struct dsa_switch *ds, int port) err = _mv88e6xxx_port_fid_set(ds, port, fid);
{ if (err)
struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); goto unlock;
const u16 pvid = 4000 + ds->index * DSA_MAX_PORTS + port;
int err;
mutex_lock(&ps->smi_mutex); /* Unassign the bridge and remap each port's VLANTable */
err = _mv88e6xxx_port_vlan_add(ds, port, pvid, true); ps->ports[port].bridge_dev = NULL;
if (!err)
err = _mv88e6xxx_port_pvid_set(ds, port, pvid); for (i = 0; i < ps->num_ports; ++i) {
if (i == port || ps->ports[i].bridge_dev == bridge) {
err = _mv88e6xxx_port_based_vlan_map(ds, i);
if (err)
break;
}
}
unlock:
mutex_unlock(&ps->smi_mutex); mutex_unlock(&ps->smi_mutex);
return err; return err;
} }
...@@ -2098,7 +2339,7 @@ static int mv88e6xxx_setup_port(struct dsa_switch *ds, int port) ...@@ -2098,7 +2339,7 @@ static int mv88e6xxx_setup_port(struct dsa_switch *ds, int port)
} }
/* Port Control 2: don't force a good FCS, set the maximum frame size to /* Port Control 2: don't force a good FCS, set the maximum frame size to
* 10240 bytes, enable secure 802.1q tags, don't discard tagged or * 10240 bytes, disable 802.1q tags checking, don't discard tagged or
* untagged frames on this port, do a destination address lookup on all * untagged frames on this port, do a destination address lookup on all
* received packets as usual, disable ARP mirroring and don't send a * received packets as usual, disable ARP mirroring and don't send a
* copy of all transmitted/received frames on this port to the CPU. * copy of all transmitted/received frames on this port to the CPU.
...@@ -2123,7 +2364,7 @@ static int mv88e6xxx_setup_port(struct dsa_switch *ds, int port) ...@@ -2123,7 +2364,7 @@ static int mv88e6xxx_setup_port(struct dsa_switch *ds, int port)
reg |= PORT_CONTROL_2_FORWARD_UNKNOWN; reg |= PORT_CONTROL_2_FORWARD_UNKNOWN;
} }
reg |= PORT_CONTROL_2_8021Q_SECURE; reg |= PORT_CONTROL_2_8021Q_DISABLED;
if (reg) { if (reg) {
ret = _mv88e6xxx_reg_write(ds, REG_PORT(port), ret = _mv88e6xxx_reg_write(ds, REG_PORT(port),
...@@ -2220,12 +2461,15 @@ static int mv88e6xxx_setup_port(struct dsa_switch *ds, int port) ...@@ -2220,12 +2461,15 @@ static int mv88e6xxx_setup_port(struct dsa_switch *ds, int port)
if (ret) if (ret)
goto abort; goto abort;
/* Port based VLAN map: do not give each port its own address /* Port based VLAN map: give each port its own address
* database, and allow every port to egress frames on all other ports. * database, and allow bidirectional communication between the
* CPU and DSA port(s), and the other ports.
*/ */
reg = BIT(ps->num_ports) - 1; /* all ports */ ret = _mv88e6xxx_port_fid_set(ds, port, port + 1);
reg &= ~BIT(port); /* except itself */ if (ret)
ret = _mv88e6xxx_port_vlan_map_set(ds, port, reg); goto abort;
ret = _mv88e6xxx_port_based_vlan_map(ds, port);
if (ret) if (ret)
goto abort; goto abort;
...@@ -2249,13 +2493,6 @@ int mv88e6xxx_setup_ports(struct dsa_switch *ds) ...@@ -2249,13 +2493,6 @@ int mv88e6xxx_setup_ports(struct dsa_switch *ds)
ret = mv88e6xxx_setup_port(ds, i); ret = mv88e6xxx_setup_port(ds, i);
if (ret < 0) if (ret < 0)
return ret; return ret;
if (dsa_is_cpu_port(ds, i) || dsa_is_dsa_port(ds, i))
continue;
ret = mv88e6xxx_setup_port_default_vlan(ds, i);
if (ret < 0)
return ret;
} }
return 0; return 0;
} }
......
...@@ -133,7 +133,9 @@ ...@@ -133,7 +133,9 @@
#define PORT_CONTROL_STATE_LEARNING 0x02 #define PORT_CONTROL_STATE_LEARNING 0x02
#define PORT_CONTROL_STATE_FORWARDING 0x03 #define PORT_CONTROL_STATE_FORWARDING 0x03
#define PORT_CONTROL_1 0x05 #define PORT_CONTROL_1 0x05
#define PORT_CONTROL_1_FID_11_4_MASK (0xff << 0)
#define PORT_BASE_VLAN 0x06 #define PORT_BASE_VLAN 0x06
#define PORT_BASE_VLAN_FID_3_0_MASK (0xf << 12)
#define PORT_DEFAULT_VLAN 0x07 #define PORT_DEFAULT_VLAN 0x07
#define PORT_DEFAULT_VLAN_MASK 0xfff #define PORT_DEFAULT_VLAN_MASK 0xfff
#define PORT_CONTROL_2 0x08 #define PORT_CONTROL_2 0x08
...@@ -355,6 +357,8 @@ ...@@ -355,6 +357,8 @@
#define GLOBAL2_QOS_WEIGHT 0x1c #define GLOBAL2_QOS_WEIGHT 0x1c
#define GLOBAL2_MISC 0x1d #define GLOBAL2_MISC 0x1d
#define MV88E6XXX_N_FID 4096
struct mv88e6xxx_switch_id { struct mv88e6xxx_switch_id {
u16 id; u16 id;
char *name; char *name;
...@@ -486,6 +490,8 @@ int mv88e6xxx_port_bridge_join(struct dsa_switch *ds, int port, ...@@ -486,6 +490,8 @@ int mv88e6xxx_port_bridge_join(struct dsa_switch *ds, int port,
struct net_device *bridge); struct net_device *bridge);
int mv88e6xxx_port_bridge_leave(struct dsa_switch *ds, int port); int mv88e6xxx_port_bridge_leave(struct dsa_switch *ds, int port);
int mv88e6xxx_port_stp_update(struct dsa_switch *ds, int port, u8 state); int mv88e6xxx_port_stp_update(struct dsa_switch *ds, int port, u8 state);
int mv88e6xxx_port_vlan_filtering(struct dsa_switch *ds, int port,
bool vlan_filtering);
int mv88e6xxx_port_vlan_prepare(struct dsa_switch *ds, int port, int mv88e6xxx_port_vlan_prepare(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_vlan *vlan, const struct switchdev_obj_port_vlan *vlan,
struct switchdev_trans *trans); struct switchdev_trans *trans);
......
...@@ -305,6 +305,8 @@ struct dsa_switch_driver { ...@@ -305,6 +305,8 @@ struct dsa_switch_driver {
/* /*
* VLAN support * VLAN support
*/ */
int (*port_vlan_filtering)(struct dsa_switch *ds, int port,
bool vlan_filtering);
int (*port_vlan_prepare)(struct dsa_switch *ds, int port, int (*port_vlan_prepare)(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_vlan *vlan, const struct switchdev_obj_port_vlan *vlan,
struct switchdev_trans *trans); struct switchdev_trans *trans);
......
...@@ -317,6 +317,24 @@ static int dsa_slave_stp_update(struct net_device *dev, u8 state) ...@@ -317,6 +317,24 @@ static int dsa_slave_stp_update(struct net_device *dev, u8 state)
return ret; return ret;
} }
static int dsa_slave_vlan_filtering(struct net_device *dev,
const struct switchdev_attr *attr,
struct switchdev_trans *trans)
{
struct dsa_slave_priv *p = netdev_priv(dev);
struct dsa_switch *ds = p->parent;
/* bridge skips -EOPNOTSUPP, so skip the prepare phase */
if (switchdev_trans_ph_prepare(trans))
return 0;
if (ds->drv->port_vlan_filtering)
return ds->drv->port_vlan_filtering(ds, p->port,
attr->u.vlan_filtering);
return 0;
}
static int dsa_slave_port_attr_set(struct net_device *dev, static int dsa_slave_port_attr_set(struct net_device *dev,
const struct switchdev_attr *attr, const struct switchdev_attr *attr,
struct switchdev_trans *trans) struct switchdev_trans *trans)
...@@ -333,6 +351,9 @@ static int dsa_slave_port_attr_set(struct net_device *dev, ...@@ -333,6 +351,9 @@ static int dsa_slave_port_attr_set(struct net_device *dev,
ret = ds->drv->port_stp_update(ds, p->port, ret = ds->drv->port_stp_update(ds, p->port,
attr->u.stp_state); attr->u.stp_state);
break; break;
case SWITCHDEV_ATTR_ID_BRIDGE_VLAN_FILTERING:
ret = dsa_slave_vlan_filtering(dev, attr, trans);
break;
default: default:
ret = -EOPNOTSUPP; ret = -EOPNOTSUPP;
break; break;
......
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