diff options
Diffstat (limited to 'drivers/net/dsa')
-rw-r--r-- | drivers/net/dsa/Kconfig | 2 | ||||
-rw-r--r-- | drivers/net/dsa/b53/b53_common.c | 10 | ||||
-rw-r--r-- | drivers/net/dsa/b53/b53_priv.h | 7 | ||||
-rw-r--r-- | drivers/net/dsa/bcm_sf2.c | 11 | ||||
-rw-r--r-- | drivers/net/dsa/dsa_loop.c | 9 | ||||
-rw-r--r-- | drivers/net/dsa/lan9303-core.c | 138 | ||||
-rw-r--r-- | drivers/net/dsa/microchip/ksz_common.c | 12 | ||||
-rw-r--r-- | drivers/net/dsa/mt7530.c | 288 | ||||
-rw-r--r-- | drivers/net/dsa/mt7530.h | 83 | ||||
-rw-r--r-- | drivers/net/dsa/mv88e6xxx/chip.c | 94 | ||||
-rw-r--r-- | drivers/net/dsa/mv88e6xxx/chip.h | 2 | ||||
-rw-r--r-- | drivers/net/dsa/mv88e6xxx/global1.h | 16 | ||||
-rw-r--r-- | drivers/net/dsa/mv88e6xxx/global1_atu.c | 87 | ||||
-rw-r--r-- | drivers/net/dsa/mv88e6xxx/global1_vtu.c | 74 |
14 files changed, 695 insertions, 138 deletions
diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig index 83a9bc892a3b..2b81b97e994f 100644 --- a/drivers/net/dsa/Kconfig +++ b/drivers/net/dsa/Kconfig @@ -33,7 +33,7 @@ config NET_DSA_MT7530 config NET_DSA_MV88E6060 tristate "Marvell 88E6060 ethernet switch chip support" - depends on NET_DSA + depends on NET_DSA && NET_DSA_LEGACY select NET_DSA_TAG_TRAILER ---help--- This enables support for the Marvell 88E6060 ethernet switch diff --git a/drivers/net/dsa/b53/b53_common.c b/drivers/net/dsa/b53/b53_common.c index 4498ab897d94..db830a1141d9 100644 --- a/drivers/net/dsa/b53/b53_common.c +++ b/drivers/net/dsa/b53/b53_common.c @@ -1029,8 +1029,7 @@ int b53_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering) EXPORT_SYMBOL(b53_vlan_filtering); int b53_vlan_prepare(struct dsa_switch *ds, int port, - const struct switchdev_obj_port_vlan *vlan, - struct switchdev_trans *trans) + const struct switchdev_obj_port_vlan *vlan) { struct b53_device *dev = ds->priv; @@ -1047,8 +1046,7 @@ int b53_vlan_prepare(struct dsa_switch *ds, int port, EXPORT_SYMBOL(b53_vlan_prepare); void b53_vlan_add(struct dsa_switch *ds, int port, - const struct switchdev_obj_port_vlan *vlan, - struct switchdev_trans *trans) + const struct switchdev_obj_port_vlan *vlan) { struct b53_device *dev = ds->priv; bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED; @@ -1495,8 +1493,7 @@ static bool b53_can_enable_brcm_tags(struct dsa_switch *ds, int port) return false; } -static enum dsa_tag_protocol b53_get_tag_protocol(struct dsa_switch *ds, - int port) +enum dsa_tag_protocol b53_get_tag_protocol(struct dsa_switch *ds, int port) { struct b53_device *dev = ds->priv; @@ -1517,6 +1514,7 @@ static enum dsa_tag_protocol b53_get_tag_protocol(struct dsa_switch *ds, return DSA_TAG_PROTO_BRCM; } +EXPORT_SYMBOL(b53_get_tag_protocol); int b53_mirror_add(struct dsa_switch *ds, int port, struct dsa_mall_mirror_tc_entry *mirror, bool ingress) diff --git a/drivers/net/dsa/b53/b53_priv.h b/drivers/net/dsa/b53/b53_priv.h index daaaa1ecb996..d954cf36ecd8 100644 --- a/drivers/net/dsa/b53/b53_priv.h +++ b/drivers/net/dsa/b53/b53_priv.h @@ -295,11 +295,9 @@ void b53_br_set_stp_state(struct dsa_switch *ds, int port, u8 state); void b53_br_fast_age(struct dsa_switch *ds, int port); int b53_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering); int b53_vlan_prepare(struct dsa_switch *ds, int port, - const struct switchdev_obj_port_vlan *vlan, - struct switchdev_trans *trans); + const struct switchdev_obj_port_vlan *vlan); void b53_vlan_add(struct dsa_switch *ds, int port, - const struct switchdev_obj_port_vlan *vlan, - struct switchdev_trans *trans); + const struct switchdev_obj_port_vlan *vlan); int b53_vlan_del(struct dsa_switch *ds, int port, const struct switchdev_obj_port_vlan *vlan); int b53_fdb_add(struct dsa_switch *ds, int port, @@ -310,6 +308,7 @@ int b53_fdb_dump(struct dsa_switch *ds, int port, dsa_fdb_dump_cb_t *cb, void *data); int b53_mirror_add(struct dsa_switch *ds, int port, struct dsa_mall_mirror_tc_entry *mirror, bool ingress); +enum dsa_tag_protocol b53_get_tag_protocol(struct dsa_switch *ds, int port); void b53_mirror_del(struct dsa_switch *ds, int port, struct dsa_mall_mirror_tc_entry *mirror); int b53_enable_port(struct dsa_switch *ds, int port, struct phy_device *phy); diff --git a/drivers/net/dsa/bcm_sf2.c b/drivers/net/dsa/bcm_sf2.c index b62d47210db8..0378eded31f2 100644 --- a/drivers/net/dsa/bcm_sf2.c +++ b/drivers/net/dsa/bcm_sf2.c @@ -34,12 +34,6 @@ #include "b53/b53_priv.h" #include "b53/b53_regs.h" -static enum dsa_tag_protocol bcm_sf2_sw_get_tag_protocol(struct dsa_switch *ds, - int port) -{ - return DSA_TAG_PROTO_BRCM; -} - static void bcm_sf2_imp_setup(struct dsa_switch *ds, int port) { struct bcm_sf2_priv *priv = bcm_sf2_to_priv(ds); @@ -860,7 +854,7 @@ static const struct b53_io_ops bcm_sf2_io_ops = { }; static const struct dsa_switch_ops bcm_sf2_ops = { - .get_tag_protocol = bcm_sf2_sw_get_tag_protocol, + .get_tag_protocol = b53_get_tag_protocol, .setup = bcm_sf2_sw_setup, .get_strings = b53_get_strings, .get_ethtool_stats = b53_get_ethtool_stats, @@ -954,6 +948,9 @@ static const struct of_device_id bcm_sf2_of_match[] = { { .compatible = "brcm,bcm7278-switch-v4.0", .data = &bcm_sf2_7278_data }, + { .compatible = "brcm,bcm7278-switch-v4.8", + .data = &bcm_sf2_7278_data + }, { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, bcm_sf2_of_match); diff --git a/drivers/net/dsa/dsa_loop.c b/drivers/net/dsa/dsa_loop.c index bb71d3d6f65b..7aa84ee4e771 100644 --- a/drivers/net/dsa/dsa_loop.c +++ b/drivers/net/dsa/dsa_loop.c @@ -174,9 +174,9 @@ static int dsa_loop_port_vlan_filtering(struct dsa_switch *ds, int port, return 0; } -static int dsa_loop_port_vlan_prepare(struct dsa_switch *ds, int port, - const struct switchdev_obj_port_vlan *vlan, - struct switchdev_trans *trans) +static int +dsa_loop_port_vlan_prepare(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan) { struct dsa_loop_priv *ps = ds->priv; struct mii_bus *bus = ps->bus; @@ -193,8 +193,7 @@ static int dsa_loop_port_vlan_prepare(struct dsa_switch *ds, int port, } static void dsa_loop_port_vlan_add(struct dsa_switch *ds, int port, - const struct switchdev_obj_port_vlan *vlan, - struct switchdev_trans *trans) + const struct switchdev_obj_port_vlan *vlan) { bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED; bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID; diff --git a/drivers/net/dsa/lan9303-core.c b/drivers/net/dsa/lan9303-core.c index b24566bb74d2..6171c0853ff1 100644 --- a/drivers/net/dsa/lan9303-core.c +++ b/drivers/net/dsa/lan9303-core.c @@ -249,6 +249,28 @@ static int lan9303_read(struct regmap *regmap, unsigned int offset, u32 *reg) return -EIO; } +static int lan9303_read_wait(struct lan9303 *chip, int offset, u32 mask) +{ + int i; + + for (i = 0; i < 25; i++) { + u32 reg; + int ret; + + ret = lan9303_read(chip->regmap, offset, ®); + if (ret) { + dev_err(chip->dev, "%s failed to read offset %d: %d\n", + __func__, offset, ret); + return ret; + } + if (!(reg & mask)) + return 0; + usleep_range(1000, 2000); + } + + return -ETIMEDOUT; +} + static int lan9303_virt_phy_reg_read(struct lan9303 *chip, int regnum) { int ret; @@ -274,22 +296,8 @@ static int lan9303_virt_phy_reg_write(struct lan9303 *chip, int regnum, u16 val) static int lan9303_indirect_phy_wait_for_completion(struct lan9303 *chip) { - int ret, i; - u32 reg; - - for (i = 0; i < 25; i++) { - ret = lan9303_read(chip->regmap, LAN9303_PMI_ACCESS, ®); - if (ret) { - dev_err(chip->dev, - "Failed to read pmi access status: %d\n", ret); - return ret; - } - if (!(reg & LAN9303_PMI_ACCESS_MII_BUSY)) - return 0; - usleep_range(1000, 2000); - } - - return -EIO; + return lan9303_read_wait(chip, LAN9303_PMI_ACCESS, + LAN9303_PMI_ACCESS_MII_BUSY); } static int lan9303_indirect_phy_read(struct lan9303 *chip, int addr, int regnum) @@ -366,22 +374,8 @@ EXPORT_SYMBOL_GPL(lan9303_indirect_phy_ops); static int lan9303_switch_wait_for_completion(struct lan9303 *chip) { - int ret, i; - u32 reg; - - for (i = 0; i < 25; i++) { - ret = lan9303_read(chip->regmap, LAN9303_SWITCH_CSR_CMD, ®); - if (ret) { - dev_err(chip->dev, - "Failed to read csr command status: %d\n", ret); - return ret; - } - if (!(reg & LAN9303_SWITCH_CSR_CMD_BUSY)) - return 0; - usleep_range(1000, 2000); - } - - return -EIO; + return lan9303_read_wait(chip, LAN9303_SWITCH_CSR_CMD, + LAN9303_SWITCH_CSR_CMD_BUSY); } static int lan9303_write_switch_reg(struct lan9303 *chip, u16 regnum, u32 val) @@ -485,7 +479,8 @@ static int lan9303_detect_phy_setup(struct lan9303 *chip) { int reg; - /* depending on the 'phy_addr_sel_strap' setting, the three phys are + /* Calculate chip->phy_addr_base: + * Depending on the 'phy_addr_sel_strap' setting, the three phys are * using IDs 0-1-2 or IDs 1-2-3. We cannot read back the * 'phy_addr_sel_strap' setting directly, so we need a test, which * configuration is active: @@ -500,13 +495,10 @@ static int lan9303_detect_phy_setup(struct lan9303 *chip) return reg; } - if ((reg != 0) && (reg != 0xffff)) - chip->phy_addr_sel_strap = 1; - else - chip->phy_addr_sel_strap = 0; + chip->phy_addr_base = reg != 0 && reg != 0xffff; dev_dbg(chip->dev, "Phy setup '%s' detected\n", - chip->phy_addr_sel_strap ? "1-2-3" : "0-1-2"); + chip->phy_addr_base ? "1-2-3" : "0-1-2"); return 0; } @@ -546,20 +538,19 @@ lan9303_alr_cache_find_mac(struct lan9303 *chip, const u8 *mac_addr) return NULL; } -/* Wait a while until mask & reg == value. Otherwise return timeout. */ -static int lan9303_csr_reg_wait(struct lan9303 *chip, int regno, - int mask, char value) +static int lan9303_csr_reg_wait(struct lan9303 *chip, int regno, u32 mask) { int i; - for (i = 0; i < 0x1000; i++) { + for (i = 0; i < 25; i++) { u32 reg; lan9303_read_switch_reg(chip, regno, ®); - if ((reg & mask) == value) + if (!(reg & mask)) return 0; usleep_range(1000, 2000); } + return -ETIMEDOUT; } @@ -569,8 +560,7 @@ static int lan9303_alr_make_entry_raw(struct lan9303 *chip, u32 dat0, u32 dat1) lan9303_write_switch_reg(chip, LAN9303_SWE_ALR_WR_DAT_1, dat1); lan9303_write_switch_reg(chip, LAN9303_SWE_ALR_CMD, LAN9303_ALR_CMD_MAKE_ENTRY); - lan9303_csr_reg_wait(chip, LAN9303_SWE_ALR_CMD_STS, ALR_STS_MAKE_PEND, - 0); + lan9303_csr_reg_wait(chip, LAN9303_SWE_ALR_CMD_STS, ALR_STS_MAKE_PEND); lan9303_write_switch_reg(chip, LAN9303_SWE_ALR_CMD, 0); return 0; @@ -583,6 +573,7 @@ static void lan9303_alr_loop(struct lan9303 *chip, alr_loop_cb_t *cb, void *ctx) { int i; + mutex_lock(&chip->alr_mutex); lan9303_write_switch_reg(chip, LAN9303_SWE_ALR_CMD, LAN9303_ALR_CMD_GET_FIRST); lan9303_write_switch_reg(chip, LAN9303_SWE_ALR_CMD, 0); @@ -606,6 +597,7 @@ static void lan9303_alr_loop(struct lan9303 *chip, alr_loop_cb_t *cb, void *ctx) LAN9303_ALR_CMD_GET_NEXT); lan9303_write_switch_reg(chip, LAN9303_SWE_ALR_CMD, 0); } + mutex_unlock(&chip->alr_mutex); } static void alr_reg_to_mac(u32 dat0, u32 dat1, u8 mac[6]) @@ -694,16 +686,20 @@ static int lan9303_alr_add_port(struct lan9303 *chip, const u8 *mac, int port, { struct lan9303_alr_cache_entry *entr; + mutex_lock(&chip->alr_mutex); entr = lan9303_alr_cache_find_mac(chip, mac); if (!entr) { /*New entry */ entr = lan9303_alr_cache_find_free(chip); - if (!entr) + if (!entr) { + mutex_unlock(&chip->alr_mutex); return -ENOSPC; + } ether_addr_copy(entr->mac_addr, mac); } entr->port_map |= BIT(port); entr->stp_override = stp_override; lan9303_alr_set_entry(chip, mac, entr->port_map, stp_override); + mutex_unlock(&chip->alr_mutex); return 0; } @@ -713,15 +709,18 @@ static int lan9303_alr_del_port(struct lan9303 *chip, const u8 *mac, int port) { struct lan9303_alr_cache_entry *entr; + mutex_lock(&chip->alr_mutex); entr = lan9303_alr_cache_find_mac(chip, mac); if (!entr) - return 0; /* no static entry found */ + goto out; /* no static entry found */ entr->port_map &= ~BIT(port); if (entr->port_map == 0) /* zero means its free again */ eth_zero_addr(entr->mac_addr); lan9303_alr_set_entry(chip, mac, entr->port_map, entr->stp_override); +out: + mutex_unlock(&chip->alr_mutex); return 0; } @@ -818,18 +817,16 @@ static void lan9303_bridge_ports(struct lan9303 *chip) lan9303_alr_add_port(chip, eth_stp_addr, 0, true); } -static int lan9303_handle_reset(struct lan9303 *chip) +static void lan9303_handle_reset(struct lan9303 *chip) { if (!chip->reset_gpio) - return 0; + return; if (chip->reset_duration != 0) msleep(chip->reset_duration); /* release (deassert) reset and activate the device */ gpiod_set_value_cansleep(chip->reset_gpio, 0); - - return 0; } /* stop processing packets for all ports */ @@ -866,7 +863,7 @@ static int lan9303_check_device(struct lan9303 *chip) if ((reg >> 16) != LAN9303_CHIP_ID) { dev_err(chip->dev, "expecting LAN9303 chip, but found: %X\n", reg >> 16); - return ret; + return -ENODEV; } /* The default state of the LAN9303 device is to forward packets between @@ -1018,7 +1015,7 @@ static int lan9303_get_sset_count(struct dsa_switch *ds) static int lan9303_phy_read(struct dsa_switch *ds, int phy, int regnum) { struct lan9303 *chip = ds->priv; - int phy_base = chip->phy_addr_sel_strap; + int phy_base = chip->phy_addr_base; if (phy == phy_base) return lan9303_virt_phy_reg_read(chip, regnum); @@ -1032,7 +1029,7 @@ static int lan9303_phy_write(struct dsa_switch *ds, int phy, int regnum, u16 val) { struct lan9303 *chip = ds->priv; - int phy_base = chip->phy_addr_sel_strap; + int phy_base = chip->phy_addr_base; if (phy == phy_base) return lan9303_virt_phy_reg_write(chip, regnum, val); @@ -1069,7 +1066,7 @@ static void lan9303_adjust_link(struct dsa_switch *ds, int port, res = lan9303_phy_write(ds, port, MII_BMCR, ctl); - if (port == chip->phy_addr_sel_strap) { + if (port == chip->phy_addr_base) { /* Virtual Phy: Remove Turbo 200Mbit mode */ lan9303_read(chip->regmap, LAN9303_VIRT_SPECIAL_CTRL, &ctl); @@ -1093,8 +1090,7 @@ static void lan9303_port_disable(struct dsa_switch *ds, int port, struct lan9303 *chip = ds->priv; lan9303_disable_processing_port(chip, port); - lan9303_phy_write(ds, chip->phy_addr_sel_strap + port, - MII_BMCR, BMCR_PDOWN); + lan9303_phy_write(ds, chip->phy_addr_base + port, MII_BMCR, BMCR_PDOWN); } static int lan9303_port_bridge_join(struct dsa_switch *ds, int port, @@ -1217,8 +1213,7 @@ static int lan9303_port_fdb_dump(struct dsa_switch *ds, int port, } static int lan9303_port_mdb_prepare(struct dsa_switch *ds, int port, - const struct switchdev_obj_port_mdb *mdb, - struct switchdev_trans *trans) + const struct switchdev_obj_port_mdb *mdb) { struct lan9303 *chip = ds->priv; @@ -1235,8 +1230,7 @@ static int lan9303_port_mdb_prepare(struct dsa_switch *ds, int port, } static void lan9303_port_mdb_add(struct dsa_switch *ds, int port, - const struct switchdev_obj_port_mdb *mdb, - struct switchdev_trans *trans) + const struct switchdev_obj_port_mdb *mdb) { struct lan9303 *chip = ds->priv; @@ -1284,26 +1278,31 @@ static const struct dsa_switch_ops lan9303_switch_ops = { static int lan9303_register_switch(struct lan9303 *chip) { + int base; + chip->ds = dsa_switch_alloc(chip->dev, LAN9303_NUM_PORTS); if (!chip->ds) return -ENOMEM; chip->ds->priv = chip; chip->ds->ops = &lan9303_switch_ops; - chip->ds->phys_mii_mask = chip->phy_addr_sel_strap ? 0xe : 0x7; + base = chip->phy_addr_base; + chip->ds->phys_mii_mask = GENMASK(LAN9303_NUM_PORTS - 1 + base, base); return dsa_register_switch(chip->ds); } -static void lan9303_probe_reset_gpio(struct lan9303 *chip, +static int lan9303_probe_reset_gpio(struct lan9303 *chip, struct device_node *np) { chip->reset_gpio = devm_gpiod_get_optional(chip->dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(chip->reset_gpio)) + return PTR_ERR(chip->reset_gpio); - if (IS_ERR(chip->reset_gpio)) { + if (!chip->reset_gpio) { dev_dbg(chip->dev, "No reset GPIO defined\n"); - return; + return 0; } chip->reset_duration = 200; @@ -1318,6 +1317,8 @@ static void lan9303_probe_reset_gpio(struct lan9303 *chip, /* A sane reset duration should not be longer than 1s */ if (chip->reset_duration > 1000) chip->reset_duration = 1000; + + return 0; } int lan9303_probe(struct lan9303 *chip, struct device_node *np) @@ -1325,13 +1326,14 @@ int lan9303_probe(struct lan9303 *chip, struct device_node *np) int ret; mutex_init(&chip->indirect_mutex); + mutex_init(&chip->alr_mutex); - lan9303_probe_reset_gpio(chip, np); - - ret = lan9303_handle_reset(chip); + ret = lan9303_probe_reset_gpio(chip, np); if (ret) return ret; + lan9303_handle_reset(chip); + ret = lan9303_check_device(chip); if (ret) return ret; diff --git a/drivers/net/dsa/microchip/ksz_common.c b/drivers/net/dsa/microchip/ksz_common.c index b5be93a1e0df..663b0d5b982b 100644 --- a/drivers/net/dsa/microchip/ksz_common.c +++ b/drivers/net/dsa/microchip/ksz_common.c @@ -559,8 +559,7 @@ static int ksz_port_vlan_filtering(struct dsa_switch *ds, int port, bool flag) } static int ksz_port_vlan_prepare(struct dsa_switch *ds, int port, - const struct switchdev_obj_port_vlan *vlan, - struct switchdev_trans *trans) + const struct switchdev_obj_port_vlan *vlan) { /* nothing needed */ @@ -568,8 +567,7 @@ static int ksz_port_vlan_prepare(struct dsa_switch *ds, int port, } static void ksz_port_vlan_add(struct dsa_switch *ds, int port, - const struct switchdev_obj_port_vlan *vlan, - struct switchdev_trans *trans) + const struct switchdev_obj_port_vlan *vlan) { struct ksz_device *dev = ds->priv; u32 vlan_table[3]; @@ -858,16 +856,14 @@ exit: } static int ksz_port_mdb_prepare(struct dsa_switch *ds, int port, - const struct switchdev_obj_port_mdb *mdb, - struct switchdev_trans *trans) + const struct switchdev_obj_port_mdb *mdb) { /* nothing to do */ return 0; } static void ksz_port_mdb_add(struct dsa_switch *ds, int port, - const struct switchdev_obj_port_mdb *mdb, - struct switchdev_trans *trans) + const struct switchdev_obj_port_mdb *mdb) { struct ksz_device *dev = ds->priv; u32 static_table[4]; diff --git a/drivers/net/dsa/mt7530.c b/drivers/net/dsa/mt7530.c index 2820d69810b3..8a0bb000d056 100644 --- a/drivers/net/dsa/mt7530.c +++ b/drivers/net/dsa/mt7530.c @@ -805,6 +805,69 @@ mt7530_port_bridge_join(struct dsa_switch *ds, int port, } static void +mt7530_port_set_vlan_unaware(struct dsa_switch *ds, int port) +{ + struct mt7530_priv *priv = ds->priv; + bool all_user_ports_removed = true; + int i; + + /* When a port is removed from the bridge, the port would be set up + * back to the default as is at initial boot which is a VLAN-unaware + * port. + */ + mt7530_rmw(priv, MT7530_PCR_P(port), PCR_PORT_VLAN_MASK, + MT7530_PORT_MATRIX_MODE); + mt7530_rmw(priv, MT7530_PVC_P(port), VLAN_ATTR_MASK, + VLAN_ATTR(MT7530_VLAN_TRANSPARENT)); + + priv->ports[port].vlan_filtering = false; + + for (i = 0; i < MT7530_NUM_PORTS; i++) { + if (dsa_is_user_port(ds, i) && + priv->ports[i].vlan_filtering) { + all_user_ports_removed = false; + break; + } + } + + /* CPU port also does the same thing until all user ports belonging to + * the CPU port get out of VLAN filtering mode. + */ + if (all_user_ports_removed) { + mt7530_write(priv, MT7530_PCR_P(MT7530_CPU_PORT), + PCR_MATRIX(dsa_user_ports(priv->ds))); + mt7530_write(priv, MT7530_PVC_P(MT7530_CPU_PORT), + PORT_SPEC_TAG); + } +} + +static void +mt7530_port_set_vlan_aware(struct dsa_switch *ds, int port) +{ + struct mt7530_priv *priv = ds->priv; + + /* The real fabric path would be decided on the membership in the + * entry of VLAN table. PCR_MATRIX set up here with ALL_MEMBERS + * means potential VLAN can be consisting of certain subset of all + * ports. + */ + mt7530_rmw(priv, MT7530_PCR_P(port), + PCR_MATRIX_MASK, PCR_MATRIX(MT7530_ALL_MEMBERS)); + + /* Trapped into security mode allows packet forwarding through VLAN + * table lookup. + */ + mt7530_rmw(priv, MT7530_PCR_P(port), PCR_PORT_VLAN_MASK, + MT7530_PORT_SECURITY_MODE); + + /* Set the port as a user port which is to be able to recognize VID + * from incoming packets before fetching entry within the VLAN table. + */ + mt7530_rmw(priv, MT7530_PVC_P(port), VLAN_ATTR_MASK, + VLAN_ATTR(MT7530_VLAN_USER)); +} + +static void mt7530_port_bridge_leave(struct dsa_switch *ds, int port, struct net_device *bridge) { @@ -817,8 +880,11 @@ mt7530_port_bridge_leave(struct dsa_switch *ds, int port, /* Remove this port from the port matrix of the other ports * in the same bridge. If the port is disabled, port matrix * is kept and not being setup until the port becomes enabled. + * And the other port's port matrix cannot be broken when the + * other port is still a VLAN-aware port. */ - if (dsa_is_user_port(ds, i) && i != port) { + if (!priv->ports[i].vlan_filtering && + dsa_is_user_port(ds, i) && i != port) { if (dsa_to_port(ds, i)->bridge_dev != bridge) continue; if (priv->ports[i].enable) @@ -836,6 +902,8 @@ mt7530_port_bridge_leave(struct dsa_switch *ds, int port, PCR_MATRIX(BIT(MT7530_CPU_PORT))); priv->ports[port].pm = PCR_MATRIX(BIT(MT7530_CPU_PORT)); + mt7530_port_set_vlan_unaware(ds, port); + mutex_unlock(&priv->reg_mutex); } @@ -906,6 +974,220 @@ err: return 0; } +static int +mt7530_vlan_cmd(struct mt7530_priv *priv, enum mt7530_vlan_cmd cmd, u16 vid) +{ + struct mt7530_dummy_poll p; + u32 val; + int ret; + + val = VTCR_BUSY | VTCR_FUNC(cmd) | vid; + mt7530_write(priv, MT7530_VTCR, val); + + INIT_MT7530_DUMMY_POLL(&p, priv, MT7530_VTCR); + ret = readx_poll_timeout(_mt7530_read, &p, val, + !(val & VTCR_BUSY), 20, 20000); + if (ret < 0) { + dev_err(priv->dev, "poll timeout\n"); + return ret; + } + + val = mt7530_read(priv, MT7530_VTCR); + if (val & VTCR_INVALID) { + dev_err(priv->dev, "read VTCR invalid\n"); + return -EINVAL; + } + + return 0; +} + +static int +mt7530_port_vlan_filtering(struct dsa_switch *ds, int port, + bool vlan_filtering) +{ + struct mt7530_priv *priv = ds->priv; + + priv->ports[port].vlan_filtering = vlan_filtering; + + if (vlan_filtering) { + /* The port is being kept as VLAN-unaware port when bridge is + * set up with vlan_filtering not being set, Otherwise, the + * port and the corresponding CPU port is required the setup + * for becoming a VLAN-aware port. + */ + mt7530_port_set_vlan_aware(ds, port); + mt7530_port_set_vlan_aware(ds, MT7530_CPU_PORT); + } + + return 0; +} + +static int +mt7530_port_vlan_prepare(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan) +{ + /* nothing needed */ + + return 0; +} + +static void +mt7530_hw_vlan_add(struct mt7530_priv *priv, + struct mt7530_hw_vlan_entry *entry) +{ + u8 new_members; + u32 val; + + new_members = entry->old_members | BIT(entry->port) | + BIT(MT7530_CPU_PORT); + + /* Validate the entry with independent learning, create egress tag per + * VLAN and joining the port as one of the port members. + */ + val = IVL_MAC | VTAG_EN | PORT_MEM(new_members) | VLAN_VALID; + mt7530_write(priv, MT7530_VAWD1, val); + + /* Decide whether adding tag or not for those outgoing packets from the + * port inside the VLAN. + */ + val = entry->untagged ? MT7530_VLAN_EGRESS_UNTAG : + MT7530_VLAN_EGRESS_TAG; + mt7530_rmw(priv, MT7530_VAWD2, + ETAG_CTRL_P_MASK(entry->port), + ETAG_CTRL_P(entry->port, val)); + + /* CPU port is always taken as a tagged port for serving more than one + * VLANs across and also being applied with egress type stack mode for + * that VLAN tags would be appended after hardware special tag used as + * DSA tag. + */ + mt7530_rmw(priv, MT7530_VAWD2, + ETAG_CTRL_P_MASK(MT7530_CPU_PORT), + ETAG_CTRL_P(MT7530_CPU_PORT, + MT7530_VLAN_EGRESS_STACK)); +} + +static void +mt7530_hw_vlan_del(struct mt7530_priv *priv, + struct mt7530_hw_vlan_entry *entry) +{ + u8 new_members; + u32 val; + + new_members = entry->old_members & ~BIT(entry->port); + + val = mt7530_read(priv, MT7530_VAWD1); + if (!(val & VLAN_VALID)) { + dev_err(priv->dev, + "Cannot be deleted due to invalid entry\n"); + return; + } + + /* If certain member apart from CPU port is still alive in the VLAN, + * the entry would be kept valid. Otherwise, the entry is got to be + * disabled. + */ + if (new_members && new_members != BIT(MT7530_CPU_PORT)) { + val = IVL_MAC | VTAG_EN | PORT_MEM(new_members) | + VLAN_VALID; + mt7530_write(priv, MT7530_VAWD1, val); + } else { + mt7530_write(priv, MT7530_VAWD1, 0); + mt7530_write(priv, MT7530_VAWD2, 0); + } +} + +static void +mt7530_hw_vlan_update(struct mt7530_priv *priv, u16 vid, + struct mt7530_hw_vlan_entry *entry, + mt7530_vlan_op vlan_op) +{ + u32 val; + + /* Fetch entry */ + mt7530_vlan_cmd(priv, MT7530_VTCR_RD_VID, vid); + + val = mt7530_read(priv, MT7530_VAWD1); + + entry->old_members = (val >> PORT_MEM_SHFT) & PORT_MEM_MASK; + + /* Manipulate entry */ + vlan_op(priv, entry); + + /* Flush result to hardware */ + mt7530_vlan_cmd(priv, MT7530_VTCR_WR_VID, vid); +} + +static void +mt7530_port_vlan_add(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan) +{ + bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED; + bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID; + struct mt7530_hw_vlan_entry new_entry; + struct mt7530_priv *priv = ds->priv; + u16 vid; + + /* The port is kept as VLAN-unaware if bridge with vlan_filtering not + * being set. + */ + if (!priv->ports[port].vlan_filtering) + return; + + mutex_lock(&priv->reg_mutex); + + for (vid = vlan->vid_begin; vid <= vlan->vid_end; ++vid) { + mt7530_hw_vlan_entry_init(&new_entry, port, untagged); + mt7530_hw_vlan_update(priv, vid, &new_entry, + mt7530_hw_vlan_add); + } + + if (pvid) { + mt7530_rmw(priv, MT7530_PPBV1_P(port), G0_PORT_VID_MASK, + G0_PORT_VID(vlan->vid_end)); + priv->ports[port].pvid = vlan->vid_end; + } + + mutex_unlock(&priv->reg_mutex); +} + +static int +mt7530_port_vlan_del(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan) +{ + struct mt7530_hw_vlan_entry target_entry; + struct mt7530_priv *priv = ds->priv; + u16 vid, pvid; + + /* The port is kept as VLAN-unaware if bridge with vlan_filtering not + * being set. + */ + if (!priv->ports[port].vlan_filtering) + return 0; + + mutex_lock(&priv->reg_mutex); + + pvid = priv->ports[port].pvid; + for (vid = vlan->vid_begin; vid <= vlan->vid_end; ++vid) { + mt7530_hw_vlan_entry_init(&target_entry, port, 0); + mt7530_hw_vlan_update(priv, vid, &target_entry, + mt7530_hw_vlan_del); + + /* PVID is being restored to the default whenever the PVID port + * is being removed from the VLAN. + */ + if (pvid == vid) + pvid = G0_PORT_VID_DEF; + } + + mt7530_rmw(priv, MT7530_PPBV1_P(port), G0_PORT_VID_MASK, pvid); + priv->ports[port].pvid = pvid; + + mutex_unlock(&priv->reg_mutex); + + return 0; +} + static enum dsa_tag_protocol mtk_get_tag_protocol(struct dsa_switch *ds, int port) { @@ -1035,6 +1317,10 @@ static const struct dsa_switch_ops mt7530_switch_ops = { .port_fdb_add = mt7530_port_fdb_add, .port_fdb_del = mt7530_port_fdb_del, .port_fdb_dump = mt7530_port_fdb_dump, + .port_vlan_filtering = mt7530_port_vlan_filtering, + .port_vlan_prepare = mt7530_port_vlan_prepare, + .port_vlan_add = mt7530_port_vlan_add, + .port_vlan_del = mt7530_port_vlan_del, }; static int diff --git a/drivers/net/dsa/mt7530.h b/drivers/net/dsa/mt7530.h index 74db9822eb40..d9b407a22a58 100644 --- a/drivers/net/dsa/mt7530.h +++ b/drivers/net/dsa/mt7530.h @@ -17,6 +17,7 @@ #define MT7530_NUM_PORTS 7 #define MT7530_CPU_PORT 6 #define MT7530_NUM_FDB_RECORDS 2048 +#define MT7530_ALL_MEMBERS 0xff #define NUM_TRGMII_CTRL 5 @@ -88,21 +89,42 @@ enum mt7530_fdb_cmd { /* Register for vlan table control */ #define MT7530_VTCR 0x90 #define VTCR_BUSY BIT(31) -#define VTCR_FUNC (((x) & 0xf) << 12) -#define VTCR_FUNC_RD_VID 0x1 -#define VTCR_FUNC_WR_VID 0x2 -#define VTCR_FUNC_INV_VID 0x3 -#define VTCR_FUNC_VAL_VID 0x4 +#define VTCR_INVALID BIT(16) +#define VTCR_FUNC(x) (((x) & 0xf) << 12) #define VTCR_VID ((x) & 0xfff) +enum mt7530_vlan_cmd { + /* Read/Write the specified VID entry from VAWD register based + * on VID. + */ + MT7530_VTCR_RD_VID = 0, + MT7530_VTCR_WR_VID = 1, +}; + /* Register for setup vlan and acl write data */ #define MT7530_VAWD1 0x94 #define PORT_STAG BIT(31) +/* Independent VLAN Learning */ #define IVL_MAC BIT(30) +/* Per VLAN Egress Tag Control */ +#define VTAG_EN BIT(28) +/* VLAN Member Control */ #define PORT_MEM(x) (((x) & 0xff) << 16) -#define VALID BIT(1) +/* VLAN Entry Valid */ +#define VLAN_VALID BIT(0) +#define PORT_MEM_SHFT 16 +#define PORT_MEM_MASK 0xff #define MT7530_VAWD2 0x98 +/* Egress Tag Control */ +#define ETAG_CTRL_P(p, x) (((x) & 0x3) << ((p) << 1)) +#define ETAG_CTRL_P_MASK(p) ETAG_CTRL_P(p, 3) + +enum mt7530_vlan_egress_attr { + MT7530_VLAN_EGRESS_UNTAG = 0, + MT7530_VLAN_EGRESS_TAG = 2, + MT7530_VLAN_EGRESS_STACK = 3, +}; /* Register for port STP state control */ #define MT7530_SSP_P(x) (0x2000 + ((x) * 0x100)) @@ -120,11 +142,23 @@ enum mt7530_stp_state { /* Register for port control */ #define MT7530_PCR_P(x) (0x2004 + ((x) * 0x100)) #define PORT_VLAN(x) ((x) & 0x3) + +enum mt7530_port_mode { + /* Port Matrix Mode: Frames are forwarded by the PCR_MATRIX members. */ + MT7530_PORT_MATRIX_MODE = PORT_VLAN(0), + + /* Security Mode: Discard any frame due to ingress membership + * violation or VID missed on the VLAN table. + */ + MT7530_PORT_SECURITY_MODE = PORT_VLAN(3), +}; + #define PCR_MATRIX(x) (((x) & 0xff) << 16) #define PORT_PRI(x) (((x) & 0x7) << 24) #define EG_TAG(x) (((x) & 0x3) << 28) #define PCR_MATRIX_MASK PCR_MATRIX(0xff) #define PCR_MATRIX_CLR PCR_MATRIX(0) +#define PCR_PORT_VLAN_MASK PORT_VLAN(3) /* Register for port security control */ #define MT7530_PSC_P(x) (0x200c + ((x) * 0x100)) @@ -134,10 +168,20 @@ enum mt7530_stp_state { #define MT7530_PVC_P(x) (0x2010 + ((x) * 0x100)) #define PORT_SPEC_TAG BIT(5) #define VLAN_ATTR(x) (((x) & 0x3) << 6) +#define VLAN_ATTR_MASK VLAN_ATTR(3) + +enum mt7530_vlan_port_attr { + MT7530_VLAN_USER = 0, + MT7530_VLAN_TRANSPARENT = 3, +}; + #define STAG_VPID (((x) & 0xffff) << 16) /* Register for port port-and-protocol based vlan 1 control */ #define MT7530_PPBV1_P(x) (0x2014 + ((x) * 0x100)) +#define G0_PORT_VID(x) (((x) & 0xfff) << 0) +#define G0_PORT_VID_MASK G0_PORT_VID(0xfff) +#define G0_PORT_VID_DEF G0_PORT_VID(1) /* Register for port MAC control register */ #define MT7530_PMCR_P(x) (0x3000 + ((x) * 0x100)) @@ -345,9 +389,20 @@ struct mt7530_fdb { bool noarp; }; +/* struct mt7530_port - This is the main data structure for holding the state + * of the port. + * @enable: The status used for show port is enabled or not. + * @pm: The matrix used to show all connections with the port. + * @pvid: The VLAN specified is to be considered a PVID at ingress. Any + * untagged frames will be assigned to the related VLAN. + * @vlan_filtering: The flags indicating whether the port that can recognize + * VLAN-tagged frames. + */ struct mt7530_port { bool enable; u32 pm; + u16 pvid; + bool vlan_filtering; }; /* struct mt7530_priv - This is the main data structure for holding the state @@ -382,6 +437,22 @@ struct mt7530_priv { struct mutex reg_mutex; }; +struct mt7530_hw_vlan_entry { + int port; + u8 old_members; + bool untagged; +}; + +static inline void mt7530_hw_vlan_entry_init(struct mt7530_hw_vlan_entry *e, + int port, bool untagged) +{ + e->port = port; + e->untagged = untagged; +} + +typedef void (*mt7530_vlan_op)(struct mt7530_priv *, + struct mt7530_hw_vlan_entry *); + struct mt7530_hw_stats { const char *string; u16 reg; diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c index 66d33e97cbc5..eb328bade225 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.c +++ b/drivers/net/dsa/mv88e6xxx/chip.c @@ -1185,8 +1185,7 @@ static int mv88e6xxx_port_vlan_filtering(struct dsa_switch *ds, int port, static int mv88e6xxx_port_vlan_prepare(struct dsa_switch *ds, int port, - const struct switchdev_obj_port_vlan *vlan, - struct switchdev_trans *trans) + const struct switchdev_obj_port_vlan *vlan) { struct mv88e6xxx_chip *chip = ds->priv; int err; @@ -1295,8 +1294,7 @@ static int _mv88e6xxx_port_vlan_add(struct mv88e6xxx_chip *chip, int port, } static void mv88e6xxx_port_vlan_add(struct dsa_switch *ds, int port, - const struct switchdev_obj_port_vlan *vlan, - struct switchdev_trans *trans) + const struct switchdev_obj_port_vlan *vlan) { struct mv88e6xxx_chip *chip = ds->priv; bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED; @@ -1725,9 +1723,11 @@ static int mv88e6xxx_setup_message_port(struct mv88e6xxx_chip *chip, int port) static int mv88e6xxx_setup_egress_floods(struct mv88e6xxx_chip *chip, int port) { - bool flood = port == dsa_upstream_port(chip->ds); + struct dsa_switch *ds = chip->ds; + bool flood; /* Upstream ports flood frames with unknown unicast or multicast DA */ + flood = dsa_is_cpu_port(ds, port) || dsa_is_dsa_port(ds, port); if (chip->info->ops->port_set_egress_floods) return chip->info->ops->port_set_egress_floods(chip, port, flood, flood); @@ -1744,6 +1744,39 @@ static int mv88e6xxx_serdes_power(struct mv88e6xxx_chip *chip, int port, return 0; } +static int mv88e6xxx_setup_upstream_port(struct mv88e6xxx_chip *chip, int port) +{ + struct dsa_switch *ds = chip->ds; + int upstream_port; + int err; + + upstream_port = dsa_upstream_port(ds, port); + if (chip->info->ops->port_set_upstream_port) { + err = chip->info->ops->port_set_upstream_port(chip, port, + upstream_port); + if (err) + return err; + } + + if (port == upstream_port) { + if (chip->info->ops->set_cpu_port) { + err = chip->info->ops->set_cpu_port(chip, + upstream_port); + if (err) + return err; + } + + if (chip->info->ops->set_egress_port) { + err = chip->info->ops->set_egress_port(chip, + upstream_port); + if (err) + return err; + } + } + + return 0; +} + static int mv88e6xxx_setup_port(struct mv88e6xxx_chip *chip, int port) { struct dsa_switch *ds = chip->ds; @@ -1814,13 +1847,9 @@ static int mv88e6xxx_setup_port(struct mv88e6xxx_chip *chip, int port) if (err) return err; - reg = 0; - if (chip->info->ops->port_set_upstream_port) { - err = chip->info->ops->port_set_upstream_port( - chip, port, dsa_upstream_port(ds)); - if (err) - return err; - } + err = mv88e6xxx_setup_upstream_port(chip, port); + if (err) + return err; err = mv88e6xxx_port_set_8021q_mode(chip, port, MV88E6XXX_PORT_CTL2_8021Q_MODE_DISABLED); @@ -1946,21 +1975,8 @@ static int mv88e6xxx_set_ageing_time(struct dsa_switch *ds, static int mv88e6xxx_g1_setup(struct mv88e6xxx_chip *chip) { struct dsa_switch *ds = chip->ds; - u32 upstream_port = dsa_upstream_port(ds); int err; - if (chip->info->ops->set_cpu_port) { - err = chip->info->ops->set_cpu_port(chip, upstream_port); - if (err) - return err; - } - - if (chip->info->ops->set_egress_port) { - err = chip->info->ops->set_egress_port(chip, upstream_port); - if (err) - return err; - } - /* Disable remote management, and set the switch's DSA device number. */ err = mv88e6xxx_g1_write(chip, MV88E6XXX_G1_CTL2, MV88E6XXX_G1_CTL2_MULTIPLE_CASCADE | @@ -3741,6 +3757,7 @@ static enum dsa_tag_protocol mv88e6xxx_get_tag_protocol(struct dsa_switch *ds, return chip->info->tag_protocol; } +#if IS_ENABLED(CONFIG_NET_DSA_LEGACY) static const char *mv88e6xxx_drv_probe(struct device *dsa_dev, struct device *host_dev, int sw_addr, void **priv) @@ -3788,10 +3805,10 @@ free: return NULL; } +#endif static int mv88e6xxx_port_mdb_prepare(struct dsa_switch *ds, int port, - const struct switchdev_obj_port_mdb *mdb, - struct switchdev_trans *trans) + const struct switchdev_obj_port_mdb *mdb) { /* We don't need any dynamic resource from the kernel (yet), * so skip the prepare phase. @@ -3801,8 +3818,7 @@ static int mv88e6xxx_port_mdb_prepare(struct dsa_switch *ds, int port, } static void mv88e6xxx_port_mdb_add(struct dsa_switch *ds, int port, - const struct switchdev_obj_port_mdb *mdb, - struct switchdev_trans *trans) + const struct switchdev_obj_port_mdb *mdb) { struct mv88e6xxx_chip *chip = ds->priv; @@ -3829,7 +3845,9 @@ static int mv88e6xxx_port_mdb_del(struct dsa_switch *ds, int port, } static const struct dsa_switch_ops mv88e6xxx_switch_ops = { +#if IS_ENABLED(CONFIG_NET_DSA_LEGACY) .probe = mv88e6xxx_drv_probe, +#endif .get_tag_protocol = mv88e6xxx_get_tag_protocol, .setup = mv88e6xxx_setup, .adjust_link = mv88e6xxx_adjust_link, @@ -3958,11 +3976,19 @@ static int mv88e6xxx_probe(struct mdio_device *mdiodev) if (err) goto out_g1_irq; } + + err = mv88e6xxx_g1_atu_prob_irq_setup(chip); + if (err) + goto out_g2_irq; + + err = mv88e6xxx_g1_vtu_prob_irq_setup(chip); + if (err) + goto out_g1_atu_prob_irq; } err = mv88e6xxx_mdios_register(chip, np); if (err) - goto out_g2_irq; + goto out_g1_vtu_prob_irq; err = mv88e6xxx_register_switch(chip); if (err) @@ -3972,6 +3998,12 @@ static int mv88e6xxx_probe(struct mdio_device *mdiodev) out_mdio: mv88e6xxx_mdios_unregister(chip); +out_g1_vtu_prob_irq: + if (chip->irq > 0) + mv88e6xxx_g1_vtu_prob_irq_free(chip); +out_g1_atu_prob_irq: + if (chip->irq > 0) + mv88e6xxx_g1_atu_prob_irq_free(chip); out_g2_irq: if (chip->info->g2_irqs > 0 && chip->irq > 0) mv88e6xxx_g2_irq_free(chip); @@ -3995,6 +4027,8 @@ static void mv88e6xxx_remove(struct mdio_device *mdiodev) mv88e6xxx_mdios_unregister(chip); if (chip->irq > 0) { + mv88e6xxx_g1_vtu_prob_irq_free(chip); + mv88e6xxx_g1_atu_prob_irq_free(chip); if (chip->info->g2_irqs > 0) mv88e6xxx_g2_irq_free(chip); mutex_lock(&chip->reg_lock); diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h index 334f6f7544ba..3dba6e90adcf 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.h +++ b/drivers/net/dsa/mv88e6xxx/chip.h @@ -207,6 +207,8 @@ struct mv88e6xxx_chip { int irq; int device_irq; int watchdog_irq; + int atu_prob_irq; + int vtu_prob_irq; }; struct mv88e6xxx_bus_ops { diff --git a/drivers/net/dsa/mv88e6xxx/global1.h b/drivers/net/dsa/mv88e6xxx/global1.h index b0dc7518b47f..6aee7316fea6 100644 --- a/drivers/net/dsa/mv88e6xxx/global1.h +++ b/drivers/net/dsa/mv88e6xxx/global1.h @@ -29,9 +29,9 @@ #define MV88E6XXX_G1_STS_IRQ_AVB 8 #define MV88E6XXX_G1_STS_IRQ_DEVICE 7 #define MV88E6XXX_G1_STS_IRQ_STATS 6 -#define MV88E6XXX_G1_STS_IRQ_VTU_PROBLEM 5 +#define MV88E6XXX_G1_STS_IRQ_VTU_PROB 5 #define MV88E6XXX_G1_STS_IRQ_VTU_DONE 4 -#define MV88E6XXX_G1_STS_IRQ_ATU_PROBLEM 3 +#define MV88E6XXX_G1_STS_IRQ_ATU_PROB 3 #define MV88E6XXX_G1_STS_IRQ_ATU_DONE 2 #define MV88E6XXX_G1_STS_IRQ_TCAM_DONE 1 #define MV88E6XXX_G1_STS_IRQ_EEPROM_DONE 0 @@ -82,6 +82,10 @@ #define MV88E6XXX_G1_VTU_OP_VTU_GET_NEXT 0x4000 #define MV88E6XXX_G1_VTU_OP_STU_LOAD_PURGE 0x5000 #define MV88E6XXX_G1_VTU_OP_STU_GET_NEXT 0x6000 +#define MV88E6XXX_G1_VTU_OP_GET_CLR_VIOLATION 0x7000 +#define MV88E6XXX_G1_VTU_OP_MEMBER_VIOLATION BIT(6) +#define MV88E6XXX_G1_VTU_OP_MISS_VIOLATION BIT(5) +#define MV88E6XXX_G1_VTU_OP_SPID_MASK 0xf /* Offset 0x06: VTU VID Register */ #define MV88E6XXX_G1_VTU_VID 0x06 @@ -122,6 +126,10 @@ #define MV88E6XXX_G1_ATU_OP_FLUSH_MOVE_ALL_DB 0x5000 #define MV88E6XXX_G1_ATU_OP_FLUSH_MOVE_NON_STATIC_DB 0x6000 #define MV88E6XXX_G1_ATU_OP_GET_CLR_VIOLATION 0x7000 +#define MV88E6XXX_G1_ATU_OP_AGE_OUT_VIOLATION BIT(7) +#define MV88E6XXX_G1_ATU_OP_MEMBER_VIOLATION BIT(6) +#define MV88E6XXX_G1_ATU_OP_MISS_VIOLTATION BIT(5) +#define MV88E6XXX_G1_ATU_OP_FULL_VIOLATION BIT(4) /* Offset 0x0C: ATU Data Register */ #define MV88E6XXX_G1_ATU_DATA 0x0c @@ -255,6 +263,8 @@ int mv88e6xxx_g1_atu_loadpurge(struct mv88e6xxx_chip *chip, u16 fid, int mv88e6xxx_g1_atu_flush(struct mv88e6xxx_chip *chip, u16 fid, bool all); int mv88e6xxx_g1_atu_remove(struct mv88e6xxx_chip *chip, u16 fid, int port, bool all); +int mv88e6xxx_g1_atu_prob_irq_setup(struct mv88e6xxx_chip *chip); +void mv88e6xxx_g1_atu_prob_irq_free(struct mv88e6xxx_chip *chip); int mv88e6185_g1_vtu_getnext(struct mv88e6xxx_chip *chip, struct mv88e6xxx_vtu_entry *entry); @@ -269,5 +279,7 @@ int mv88e6390_g1_vtu_getnext(struct mv88e6xxx_chip *chip, int mv88e6390_g1_vtu_loadpurge(struct mv88e6xxx_chip *chip, struct mv88e6xxx_vtu_entry *entry); int mv88e6xxx_g1_vtu_flush(struct mv88e6xxx_chip *chip); +int mv88e6xxx_g1_vtu_prob_irq_setup(struct mv88e6xxx_chip *chip); +void mv88e6xxx_g1_vtu_prob_irq_free(struct mv88e6xxx_chip *chip); #endif /* _MV88E6XXX_GLOBAL1_H */ diff --git a/drivers/net/dsa/mv88e6xxx/global1_atu.c b/drivers/net/dsa/mv88e6xxx/global1_atu.c index efeef4b01442..20d941f4273b 100644 --- a/drivers/net/dsa/mv88e6xxx/global1_atu.c +++ b/drivers/net/dsa/mv88e6xxx/global1_atu.c @@ -9,6 +9,8 @@ * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ +#include <linux/interrupt.h> +#include <linux/irqdomain.h> #include "chip.h" #include "global1.h" @@ -307,3 +309,88 @@ int mv88e6xxx_g1_atu_remove(struct mv88e6xxx_chip *chip, u16 fid, int port, return mv88e6xxx_g1_atu_move(chip, fid, from_port, to_port, all); } + +static irqreturn_t mv88e6xxx_g1_atu_prob_irq_thread_fn(int irq, void *dev_id) +{ + struct mv88e6xxx_chip *chip = dev_id; + struct mv88e6xxx_atu_entry entry; + int err; + u16 val; + + mutex_lock(&chip->reg_lock); + + err = mv88e6xxx_g1_atu_op(chip, 0, + MV88E6XXX_G1_ATU_OP_GET_CLR_VIOLATION); + if (err) + goto out; + + err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_ATU_OP, &val); + if (err) + goto out; + + err = mv88e6xxx_g1_atu_data_read(chip, &entry); + if (err) + goto out; + + err = mv88e6xxx_g1_atu_mac_read(chip, &entry); + if (err) + goto out; + + mutex_unlock(&chip->reg_lock); + + if (val & MV88E6XXX_G1_ATU_OP_AGE_OUT_VIOLATION) { + dev_err_ratelimited(chip->dev, + "ATU age out violation for %pM\n", + entry.mac); + } + + if (val & MV88E6XXX_G1_ATU_OP_MEMBER_VIOLATION) { + dev_err_ratelimited(chip->dev, + "ATU member violation for %pM portvec %x\n", + entry.mac, entry.portvec); + } + + if (val & MV88E6XXX_G1_ATU_OP_MEMBER_VIOLATION) + dev_err_ratelimited(chip->dev, + "ATU miss violation for %pM portvec %x\n", + entry.mac, entry.portvec); + + if (val & MV88E6XXX_G1_ATU_OP_FULL_VIOLATION) + dev_err_ratelimited(chip->dev, + "ATU full violation for %pM portvec %x\n", + entry.mac, entry.portvec); + + return IRQ_HANDLED; + +out: + mutex_unlock(&chip->reg_lock); + + dev_err(chip->dev, "ATU problem: error %d while handling interrupt\n", + err); + return IRQ_HANDLED; +} + +int mv88e6xxx_g1_atu_prob_irq_setup(struct mv88e6xxx_chip *chip) +{ + int err; + + chip->atu_prob_irq = irq_find_mapping(chip->g1_irq.domain, + MV88E6XXX_G1_STS_IRQ_ATU_PROB); + if (chip->atu_prob_irq < 0) + return chip->atu_prob_irq; + + err = request_threaded_irq(chip->atu_prob_irq, NULL, + mv88e6xxx_g1_atu_prob_irq_thread_fn, + IRQF_ONESHOT, "mv88e6xxx-g1-atu-prob", + chip); + if (err) + irq_dispose_mapping(chip->atu_prob_irq); + + return err; +} + +void mv88e6xxx_g1_atu_prob_irq_free(struct mv88e6xxx_chip *chip) +{ + free_irq(chip->atu_prob_irq, chip); + irq_dispose_mapping(chip->atu_prob_irq); +} diff --git a/drivers/net/dsa/mv88e6xxx/global1_vtu.c b/drivers/net/dsa/mv88e6xxx/global1_vtu.c index 8c8a0ec3d6e9..7997961647de 100644 --- a/drivers/net/dsa/mv88e6xxx/global1_vtu.c +++ b/drivers/net/dsa/mv88e6xxx/global1_vtu.c @@ -11,6 +11,9 @@ * (at your option) any later version. */ +#include <linux/interrupt.h> +#include <linux/irqdomain.h> + #include "chip.h" #include "global1.h" @@ -513,3 +516,74 @@ int mv88e6xxx_g1_vtu_flush(struct mv88e6xxx_chip *chip) return mv88e6xxx_g1_vtu_op(chip, MV88E6XXX_G1_VTU_OP_FLUSH_ALL); } + +static irqreturn_t mv88e6xxx_g1_vtu_prob_irq_thread_fn(int irq, void *dev_id) +{ + struct mv88e6xxx_chip *chip = dev_id; + struct mv88e6xxx_vtu_entry entry; + int spid; + int err; + u16 val; + + mutex_lock(&chip->reg_lock); + + err = mv88e6xxx_g1_vtu_op(chip, MV88E6XXX_G1_VTU_OP_GET_CLR_VIOLATION); + if (err) + goto out; + + err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_OP, &val); + if (err) + goto out; + + err = mv88e6xxx_g1_vtu_vid_read(chip, &entry); + if (err) + goto out; + + mutex_unlock(&chip->reg_lock); + + spid = val & MV88E6XXX_G1_VTU_OP_SPID_MASK; + + if (val & MV88E6XXX_G1_VTU_OP_MEMBER_VIOLATION) { + dev_err_ratelimited(chip->dev, "VTU member violation for vid %d, source port %d\n", + entry.vid, spid); + } + + if (val & MV88E6XXX_G1_VTU_OP_MISS_VIOLATION) + dev_err_ratelimited(chip->dev, "VTU miss violation for vid %d, source port %d\n", + entry.vid, spid); + + return IRQ_HANDLED; + +out: + mutex_unlock(&chip->reg_lock); + + dev_err(chip->dev, "VTU problem: error %d while handling interrupt\n", + err); + + return IRQ_HANDLED; +} + +int mv88e6xxx_g1_vtu_prob_irq_setup(struct mv88e6xxx_chip *chip) +{ + int err; + + chip->vtu_prob_irq = irq_find_mapping(chip->g1_irq.domain, + MV88E6XXX_G1_STS_IRQ_VTU_PROB); + if (chip->vtu_prob_irq < 0) + return chip->vtu_prob_irq; + + err = request_threaded_irq(chip->vtu_prob_irq, NULL, + mv88e6xxx_g1_vtu_prob_irq_thread_fn, + IRQF_ONESHOT, "mv88e6xxx-g1-vtu-prob", + chip); + if (err) + irq_dispose_mapping(chip->vtu_prob_irq); + + return err; +} + +void mv88e6xxx_g1_vtu_prob_irq_free(struct mv88e6xxx_chip *chip) +{ + free_irq(chip->vtu_prob_irq, chip); + irq_dispose_mapping(chip->vtu_prob_irq); +} |