This commit implements the port_vlan_add, port_vlan_kill, and port_bridge_setlink dsa_switch_driver functions to access the VTU, and thus add support for adding, removing VLANs, and joining ports to them.
Signed-off-by: Vivien Didelot <vivien.dide...@savoirfairelinux.com> --- drivers/net/dsa/mv88e6xxx.c | 309 ++++++++++++++++++++++++++++++++++++++++++++ drivers/net/dsa/mv88e6xxx.h | 28 ++++ 2 files changed, 337 insertions(+) diff --git a/drivers/net/dsa/mv88e6xxx.c b/drivers/net/dsa/mv88e6xxx.c index cf309aa9..2f4c99f 100644 --- a/drivers/net/dsa/mv88e6xxx.c +++ b/drivers/net/dsa/mv88e6xxx.c @@ -2,6 +2,9 @@ * net/dsa/mv88e6xxx.c - Marvell 88e6xxx switch chip support * Copyright (c) 2008 Marvell Semiconductor * + * Copyright (c) 2015 CMC Electronics, Inc. + * Added support for 802.1q VTU operations + * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or @@ -1241,6 +1244,312 @@ static void mv88e6xxx_bridge_work(struct work_struct *work) } } +static int _mv88e6xxx_vtu_wait(struct dsa_switch *ds) +{ + return _mv88e6xxx_wait(ds, REG_GLOBAL, GLOBAL_VTU_OP, + GLOBAL_VTU_OP_BUSY); +} + +static int _mv88e6xxx_vtu_cmd(struct dsa_switch *ds, u16 op) +{ + int ret; + + ret = _mv88e6xxx_reg_write(ds, REG_GLOBAL, GLOBAL_VTU_OP, op); + if (ret < 0) + return ret; + + return _mv88e6xxx_vtu_wait(ds); +} + +static int _mv88e6xxx_stu_loadpurge(struct dsa_switch *ds, u8 sid, bool valid) +{ + int ret, data; + + ret = _mv88e6xxx_vtu_wait(ds); + if (ret < 0) + return ret; + + data = sid & GLOBAL_VTU_SID_MASK; + if (valid) + data |= GLOBAL_VTU_VID_VALID; + + ret = _mv88e6xxx_reg_write(ds, REG_GLOBAL, GLOBAL_VTU_VID, data); + if (ret < 0) + return ret; + + /* Unused (yet) data registers */ + ret = _mv88e6xxx_reg_write(ds, REG_GLOBAL, GLOBAL_VTU_DATA_0_3, 0); + if (ret < 0) + return ret; + + ret = _mv88e6xxx_reg_write(ds, REG_GLOBAL, GLOBAL_VTU_DATA_4_7, 0); + if (ret < 0) + return ret; + + ret = _mv88e6xxx_reg_write(ds, REG_GLOBAL, GLOBAL_VTU_DATA_8_11, 0); + if (ret < 0) + return ret; + + return _mv88e6xxx_vtu_cmd(ds, GLOBAL_VTU_OP_STU_LOAD_PURGE); +} + +static int _mv88e6xxx_vtu_getnext(struct dsa_switch *ds, u16 vid, + struct mv88e6xxx_vtu_entry *entry) +{ + int ret, i; + + ret = _mv88e6xxx_vtu_wait(ds); + if (ret < 0) + return ret; + + ret = _mv88e6xxx_reg_write(ds, REG_GLOBAL, GLOBAL_VTU_VID, + vid & GLOBAL_VTU_VID_MASK); + if (ret < 0) + return ret; + + ret = _mv88e6xxx_vtu_cmd(ds, GLOBAL_VTU_OP_VTU_GET_NEXT); + if (ret < 0) + return ret; + + ret = _mv88e6xxx_reg_read(ds, REG_GLOBAL, GLOBAL_VTU_VID); + if (ret < 0) + return ret; + + entry->vid = ret & GLOBAL_VTU_VID_MASK; + entry->valid = !!(ret & GLOBAL_VTU_VID_VALID); + + if (entry->valid) { + /* Ports 0-3, offsets 0, 4, 8, 12 */ + ret = _mv88e6xxx_reg_read(ds, REG_GLOBAL, GLOBAL_VTU_DATA_0_3); + if (ret < 0) + return ret; + + for (i = 0; i < 4; ++i) + entry->tags[i] = (ret >> (i * 4)) & 3; + + /* Ports 4-6, offsets 0, 4, 8 */ + ret = _mv88e6xxx_reg_read(ds, REG_GLOBAL, GLOBAL_VTU_DATA_4_7); + if (ret < 0) + return ret; + + for (i = 4; i < 7; ++i) + entry->tags[i] = (ret >> ((i - 4) * 4)) & 3; + + ret = _mv88e6xxx_reg_read(ds, REG_GLOBAL, GLOBAL_VTU_FID); + if (ret < 0) + return ret; + + entry->fid = ret & GLOBAL_VTU_FID_MASK; + + ret = _mv88e6xxx_reg_read(ds, REG_GLOBAL, GLOBAL_VTU_SID); + if (ret < 0) + return ret; + + entry->sid = ret & GLOBAL_VTU_SID_MASK; + } + + return 0; +} + +static int _mv88e6xxx_vtu_loadpurge(struct dsa_switch *ds, + struct mv88e6xxx_vtu_entry *entry) +{ + u16 data = 0; + int ret, i; + + ret = _mv88e6xxx_vtu_wait(ds); + if (ret < 0) + return ret; + + if (entry->valid) { + /* Set Data Register, ports 0-3, offsets 0, 4, 8, 12 */ + for (data = i = 0; i < 4; ++i) + data |= entry->tags[i] << (i * 4); + ret = _mv88e6xxx_reg_write(ds, REG_GLOBAL, GLOBAL_VTU_DATA_0_3, + data); + if (ret < 0) + return ret; + + /* Set Data Register, ports 4-6, offsets 0, 4, 8 */ + for (data = 0, i = 4; i < 7; ++i) + data |= entry->tags[i] << ((i - 4) * 4); + ret = _mv88e6xxx_reg_write(ds, REG_GLOBAL, GLOBAL_VTU_DATA_4_7, + data); + if (ret < 0) + return ret; + + /* Unused Data Register */ + ret = _mv88e6xxx_reg_write(ds, REG_GLOBAL, GLOBAL_VTU_DATA_8_11, + 0); + if (ret < 0) + return ret; + + ret = _mv88e6xxx_reg_write(ds, REG_GLOBAL, GLOBAL_VTU_SID, + entry->sid & GLOBAL_VTU_SID_MASK); + if (ret < 0) + return ret; + + ret = _mv88e6xxx_reg_write(ds, REG_GLOBAL, GLOBAL_VTU_FID, + entry->fid & GLOBAL_VTU_FID_MASK); + if (ret < 0) + return ret; + + /* Valid bit set means load, unset means purge */ + data = GLOBAL_VTU_VID_VALID; + } + + data |= entry->vid & GLOBAL_VTU_VID_MASK; + ret = _mv88e6xxx_reg_write(ds, REG_GLOBAL, GLOBAL_VTU_VID, data); + if (ret < 0) + return ret; + + return _mv88e6xxx_vtu_cmd(ds, GLOBAL_VTU_OP_VTU_LOAD_PURGE); +} + +int mv88e6xxx_port_vlan_add(struct dsa_switch *ds, int port, u16 vid) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + struct mv88e6xxx_vtu_entry entry = { 0 }; + int prev_vid = vid ? vid - 1 : 4095; + int i, ret; + + /* Bringing an interface up adds it to the VLAN 0. Ignore this. */ + if (!vid) + return 0; + + /* The DSA port-based VLAN setup reserves FID 0 to DSA_MAX_PORTS; + * we will use the next FIDs for 802.1q; + * thus, forbid the last DSA_MAX_PORTS VLANs. + */ + if (vid > 4095 - DSA_MAX_PORTS) + return -EINVAL; + + mutex_lock(&ps->smi_mutex); + ret = _mv88e6xxx_vtu_getnext(ds, prev_vid, &entry); + if (ret < 0) + goto unlock; + + /* If the VLAN does not exist, re-initialize the entry for addition */ + if (entry.vid != vid || !entry.valid) { + memset(&entry, 0, sizeof(entry)); + entry.valid = true; + entry.vid = vid; + entry.fid = DSA_MAX_PORTS + vid; + entry.sid = 0; /* We don't use 802.1s (yet) */ + + /* A VTU entry must have a valid STU entry (undocumented). + * The default STU pointer for a VTU entry is 0. If per VLAN + * spanning tree is not used then only one STU entry is needed + * to cover all VTU entries. Thus, validate the STU entry 0. + */ + ret = _mv88e6xxx_stu_loadpurge(ds, 0, true); + if (ret < 0) + goto unlock; + + for (i = 0; i < ps->num_ports; ++i) + entry.tags[i] = dsa_is_cpu_port(ds, i) ? + GLOBAL_VTU_DATA_MEMBER_TAG_TAGGED : + GLOBAL_VTU_DATA_MEMBER_TAG_NON_MEMBER; + } + + entry.tags[port] = GLOBAL_VTU_DATA_MEMBER_TAG_UNTAGGED; + + ret = _mv88e6xxx_vtu_loadpurge(ds, &entry); +unlock: + mutex_unlock(&ps->smi_mutex); + + return ret; +} + +int mv88e6xxx_port_vlan_kill(struct dsa_switch *ds, int port, u16 vid) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + struct mv88e6xxx_vtu_entry entry = { 0 }; + int i, ret, prev_vid = vid ? vid - 1 : 4095; + bool keep = false; + + /* Bringing an interface up adds it to the VLAN 0. Ignore this. */ + if (!vid) + return 0; + + mutex_lock(&ps->smi_mutex); + ret = _mv88e6xxx_vtu_getnext(ds, prev_vid, &entry); + if (ret < 0) + goto unlock; + + if (entry.vid != vid || !entry.valid) { + ret = -EINVAL; + goto unlock; + } + + entry.tags[port] = GLOBAL_VTU_DATA_MEMBER_TAG_NON_MEMBER; + + /* keep the VLAN unless all ports are excluded */ + for (i = 0; i < ps->num_ports; ++i) { + if (dsa_is_cpu_port(ds, i)) + continue; + + if (entry.tags[i] != GLOBAL_VTU_DATA_MEMBER_TAG_NON_MEMBER) { + keep = true; + break; + } + } + + entry.valid = keep; + ret = _mv88e6xxx_vtu_loadpurge(ds, &entry); + if (ret < 0) + goto unlock; + + if (!keep) + ret = _mv88e6xxx_update_bridge_config(ds, entry.fid); +unlock: + mutex_unlock(&ps->smi_mutex); + + return ret; +} + +int mv88e6xxx_port_bridge_setlink(struct dsa_switch *ds, int port, + struct bridge_vlan_info *vinfo) +{ + struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); + struct mv88e6xxx_vtu_entry entry = { 0 }; + int prev_vid = vinfo->vid > 0 ? vinfo->vid - 1 : 4095; + int ret; + + /* Bringing an interface up adds it to the VLAN 0. Ignore this. */ + if (!vinfo->vid) + return 0; + + mutex_lock(&ps->smi_mutex); + ret = _mv88e6xxx_vtu_getnext(ds, prev_vid, &entry); + if (ret < 0) + goto unlock; + + if (entry.vid != vinfo->vid || !entry.valid) { + ret = -EINVAL; + goto unlock; + } + + /* Set port default VID */ + if (vinfo->flags & BRIDGE_VLAN_INFO_PVID) { + ret = _mv88e6xxx_reg_write(ds, REG_PORT(port), + PORT_DEFAULT_VLAN, + vinfo->vid & PORT_DEFAULT_VLAN_MASK); + if (ret < 0) + goto unlock; + } + + /* Update VTU entry with the new port membership */ + entry.tags[port] = vinfo->flags & BRIDGE_VLAN_INFO_UNTAGGED ? + GLOBAL_VTU_DATA_MEMBER_TAG_UNTAGGED : + GLOBAL_VTU_DATA_MEMBER_TAG_TAGGED; + ret = _mv88e6xxx_vtu_loadpurge(ds, &entry); +unlock: + mutex_unlock(&ps->smi_mutex); + + return ret; +} + int mv88e6xxx_setup_port_common(struct dsa_switch *ds, int port) { struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); diff --git a/drivers/net/dsa/mv88e6xxx.h b/drivers/net/dsa/mv88e6xxx.h index e045154..383b17af 100644 --- a/drivers/net/dsa/mv88e6xxx.h +++ b/drivers/net/dsa/mv88e6xxx.h @@ -73,6 +73,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_RATE_CONTROL 0x09 #define PORT_RATE_CONTROL_2 0x0a @@ -96,6 +97,10 @@ #define GLOBAL_MAC_01 0x01 #define GLOBAL_MAC_23 0x02 #define GLOBAL_MAC_45 0x03 +#define GLOBAL_VTU_FID 0x02 /* 6352 only? */ +#define GLOBAL_VTU_FID_MASK 0xfff +#define GLOBAL_VTU_SID 0x03 /* 6352 only? */ +#define GLOBAL_VTU_SID_MASK 0x3f #define GLOBAL_CONTROL 0x04 #define GLOBAL_CONTROL_SW_RESET BIT(15) #define GLOBAL_CONTROL_PPU_ENABLE BIT(14) @@ -112,9 +117,20 @@ #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 ((1 << 12) | GLOBAL_VTU_OP_BUSY) +#define GLOBAL_VTU_OP_VTU_LOAD_PURGE ((3 << 12) | GLOBAL_VTU_OP_BUSY) +#define GLOBAL_VTU_OP_VTU_GET_NEXT ((4 << 12) | GLOBAL_VTU_OP_BUSY) +#define GLOBAL_VTU_OP_STU_LOAD_PURGE ((5 << 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_MEMBER_TAG_UNMODIFIED 0 +#define GLOBAL_VTU_DATA_MEMBER_TAG_UNTAGGED 1 +#define GLOBAL_VTU_DATA_MEMBER_TAG_TAGGED 2 +#define GLOBAL_VTU_DATA_MEMBER_TAG_NON_MEMBER 3 #define GLOBAL_VTU_DATA_8_11 0x09 #define GLOBAL_ATU_CONTROL 0x0a #define GLOBAL_ATU_OP 0x0b @@ -205,6 +221,14 @@ #define GLOBAL2_QOS_WEIGHT 0x1c #define GLOBAL2_MISC 0x1d +struct mv88e6xxx_vtu_entry { + u16 vid; + u16 fid; + u8 sid; + bool valid; + u8 tags[DSA_MAX_PORTS]; +}; + struct mv88e6xxx_priv_state { /* When using multi-chip addressing, this mutex protects * access to the indirect access registers. (In single-chip @@ -310,6 +334,10 @@ int mv88e6xxx_port_fdb_getnext(struct dsa_switch *ds, int port, int mv88e6xxx_phy_page_read(struct dsa_switch *ds, int port, int page, int reg); int mv88e6xxx_phy_page_write(struct dsa_switch *ds, int port, int page, int reg, int val); +int mv88e6xxx_port_vlan_add(struct dsa_switch *ds, int port, u16 vid); +int mv88e6xxx_port_vlan_kill(struct dsa_switch *ds,int port, u16 vid); +int mv88e6xxx_port_bridge_setlink(struct dsa_switch *ds,int port, + struct bridge_vlan_info *vinfo); extern struct dsa_switch_driver mv88e6131_switch_driver; extern struct dsa_switch_driver mv88e6123_61_65_switch_driver; extern struct dsa_switch_driver mv88e6352_switch_driver; -- 2.4.1 -- To unsubscribe from this list: send the line "unsubscribe netdev" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html