| // SPDX-License-Identifier: GPL-2.0-or-later | |
| /* | |
| * Driver for Motorcomm YT921x Switch | |
| * | |
| * Should work on YT9213/YT9214/YT9215/YT9218, but only tested on YT9215+SGMII, | |
| * be sure to do your own checks before porting to another chip. | |
| * | |
| * Copyright (c) 2025 David Yang | |
| */ | |
| #include <linux/dcbnl.h> | |
| #include <linux/etherdevice.h> | |
| #include <linux/if_bridge.h> | |
| #include <linux/if_hsr.h> | |
| #include <linux/if_vlan.h> | |
| #include <linux/iopoll.h> | |
| #include <linux/mdio.h> | |
| #include <linux/module.h> | |
| #include <linux/of.h> | |
| #include <linux/of_mdio.h> | |
| #include <linux/of_net.h> | |
| #include <linux/sort.h> | |
| #include <net/dsa.h> | |
| #include <net/dscp.h> | |
| #include <net/ieee8021q.h> | |
| #include <net/pkt_cls.h> | |
| #include "yt921x.h" | |
| struct yt921x_mib_desc { | |
| unsigned int size; | |
| unsigned int offset; | |
| const char *name; | |
| }; | |
| #define MIB_DESC(_size, _offset, _name) \ | |
| {_size, _offset, _name} | |
| /* Must agree with yt921x_mib | |
| * | |
| * Unstructured fields (name != NULL) will appear in get_ethtool_stats(), | |
| * structured go to their *_stats() methods, but we need their sizes and offsets | |
| * to perform 32bit MIB overflow wraparound. | |
| */ | |
| static const struct yt921x_mib_desc yt921x_mib_descs[] = { | |
| MIB_DESC(1, YT921X_MIB_DATA_RX_BROADCAST, NULL), | |
| MIB_DESC(1, YT921X_MIB_DATA_RX_PAUSE, NULL), | |
| MIB_DESC(1, YT921X_MIB_DATA_RX_MULTICAST, NULL), | |
| MIB_DESC(1, YT921X_MIB_DATA_RX_CRC_ERR, NULL), | |
| MIB_DESC(1, YT921X_MIB_DATA_RX_ALIGN_ERR, NULL), | |
| MIB_DESC(1, YT921X_MIB_DATA_RX_UNDERSIZE_ERR, NULL), | |
| MIB_DESC(1, YT921X_MIB_DATA_RX_FRAG_ERR, NULL), | |
| MIB_DESC(1, YT921X_MIB_DATA_RX_PKT_SZ_64, NULL), | |
| MIB_DESC(1, YT921X_MIB_DATA_RX_PKT_SZ_65_TO_127, NULL), | |
| MIB_DESC(1, YT921X_MIB_DATA_RX_PKT_SZ_128_TO_255, NULL), | |
| MIB_DESC(1, YT921X_MIB_DATA_RX_PKT_SZ_256_TO_511, NULL), | |
| MIB_DESC(1, YT921X_MIB_DATA_RX_PKT_SZ_512_TO_1023, NULL), | |
| MIB_DESC(1, YT921X_MIB_DATA_RX_PKT_SZ_1024_TO_1518, NULL), | |
| MIB_DESC(1, YT921X_MIB_DATA_RX_PKT_SZ_1519_TO_MAX, NULL), | |
| MIB_DESC(2, YT921X_MIB_DATA_RX_GOOD_BYTES, NULL), | |
| MIB_DESC(2, YT921X_MIB_DATA_RX_BAD_BYTES, "RxBadBytes"), | |
| MIB_DESC(1, YT921X_MIB_DATA_RX_OVERSIZE_ERR, NULL), | |
| MIB_DESC(1, YT921X_MIB_DATA_RX_DROPPED, NULL), | |
| MIB_DESC(1, YT921X_MIB_DATA_TX_BROADCAST, NULL), | |
| MIB_DESC(1, YT921X_MIB_DATA_TX_PAUSE, NULL), | |
| MIB_DESC(1, YT921X_MIB_DATA_TX_MULTICAST, NULL), | |
| MIB_DESC(1, YT921X_MIB_DATA_TX_UNDERSIZE_ERR, NULL), | |
| MIB_DESC(1, YT921X_MIB_DATA_TX_PKT_SZ_64, NULL), | |
| MIB_DESC(1, YT921X_MIB_DATA_TX_PKT_SZ_65_TO_127, NULL), | |
| MIB_DESC(1, YT921X_MIB_DATA_TX_PKT_SZ_128_TO_255, NULL), | |
| MIB_DESC(1, YT921X_MIB_DATA_TX_PKT_SZ_256_TO_511, NULL), | |
| MIB_DESC(1, YT921X_MIB_DATA_TX_PKT_SZ_512_TO_1023, NULL), | |
| MIB_DESC(1, YT921X_MIB_DATA_TX_PKT_SZ_1024_TO_1518, NULL), | |
| MIB_DESC(1, YT921X_MIB_DATA_TX_PKT_SZ_1519_TO_MAX, NULL), | |
| MIB_DESC(2, YT921X_MIB_DATA_TX_GOOD_BYTES, NULL), | |
| MIB_DESC(1, YT921X_MIB_DATA_TX_COLLISION, NULL), | |
| MIB_DESC(1, YT921X_MIB_DATA_TX_EXCESSIVE_COLLISION, NULL), | |
| MIB_DESC(1, YT921X_MIB_DATA_TX_MULTIPLE_COLLISION, NULL), | |
| MIB_DESC(1, YT921X_MIB_DATA_TX_SINGLE_COLLISION, NULL), | |
| MIB_DESC(1, YT921X_MIB_DATA_TX_PKT, NULL), | |
| MIB_DESC(1, YT921X_MIB_DATA_TX_DEFERRED, NULL), | |
| MIB_DESC(1, YT921X_MIB_DATA_TX_LATE_COLLISION, NULL), | |
| MIB_DESC(1, YT921X_MIB_DATA_RX_OAM, "RxOAM"), | |
| MIB_DESC(1, YT921X_MIB_DATA_TX_OAM, "TxOAM"), | |
| }; | |
| struct yt921x_info { | |
| const char *name; | |
| u16 major; | |
| /* Unknown, seems to be plain enumeration */ | |
| u8 mode; | |
| u8 extmode; | |
| /* Ports with integral GbE PHYs, not including MCU Port 10 */ | |
| u16 internal_mask; | |
| /* TODO: see comments in yt921x_dsa_phylink_get_caps() */ | |
| u16 external_mask; | |
| }; | |
| #define YT921X_PORT_MASK_INTn(port) BIT(port) | |
| #define YT921X_PORT_MASK_INT0_n(n) GENMASK((n) - 1, 0) | |
| #define YT921X_PORT_MASK_EXT0 BIT(8) | |
| #define YT921X_PORT_MASK_EXT1 BIT(9) | |
| static const struct yt921x_info yt921x_infos[] = { | |
| { | |
| "YT9215SC", YT9215_MAJOR, 1, 0, | |
| YT921X_PORT_MASK_INT0_n(5), | |
| YT921X_PORT_MASK_EXT0 | YT921X_PORT_MASK_EXT1, | |
| }, | |
| { | |
| "YT9215S", YT9215_MAJOR, 2, 0, | |
| YT921X_PORT_MASK_INT0_n(5), | |
| YT921X_PORT_MASK_EXT0 | YT921X_PORT_MASK_EXT1, | |
| }, | |
| { | |
| "YT9215RB", YT9215_MAJOR, 3, 0, | |
| YT921X_PORT_MASK_INT0_n(5), | |
| YT921X_PORT_MASK_EXT0 | YT921X_PORT_MASK_EXT1, | |
| }, | |
| { | |
| "YT9214NB", YT9215_MAJOR, 3, 2, | |
| YT921X_PORT_MASK_INTn(1) | YT921X_PORT_MASK_INTn(3), | |
| YT921X_PORT_MASK_EXT0 | YT921X_PORT_MASK_EXT1, | |
| }, | |
| { | |
| "YT9213NB", YT9215_MAJOR, 3, 3, | |
| YT921X_PORT_MASK_INTn(1) | YT921X_PORT_MASK_INTn(3), | |
| YT921X_PORT_MASK_EXT1, | |
| }, | |
| { | |
| "YT9218N", YT9218_MAJOR, 0, 0, | |
| YT921X_PORT_MASK_INT0_n(8), | |
| 0, | |
| }, | |
| { | |
| "YT9218MB", YT9218_MAJOR, 1, 0, | |
| YT921X_PORT_MASK_INT0_n(8), | |
| YT921X_PORT_MASK_EXT0 | YT921X_PORT_MASK_EXT1, | |
| }, | |
| {} | |
| }; | |
| #define YT921X_NAME "yt921x" | |
| #define YT921X_VID_UNWARE 4095 | |
| #define YT921X_POLL_SLEEP_US 10000 | |
| #define YT921X_POLL_TIMEOUT_US 100000 | |
| /* The interval should be small enough to avoid overflow of 32bit MIBs. | |
| * | |
| * Until we can read MIBs from stats64 call directly (i.e. sleep | |
| * there), we have to poll stats more frequently then it is actually needed. | |
| * For overflow protection, normally, 100 sec interval should have been OK. | |
| */ | |
| #define YT921X_STATS_INTERVAL_JIFFIES (3 * HZ) | |
| struct yt921x_reg_mdio { | |
| struct mii_bus *bus; | |
| int addr; | |
| /* SWITCH_ID_1 / SWITCH_ID_0 of the device | |
| * | |
| * This is a way to multiplex multiple devices on the same MII phyaddr | |
| * and should be configurable in DT. However, MDIO core simply doesn't | |
| * allow multiple devices over one reg addr, so this is a fixed value | |
| * for now until a solution is found. | |
| * | |
| * Keep this because we need switchid to form MII regaddrs anyway. | |
| */ | |
| unsigned char switchid; | |
| }; | |
| /* TODO: SPI/I2C */ | |
| #define to_yt921x_priv(_ds) container_of_const(_ds, struct yt921x_priv, ds) | |
| #define to_device(priv) ((priv)->ds.dev) | |
| static u32 ethaddr_hi4_to_u32(const unsigned char *addr) | |
| { | |
| return (addr[0] << 24) | (addr[1] << 16) | (addr[2] << 8) | addr[3]; | |
| } | |
| static u32 ethaddr_lo2_to_u32(const unsigned char *addr) | |
| { | |
| return (addr[4] << 8) | addr[5]; | |
| } | |
| static int yt921x_reg_read(struct yt921x_priv *priv, u32 reg, u32 *valp) | |
| { | |
| WARN_ON(!mutex_is_locked(&priv->reg_lock)); | |
| return priv->reg_ops->read(priv->reg_ctx, reg, valp); | |
| } | |
| static int yt921x_reg_write(struct yt921x_priv *priv, u32 reg, u32 val) | |
| { | |
| WARN_ON(!mutex_is_locked(&priv->reg_lock)); | |
| return priv->reg_ops->write(priv->reg_ctx, reg, val); | |
| } | |
| static int | |
| yt921x_reg_wait(struct yt921x_priv *priv, u32 reg, u32 mask, u32 *valp) | |
| { | |
| u32 val; | |
| int res; | |
| int ret; | |
| ret = read_poll_timeout(yt921x_reg_read, res, | |
| res || (val & mask) == *valp, | |
| YT921X_POLL_SLEEP_US, YT921X_POLL_TIMEOUT_US, | |
| false, priv, reg, &val); | |
| if (ret) | |
| return ret; | |
| if (res) | |
| return res; | |
| *valp = val; | |
| return 0; | |
| } | |
| static int | |
| yt921x_reg_update_bits(struct yt921x_priv *priv, u32 reg, u32 mask, u32 val) | |
| { | |
| int res; | |
| u32 v; | |
| u32 u; | |
| res = yt921x_reg_read(priv, reg, &v); | |
| if (res) | |
| return res; | |
| u = v; | |
| u &= ~mask; | |
| u |= val; | |
| if (u == v) | |
| return 0; | |
| return yt921x_reg_write(priv, reg, u); | |
| } | |
| static int yt921x_reg_set_bits(struct yt921x_priv *priv, u32 reg, u32 mask) | |
| { | |
| return yt921x_reg_update_bits(priv, reg, 0, mask); | |
| } | |
| static int yt921x_reg_clear_bits(struct yt921x_priv *priv, u32 reg, u32 mask) | |
| { | |
| return yt921x_reg_update_bits(priv, reg, mask, 0); | |
| } | |
| static int | |
| yt921x_reg_toggle_bits(struct yt921x_priv *priv, u32 reg, u32 mask, bool set) | |
| { | |
| return yt921x_reg_update_bits(priv, reg, mask, !set ? 0 : mask); | |
| } | |
| /* Some multi-word registers, like VLANn_CTRL, should be treated as a single | |
| * long register. More specifically, writes to parts of its words won't become | |
| * visible, until the last word is written. | |
| * | |
| * Here we require full read and write operations over these registers to | |
| * eliminate potential issues, although partial reads/writes are also possible. | |
| */ | |
| static void update_ctrls_unaligned(u32 *lo, u32 *hi, u64 mask, u64 val) | |
| { | |
| *lo &= ~lower_32_bits(mask); | |
| *hi &= ~upper_32_bits(mask); | |
| *lo |= lower_32_bits(val); | |
| *hi |= upper_32_bits(val); | |
| } | |
| static int | |
| yt921x_regs_read(struct yt921x_priv *priv, u32 reg, u32 *vals, | |
| unsigned int num_regs) | |
| { | |
| int res; | |
| for (unsigned int i = 0; i < num_regs; i++) { | |
| res = yt921x_reg_read(priv, reg + 4 * i, &vals[i]); | |
| if (res) | |
| return res; | |
| } | |
| return 0; | |
| } | |
| static int | |
| yt921x_regs_write(struct yt921x_priv *priv, u32 reg, const u32 *vals, | |
| unsigned int num_regs) | |
| { | |
| int res; | |
| for (unsigned int i = 0; i < num_regs; i++) { | |
| res = yt921x_reg_write(priv, reg + 4 * i, vals[i]); | |
| if (res) | |
| return res; | |
| } | |
| return 0; | |
| } | |
| static int | |
| yt921x_regs_update_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks, | |
| const u32 *vals, unsigned int num_regs) | |
| { | |
| bool changed = false; | |
| u32 vs[4]; | |
| int res; | |
| BUILD_BUG_ON(num_regs > ARRAY_SIZE(vs)); | |
| res = yt921x_regs_read(priv, reg, vs, num_regs); | |
| if (res) | |
| return res; | |
| for (unsigned int i = 0; i < num_regs; i++) { | |
| u32 u = vs[i]; | |
| u &= ~masks[i]; | |
| u |= vals[i]; | |
| if (u != vs[i]) | |
| changed = true; | |
| vs[i] = u; | |
| } | |
| if (!changed) | |
| return 0; | |
| return yt921x_regs_write(priv, reg, vs, num_regs); | |
| } | |
| static int | |
| yt921x_regs_clear_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks, | |
| unsigned int num_regs) | |
| { | |
| bool changed = false; | |
| u32 vs[4]; | |
| int res; | |
| BUILD_BUG_ON(num_regs > ARRAY_SIZE(vs)); | |
| res = yt921x_regs_read(priv, reg, vs, num_regs); | |
| if (res) | |
| return res; | |
| for (unsigned int i = 0; i < num_regs; i++) { | |
| u32 u = vs[i]; | |
| u &= ~masks[i]; | |
| if (u != vs[i]) | |
| changed = true; | |
| vs[i] = u; | |
| } | |
| if (!changed) | |
| return 0; | |
| return yt921x_regs_write(priv, reg, vs, num_regs); | |
| } | |
| static int | |
| yt921x_reg64_write(struct yt921x_priv *priv, u32 reg, const u32 *vals) | |
| { | |
| return yt921x_regs_write(priv, reg, vals, 2); | |
| } | |
| static int | |
| yt921x_reg64_update_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks, | |
| const u32 *vals) | |
| { | |
| return yt921x_regs_update_bits(priv, reg, masks, vals, 2); | |
| } | |
| static int | |
| yt921x_reg64_clear_bits(struct yt921x_priv *priv, u32 reg, const u32 *masks) | |
| { | |
| return yt921x_regs_clear_bits(priv, reg, masks, 2); | |
| } | |
| static int | |
| yt921x_reg96_write(struct yt921x_priv *priv, u32 reg, const u32 *vals) | |
| { | |
| return yt921x_regs_write(priv, reg, vals, 3); | |
| } | |
| static int yt921x_reg_mdio_read(void *context, u32 reg, u32 *valp) | |
| { | |
| struct yt921x_reg_mdio *mdio = context; | |
| struct mii_bus *bus = mdio->bus; | |
| int addr = mdio->addr; | |
| u32 reg_addr; | |
| u32 reg_data; | |
| u32 val; | |
| int res; | |
| /* Hold the mdio bus lock to avoid (un)locking for 4 times */ | |
| mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED); | |
| reg_addr = YT921X_SMI_SWITCHID(mdio->switchid) | YT921X_SMI_ADDR | | |
| YT921X_SMI_READ; | |
| res = __mdiobus_write(bus, addr, reg_addr, (u16)(reg >> 16)); | |
| if (res) | |
| goto end; | |
| res = __mdiobus_write(bus, addr, reg_addr, (u16)reg); | |
| if (res) | |
| goto end; | |
| reg_data = YT921X_SMI_SWITCHID(mdio->switchid) | YT921X_SMI_DATA | | |
| YT921X_SMI_READ; | |
| res = __mdiobus_read(bus, addr, reg_data); | |
| if (res < 0) | |
| goto end; | |
| val = (u16)res; | |
| res = __mdiobus_read(bus, addr, reg_data); | |
| if (res < 0) | |
| goto end; | |
| val = (val << 16) | (u16)res; | |
| *valp = val; | |
| res = 0; | |
| end: | |
| mutex_unlock(&bus->mdio_lock); | |
| return res; | |
| } | |
| static int yt921x_reg_mdio_write(void *context, u32 reg, u32 val) | |
| { | |
| struct yt921x_reg_mdio *mdio = context; | |
| struct mii_bus *bus = mdio->bus; | |
| int addr = mdio->addr; | |
| u32 reg_addr; | |
| u32 reg_data; | |
| int res; | |
| mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED); | |
| reg_addr = YT921X_SMI_SWITCHID(mdio->switchid) | YT921X_SMI_ADDR | | |
| YT921X_SMI_WRITE; | |
| res = __mdiobus_write(bus, addr, reg_addr, (u16)(reg >> 16)); | |
| if (res) | |
| goto end; | |
| res = __mdiobus_write(bus, addr, reg_addr, (u16)reg); | |
| if (res) | |
| goto end; | |
| reg_data = YT921X_SMI_SWITCHID(mdio->switchid) | YT921X_SMI_DATA | | |
| YT921X_SMI_WRITE; | |
| res = __mdiobus_write(bus, addr, reg_data, (u16)(val >> 16)); | |
| if (res) | |
| goto end; | |
| res = __mdiobus_write(bus, addr, reg_data, (u16)val); | |
| if (res) | |
| goto end; | |
| res = 0; | |
| end: | |
| mutex_unlock(&bus->mdio_lock); | |
| return res; | |
| } | |
| static const struct yt921x_reg_ops yt921x_reg_ops_mdio = { | |
| .read = yt921x_reg_mdio_read, | |
| .write = yt921x_reg_mdio_write, | |
| }; | |
| /* TODO: SPI/I2C */ | |
| static int yt921x_intif_wait(struct yt921x_priv *priv) | |
| { | |
| u32 val = 0; | |
| return yt921x_reg_wait(priv, YT921X_INT_MBUS_OP, YT921X_MBUS_OP_START, | |
| &val); | |
| } | |
| static int | |
| yt921x_intif_read(struct yt921x_priv *priv, int port, int reg, u16 *valp) | |
| { | |
| struct device *dev = to_device(priv); | |
| u32 mask; | |
| u32 ctrl; | |
| u32 val; | |
| int res; | |
| res = yt921x_intif_wait(priv); | |
| if (res) | |
| return res; | |
| mask = YT921X_MBUS_CTRL_PORT_M | YT921X_MBUS_CTRL_REG_M | | |
| YT921X_MBUS_CTRL_OP_M; | |
| ctrl = YT921X_MBUS_CTRL_PORT(port) | YT921X_MBUS_CTRL_REG(reg) | | |
| YT921X_MBUS_CTRL_READ; | |
| res = yt921x_reg_update_bits(priv, YT921X_INT_MBUS_CTRL, mask, ctrl); | |
| if (res) | |
| return res; | |
| res = yt921x_reg_write(priv, YT921X_INT_MBUS_OP, YT921X_MBUS_OP_START); | |
| if (res) | |
| return res; | |
| res = yt921x_intif_wait(priv); | |
| if (res) | |
| return res; | |
| res = yt921x_reg_read(priv, YT921X_INT_MBUS_DIN, &val); | |
| if (res) | |
| return res; | |
| if ((u16)val != val) | |
| dev_info(dev, | |
| "%s: port %d, reg 0x%x: Expected u16, got 0x%08x\n", | |
| __func__, port, reg, val); | |
| *valp = (u16)val; | |
| return 0; | |
| } | |
| static int | |
| yt921x_intif_write(struct yt921x_priv *priv, int port, int reg, u16 val) | |
| { | |
| u32 mask; | |
| u32 ctrl; | |
| int res; | |
| res = yt921x_intif_wait(priv); | |
| if (res) | |
| return res; | |
| mask = YT921X_MBUS_CTRL_PORT_M | YT921X_MBUS_CTRL_REG_M | | |
| YT921X_MBUS_CTRL_OP_M; | |
| ctrl = YT921X_MBUS_CTRL_PORT(port) | YT921X_MBUS_CTRL_REG(reg) | | |
| YT921X_MBUS_CTRL_WRITE; | |
| res = yt921x_reg_update_bits(priv, YT921X_INT_MBUS_CTRL, mask, ctrl); | |
| if (res) | |
| return res; | |
| res = yt921x_reg_write(priv, YT921X_INT_MBUS_DOUT, val); | |
| if (res) | |
| return res; | |
| res = yt921x_reg_write(priv, YT921X_INT_MBUS_OP, YT921X_MBUS_OP_START); | |
| if (res) | |
| return res; | |
| return yt921x_intif_wait(priv); | |
| } | |
| static int yt921x_mbus_int_read(struct mii_bus *mbus, int port, int reg) | |
| { | |
| struct yt921x_priv *priv = mbus->priv; | |
| u16 val; | |
| int res; | |
| if (port >= YT921X_PORT_NUM) | |
| return U16_MAX; | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_intif_read(priv, port, reg, &val); | |
| mutex_unlock(&priv->reg_lock); | |
| if (res) | |
| return res; | |
| return val; | |
| } | |
| static int | |
| yt921x_mbus_int_write(struct mii_bus *mbus, int port, int reg, u16 data) | |
| { | |
| struct yt921x_priv *priv = mbus->priv; | |
| int res; | |
| if (port >= YT921X_PORT_NUM) | |
| return -ENODEV; | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_intif_write(priv, port, reg, data); | |
| mutex_unlock(&priv->reg_lock); | |
| return res; | |
| } | |
| static int | |
| yt921x_mbus_int_init(struct yt921x_priv *priv, struct device_node *mnp) | |
| { | |
| struct device *dev = to_device(priv); | |
| struct mii_bus *mbus; | |
| int res; | |
| mbus = devm_mdiobus_alloc(dev); | |
| if (!mbus) | |
| return -ENOMEM; | |
| mbus->name = "YT921x internal MDIO bus"; | |
| snprintf(mbus->id, MII_BUS_ID_SIZE, "%s", dev_name(dev)); | |
| mbus->priv = priv; | |
| mbus->read = yt921x_mbus_int_read; | |
| mbus->write = yt921x_mbus_int_write; | |
| mbus->parent = dev; | |
| mbus->phy_mask = (u32)~GENMASK(YT921X_PORT_NUM - 1, 0); | |
| res = devm_of_mdiobus_register(dev, mbus, mnp); | |
| if (res) | |
| return res; | |
| priv->mbus_int = mbus; | |
| return 0; | |
| } | |
| static int yt921x_extif_wait(struct yt921x_priv *priv) | |
| { | |
| u32 val = 0; | |
| return yt921x_reg_wait(priv, YT921X_EXT_MBUS_OP, YT921X_MBUS_OP_START, | |
| &val); | |
| } | |
| static int | |
| yt921x_extif_read(struct yt921x_priv *priv, int port, int reg, u16 *valp) | |
| { | |
| struct device *dev = to_device(priv); | |
| u32 mask; | |
| u32 ctrl; | |
| u32 val; | |
| int res; | |
| res = yt921x_extif_wait(priv); | |
| if (res) | |
| return res; | |
| mask = YT921X_MBUS_CTRL_PORT_M | YT921X_MBUS_CTRL_REG_M | | |
| YT921X_MBUS_CTRL_TYPE_M | YT921X_MBUS_CTRL_OP_M; | |
| ctrl = YT921X_MBUS_CTRL_PORT(port) | YT921X_MBUS_CTRL_REG(reg) | | |
| YT921X_MBUS_CTRL_TYPE_C22 | YT921X_MBUS_CTRL_READ; | |
| res = yt921x_reg_update_bits(priv, YT921X_EXT_MBUS_CTRL, mask, ctrl); | |
| if (res) | |
| return res; | |
| res = yt921x_reg_write(priv, YT921X_EXT_MBUS_OP, YT921X_MBUS_OP_START); | |
| if (res) | |
| return res; | |
| res = yt921x_extif_wait(priv); | |
| if (res) | |
| return res; | |
| res = yt921x_reg_read(priv, YT921X_EXT_MBUS_DIN, &val); | |
| if (res) | |
| return res; | |
| if ((u16)val != val) | |
| dev_info(dev, | |
| "%s: port %d, reg 0x%x: Expected u16, got 0x%08x\n", | |
| __func__, port, reg, val); | |
| *valp = (u16)val; | |
| return 0; | |
| } | |
| static int | |
| yt921x_extif_write(struct yt921x_priv *priv, int port, int reg, u16 val) | |
| { | |
| u32 mask; | |
| u32 ctrl; | |
| int res; | |
| res = yt921x_extif_wait(priv); | |
| if (res) | |
| return res; | |
| mask = YT921X_MBUS_CTRL_PORT_M | YT921X_MBUS_CTRL_REG_M | | |
| YT921X_MBUS_CTRL_TYPE_M | YT921X_MBUS_CTRL_OP_M; | |
| ctrl = YT921X_MBUS_CTRL_PORT(port) | YT921X_MBUS_CTRL_REG(reg) | | |
| YT921X_MBUS_CTRL_TYPE_C22 | YT921X_MBUS_CTRL_WRITE; | |
| res = yt921x_reg_update_bits(priv, YT921X_EXT_MBUS_CTRL, mask, ctrl); | |
| if (res) | |
| return res; | |
| res = yt921x_reg_write(priv, YT921X_EXT_MBUS_DOUT, val); | |
| if (res) | |
| return res; | |
| res = yt921x_reg_write(priv, YT921X_EXT_MBUS_OP, YT921X_MBUS_OP_START); | |
| if (res) | |
| return res; | |
| return yt921x_extif_wait(priv); | |
| } | |
| static int yt921x_mbus_ext_read(struct mii_bus *mbus, int port, int reg) | |
| { | |
| struct yt921x_priv *priv = mbus->priv; | |
| u16 val; | |
| int res; | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_extif_read(priv, port, reg, &val); | |
| mutex_unlock(&priv->reg_lock); | |
| if (res) | |
| return res; | |
| return val; | |
| } | |
| static int | |
| yt921x_mbus_ext_write(struct mii_bus *mbus, int port, int reg, u16 data) | |
| { | |
| struct yt921x_priv *priv = mbus->priv; | |
| int res; | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_extif_write(priv, port, reg, data); | |
| mutex_unlock(&priv->reg_lock); | |
| return res; | |
| } | |
| static int | |
| yt921x_mbus_ext_init(struct yt921x_priv *priv, struct device_node *mnp) | |
| { | |
| struct device *dev = to_device(priv); | |
| struct mii_bus *mbus; | |
| int res; | |
| mbus = devm_mdiobus_alloc(dev); | |
| if (!mbus) | |
| return -ENOMEM; | |
| mbus->name = "YT921x external MDIO bus"; | |
| snprintf(mbus->id, MII_BUS_ID_SIZE, "%s@ext", dev_name(dev)); | |
| mbus->priv = priv; | |
| /* TODO: c45? */ | |
| mbus->read = yt921x_mbus_ext_read; | |
| mbus->write = yt921x_mbus_ext_write; | |
| mbus->parent = dev; | |
| res = devm_of_mdiobus_register(dev, mbus, mnp); | |
| if (res) | |
| return res; | |
| priv->mbus_ext = mbus; | |
| return 0; | |
| } | |
| /* Read and handle overflow of 32bit MIBs. MIB buffer must be zeroed before. */ | |
| static int yt921x_read_mib(struct yt921x_priv *priv, int port) | |
| { | |
| struct yt921x_port *pp = &priv->ports[port]; | |
| struct device *dev = to_device(priv); | |
| struct yt921x_mib *mib = &pp->mib; | |
| int res = 0; | |
| /* Reading of yt921x_port::mib is not protected by a lock and it's vain | |
| * to keep its consistency, since we have to read registers one by one | |
| * and there is no way to make a snapshot of MIB stats. | |
| * | |
| * Writing (by this function only) is and should be protected by | |
| * reg_lock. | |
| */ | |
| for (size_t i = 0; i < ARRAY_SIZE(yt921x_mib_descs); i++) { | |
| const struct yt921x_mib_desc *desc = &yt921x_mib_descs[i]; | |
| u32 reg = YT921X_MIBn_DATA0(port) + desc->offset; | |
| u64 *valp = &((u64 *)mib)[i]; | |
| u32 val0; | |
| u64 val; | |
| res = yt921x_reg_read(priv, reg, &val0); | |
| if (res) | |
| break; | |
| if (desc->size <= 1) { | |
| u64 old_val = *valp; | |
| val = (old_val & ~(u64)U32_MAX) | val0; | |
| if (val < old_val) | |
| val += 1ull << 32; | |
| } else { | |
| u32 val1; | |
| res = yt921x_reg_read(priv, reg + 4, &val1); | |
| if (res) | |
| break; | |
| val = ((u64)val1 << 32) | val0; | |
| } | |
| WRITE_ONCE(*valp, val); | |
| } | |
| pp->rx_frames = mib->rx_64byte + mib->rx_65_127byte + | |
| mib->rx_128_255byte + mib->rx_256_511byte + | |
| mib->rx_512_1023byte + mib->rx_1024_1518byte + | |
| mib->rx_jumbo; | |
| pp->tx_frames = mib->tx_64byte + mib->tx_65_127byte + | |
| mib->tx_128_255byte + mib->tx_256_511byte + | |
| mib->tx_512_1023byte + mib->tx_1024_1518byte + | |
| mib->tx_jumbo; | |
| if (res) | |
| dev_err(dev, "Failed to %s port %d: %i\n", "read stats for", | |
| port, res); | |
| return res; | |
| } | |
| static void yt921x_poll_mib(struct work_struct *work) | |
| { | |
| struct yt921x_port *pp = container_of_const(work, struct yt921x_port, | |
| mib_read.work); | |
| struct yt921x_priv *priv = (void *)(pp - pp->index) - | |
| offsetof(struct yt921x_priv, ports); | |
| unsigned long delay = YT921X_STATS_INTERVAL_JIFFIES; | |
| int port = pp->index; | |
| int res; | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_read_mib(priv, port); | |
| mutex_unlock(&priv->reg_lock); | |
| if (res) | |
| delay *= 4; | |
| schedule_delayed_work(&pp->mib_read, delay); | |
| } | |
| static void | |
| yt921x_dsa_get_strings(struct dsa_switch *ds, int port, u32 stringset, | |
| uint8_t *data) | |
| { | |
| if (stringset != ETH_SS_STATS) | |
| return; | |
| for (size_t i = 0; i < ARRAY_SIZE(yt921x_mib_descs); i++) { | |
| const struct yt921x_mib_desc *desc = &yt921x_mib_descs[i]; | |
| if (desc->name) | |
| ethtool_puts(&data, desc->name); | |
| } | |
| } | |
| static void | |
| yt921x_dsa_get_ethtool_stats(struct dsa_switch *ds, int port, uint64_t *data) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| struct yt921x_port *pp = &priv->ports[port]; | |
| struct yt921x_mib *mib = &pp->mib; | |
| size_t j; | |
| mutex_lock(&priv->reg_lock); | |
| yt921x_read_mib(priv, port); | |
| mutex_unlock(&priv->reg_lock); | |
| j = 0; | |
| for (size_t i = 0; i < ARRAY_SIZE(yt921x_mib_descs); i++) { | |
| const struct yt921x_mib_desc *desc = &yt921x_mib_descs[i]; | |
| if (!desc->name) | |
| continue; | |
| data[j] = ((u64 *)mib)[i]; | |
| j++; | |
| } | |
| } | |
| static int yt921x_dsa_get_sset_count(struct dsa_switch *ds, int port, int sset) | |
| { | |
| int cnt = 0; | |
| if (sset != ETH_SS_STATS) | |
| return 0; | |
| for (size_t i = 0; i < ARRAY_SIZE(yt921x_mib_descs); i++) { | |
| const struct yt921x_mib_desc *desc = &yt921x_mib_descs[i]; | |
| if (desc->name) | |
| cnt++; | |
| } | |
| return cnt; | |
| } | |
| static void | |
| yt921x_dsa_get_eth_mac_stats(struct dsa_switch *ds, int port, | |
| struct ethtool_eth_mac_stats *mac_stats) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| struct yt921x_port *pp = &priv->ports[port]; | |
| struct yt921x_mib *mib = &pp->mib; | |
| mutex_lock(&priv->reg_lock); | |
| yt921x_read_mib(priv, port); | |
| mutex_unlock(&priv->reg_lock); | |
| mac_stats->FramesTransmittedOK = pp->tx_frames; | |
| mac_stats->SingleCollisionFrames = mib->tx_single_collisions; | |
| mac_stats->MultipleCollisionFrames = mib->tx_multiple_collisions; | |
| mac_stats->FramesReceivedOK = pp->rx_frames; | |
| mac_stats->FrameCheckSequenceErrors = mib->rx_crc_errors; | |
| mac_stats->AlignmentErrors = mib->rx_alignment_errors; | |
| mac_stats->OctetsTransmittedOK = mib->tx_good_bytes; | |
| mac_stats->FramesWithDeferredXmissions = mib->tx_deferred; | |
| mac_stats->LateCollisions = mib->tx_late_collisions; | |
| mac_stats->FramesAbortedDueToXSColls = mib->tx_aborted_errors; | |
| /* mac_stats->FramesLostDueToIntMACXmitError */ | |
| /* mac_stats->CarrierSenseErrors */ | |
| mac_stats->OctetsReceivedOK = mib->rx_good_bytes; | |
| /* mac_stats->FramesLostDueToIntMACRcvError */ | |
| mac_stats->MulticastFramesXmittedOK = mib->tx_multicast; | |
| mac_stats->BroadcastFramesXmittedOK = mib->tx_broadcast; | |
| /* mac_stats->FramesWithExcessiveDeferral */ | |
| mac_stats->MulticastFramesReceivedOK = mib->rx_multicast; | |
| mac_stats->BroadcastFramesReceivedOK = mib->rx_broadcast; | |
| /* mac_stats->InRangeLengthErrors */ | |
| /* mac_stats->OutOfRangeLengthField */ | |
| mac_stats->FrameTooLongErrors = mib->rx_oversize_errors; | |
| } | |
| static void | |
| yt921x_dsa_get_eth_ctrl_stats(struct dsa_switch *ds, int port, | |
| struct ethtool_eth_ctrl_stats *ctrl_stats) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| struct yt921x_port *pp = &priv->ports[port]; | |
| struct yt921x_mib *mib = &pp->mib; | |
| mutex_lock(&priv->reg_lock); | |
| yt921x_read_mib(priv, port); | |
| mutex_unlock(&priv->reg_lock); | |
| ctrl_stats->MACControlFramesTransmitted = mib->tx_pause; | |
| ctrl_stats->MACControlFramesReceived = mib->rx_pause; | |
| /* ctrl_stats->UnsupportedOpcodesReceived */ | |
| } | |
| static const struct ethtool_rmon_hist_range yt921x_rmon_ranges[] = { | |
| { 0, 64 }, | |
| { 65, 127 }, | |
| { 128, 255 }, | |
| { 256, 511 }, | |
| { 512, 1023 }, | |
| { 1024, 1518 }, | |
| { 1519, YT921X_FRAME_SIZE_MAX }, | |
| {} | |
| }; | |
| static void | |
| yt921x_dsa_get_rmon_stats(struct dsa_switch *ds, int port, | |
| struct ethtool_rmon_stats *rmon_stats, | |
| const struct ethtool_rmon_hist_range **ranges) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| struct yt921x_port *pp = &priv->ports[port]; | |
| struct yt921x_mib *mib = &pp->mib; | |
| mutex_lock(&priv->reg_lock); | |
| yt921x_read_mib(priv, port); | |
| mutex_unlock(&priv->reg_lock); | |
| *ranges = yt921x_rmon_ranges; | |
| rmon_stats->undersize_pkts = mib->rx_undersize_errors; | |
| rmon_stats->oversize_pkts = mib->rx_oversize_errors; | |
| rmon_stats->fragments = mib->rx_alignment_errors; | |
| /* rmon_stats->jabbers */ | |
| rmon_stats->hist[0] = mib->rx_64byte; | |
| rmon_stats->hist[1] = mib->rx_65_127byte; | |
| rmon_stats->hist[2] = mib->rx_128_255byte; | |
| rmon_stats->hist[3] = mib->rx_256_511byte; | |
| rmon_stats->hist[4] = mib->rx_512_1023byte; | |
| rmon_stats->hist[5] = mib->rx_1024_1518byte; | |
| rmon_stats->hist[6] = mib->rx_jumbo; | |
| rmon_stats->hist_tx[0] = mib->tx_64byte; | |
| rmon_stats->hist_tx[1] = mib->tx_65_127byte; | |
| rmon_stats->hist_tx[2] = mib->tx_128_255byte; | |
| rmon_stats->hist_tx[3] = mib->tx_256_511byte; | |
| rmon_stats->hist_tx[4] = mib->tx_512_1023byte; | |
| rmon_stats->hist_tx[5] = mib->tx_1024_1518byte; | |
| rmon_stats->hist_tx[6] = mib->tx_jumbo; | |
| } | |
| static void | |
| yt921x_dsa_get_stats64(struct dsa_switch *ds, int port, | |
| struct rtnl_link_stats64 *stats) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| struct yt921x_port *pp = &priv->ports[port]; | |
| struct yt921x_mib *mib = &pp->mib; | |
| stats->rx_length_errors = mib->rx_undersize_errors + | |
| mib->rx_fragment_errors; | |
| stats->rx_over_errors = mib->rx_oversize_errors; | |
| stats->rx_crc_errors = mib->rx_crc_errors; | |
| stats->rx_frame_errors = mib->rx_alignment_errors; | |
| /* stats->rx_fifo_errors */ | |
| /* stats->rx_missed_errors */ | |
| stats->tx_aborted_errors = mib->tx_aborted_errors; | |
| /* stats->tx_carrier_errors */ | |
| stats->tx_fifo_errors = mib->tx_undersize_errors; | |
| /* stats->tx_heartbeat_errors */ | |
| stats->tx_window_errors = mib->tx_late_collisions; | |
| stats->rx_packets = pp->rx_frames; | |
| stats->tx_packets = pp->tx_frames; | |
| stats->rx_bytes = mib->rx_good_bytes - ETH_FCS_LEN * stats->rx_packets; | |
| stats->tx_bytes = mib->tx_good_bytes - ETH_FCS_LEN * stats->tx_packets; | |
| stats->rx_errors = stats->rx_length_errors + stats->rx_over_errors + | |
| stats->rx_crc_errors + stats->rx_frame_errors; | |
| stats->tx_errors = stats->tx_aborted_errors + stats->tx_fifo_errors + | |
| stats->tx_window_errors; | |
| stats->rx_dropped = mib->rx_dropped; | |
| /* stats->tx_dropped */ | |
| stats->multicast = mib->rx_multicast; | |
| stats->collisions = mib->tx_collisions; | |
| } | |
| static void | |
| yt921x_dsa_get_pause_stats(struct dsa_switch *ds, int port, | |
| struct ethtool_pause_stats *pause_stats) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| struct yt921x_port *pp = &priv->ports[port]; | |
| struct yt921x_mib *mib = &pp->mib; | |
| mutex_lock(&priv->reg_lock); | |
| yt921x_read_mib(priv, port); | |
| mutex_unlock(&priv->reg_lock); | |
| pause_stats->tx_pause_frames = mib->tx_pause; | |
| pause_stats->rx_pause_frames = mib->rx_pause; | |
| } | |
| static int | |
| yt921x_set_eee(struct yt921x_priv *priv, int port, struct ethtool_keee *e) | |
| { | |
| /* Poor datasheet for EEE operations; don't ask if you are confused */ | |
| bool enable = e->eee_enabled; | |
| u16 new_mask; | |
| int res; | |
| /* Enable / disable global EEE */ | |
| new_mask = priv->eee_ports_mask; | |
| new_mask &= ~BIT(port); | |
| new_mask |= !enable ? 0 : BIT(port); | |
| if (!!new_mask != !!priv->eee_ports_mask) { | |
| res = yt921x_reg_toggle_bits(priv, YT921X_PON_STRAP_FUNC, | |
| YT921X_PON_STRAP_EEE, !!new_mask); | |
| if (res) | |
| return res; | |
| res = yt921x_reg_toggle_bits(priv, YT921X_PON_STRAP_VAL, | |
| YT921X_PON_STRAP_EEE, !!new_mask); | |
| if (res) | |
| return res; | |
| } | |
| priv->eee_ports_mask = new_mask; | |
| /* Enable / disable port EEE */ | |
| res = yt921x_reg_toggle_bits(priv, YT921X_EEE_CTRL, | |
| YT921X_EEE_CTRL_ENn(port), enable); | |
| if (res) | |
| return res; | |
| res = yt921x_reg_toggle_bits(priv, YT921X_EEEn_VAL(port), | |
| YT921X_EEE_VAL_DATA, enable); | |
| if (res) | |
| return res; | |
| return 0; | |
| } | |
| static int | |
| yt921x_dsa_set_mac_eee(struct dsa_switch *ds, int port, struct ethtool_keee *e) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| int res; | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_set_eee(priv, port, e); | |
| mutex_unlock(&priv->reg_lock); | |
| return res; | |
| } | |
| static int yt921x_mtu_fetch(struct yt921x_priv *priv, int port) | |
| { | |
| struct dsa_port *dp = dsa_to_port(&priv->ds, port); | |
| return dp->user ? READ_ONCE(dp->user->mtu) : ETH_DATA_LEN; | |
| } | |
| static int | |
| yt921x_dsa_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu) | |
| { | |
| /* Only serves as packet filter, since the frame size is always set to | |
| * maximum after reset | |
| */ | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| struct dsa_port *dp = dsa_to_port(ds, port); | |
| int frame_size; | |
| int res; | |
| frame_size = new_mtu + ETH_HLEN + ETH_FCS_LEN; | |
| if (dsa_port_is_cpu(dp)) | |
| frame_size += YT921X_TAG_LEN; | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_reg_update_bits(priv, YT921X_MACn_FRAME(port), | |
| YT921X_MAC_FRAME_SIZE_M, | |
| YT921X_MAC_FRAME_SIZE(frame_size)); | |
| mutex_unlock(&priv->reg_lock); | |
| return res; | |
| } | |
| static int yt921x_dsa_port_max_mtu(struct dsa_switch *ds, int port) | |
| { | |
| /* Only called for user ports, exclude tag len here */ | |
| return YT921X_FRAME_SIZE_MAX - ETH_HLEN - ETH_FCS_LEN - YT921X_TAG_LEN; | |
| } | |
| /* v * 2^e */ | |
| static u64 ldexpu64(u64 v, int e) | |
| { | |
| return e >= 0 ? v << e : v >> -e; | |
| } | |
| /* slot (ns) * rate (/s) / 10^9 (ns/s) = 2^C * token * 4^unit */ | |
| static u32 rate2token(u64 rate, unsigned int slot_ns, int unit, int C) | |
| { | |
| int e = 2 * unit + C + YT921X_TOKEN_RATE_C; | |
| return div_u64(ldexpu64(slot_ns * rate, -e), 1000000000); | |
| } | |
| static u64 token2rate(u32 token, unsigned int slot_ns, int unit, int C) | |
| { | |
| int e = 2 * unit + C + YT921X_TOKEN_RATE_C; | |
| return div_u64(ldexpu64(mul_u32_u32(1000000000, token), e), slot_ns); | |
| } | |
| /* burst = 2^C * token * 4^unit */ | |
| static u32 burst2token(u64 burst, int unit, int C) | |
| { | |
| return ldexpu64(burst, -(2 * unit + C)); | |
| } | |
| static u64 token2burst(u32 token, int unit, int C) | |
| { | |
| return ldexpu64(token, 2 * unit + C); | |
| } | |
| struct yt921x_marker { | |
| u32 cir; | |
| u32 cbs; | |
| u32 ebs; | |
| int unit; | |
| bool pkt_mode; | |
| }; | |
| #define YT921X_MARKER_PKT_MODE BIT(0) | |
| #define YT921X_MARKER_SINGLE_BUCKET BIT(1) | |
| static int | |
| yt921x_marker_tfm(struct yt921x_marker *marker, u64 rate, u64 burst, | |
| unsigned int flags, unsigned int slot_ns, u32 cir_max, | |
| u32 cbs_max, int unit_max, struct yt921x_priv *priv, int port, | |
| struct netlink_ext_ack *extack) | |
| { | |
| const int C = flags & YT921X_MARKER_PKT_MODE ? YT921X_TOKEN_PKT_C : | |
| YT921X_TOKEN_BYTE_C; | |
| struct device *dev = to_device(priv); | |
| struct yt921x_marker m; | |
| u64 burst_est; | |
| u64 burst_sug; | |
| u64 burst_max; | |
| u64 rate_max; | |
| m.unit = unit_max; | |
| rate_max = token2rate(cir_max, slot_ns, m.unit, C); | |
| burst_max = token2burst(cbs_max, m.unit, C); | |
| /* Check for unusual values */ | |
| if (rate > rate_max || burst > burst_max) { | |
| NL_SET_ERR_MSG_MOD(extack, "Unexpected tremendous rate"); | |
| return -ERANGE; | |
| } | |
| /* Check for matching burst */ | |
| burst_est = div_u64(slot_ns * rate, 1000000000); | |
| burst_sug = burst_est; | |
| if (flags & YT921X_MARKER_PKT_MODE) | |
| burst_sug++; | |
| else | |
| burst_sug += ETH_HLEN + yt921x_mtu_fetch(priv, port) + | |
| ETH_FCS_LEN; | |
| if (burst_sug > burst) | |
| NL_SET_ERR_MSG_FMT_MOD(extack, | |
| "Consider match rate %llu with burst at least %llu", | |
| rate, burst_sug); | |
| /* Select unit */ | |
| for (; m.unit > 0; m.unit--) { | |
| if (rate > (rate_max >> 2) || burst > (burst_max >> 2)) | |
| break; | |
| rate_max >>= 2; | |
| burst_max >>= 2; | |
| } | |
| /* Calculate information rate and bucket size */ | |
| m.cir = rate2token(rate, slot_ns, m.unit, C); | |
| if (!m.cir) | |
| m.cir = 1; | |
| else if (WARN_ON(m.cir > cir_max)) | |
| m.cir = cir_max; | |
| m.cbs = burst2token(burst, m.unit, C); | |
| if (!m.cbs) | |
| m.cbs = 1; | |
| else if (WARN_ON(m.cbs > cbs_max)) | |
| m.cbs = cbs_max; | |
| /* Cut EBS */ | |
| m.ebs = 0; | |
| if (!(flags & YT921X_MARKER_SINGLE_BUCKET)) { | |
| /* We don't have a chance to adjust rate when MTU is changed */ | |
| if (flags & YT921X_MARKER_PKT_MODE) | |
| burst_est++; | |
| else | |
| burst_est += YT921X_FRAME_SIZE_MAX; | |
| if (burst_est < burst) { | |
| u32 pbs = m.cbs; | |
| m.cbs = burst2token(burst_est, m.unit, C); | |
| if (!m.cbs) | |
| m.cbs = 1; | |
| else if (WARN_ON(m.cbs > cbs_max)) | |
| m.cbs = cbs_max; | |
| if (pbs > m.cbs) | |
| m.ebs = pbs - m.cbs; | |
| } | |
| } | |
| dev_dbg(dev, | |
| "slot %u ns, rate %llu, burst %llu -> unit %d, cir %u, cbs %u, ebs %u\n", | |
| slot_ns, rate, burst, m.unit, m.cir, m.cbs, m.ebs); | |
| m.pkt_mode = flags & YT921X_MARKER_PKT_MODE; | |
| *marker = m; | |
| return 0; | |
| } | |
| static int | |
| yt921x_marker_tfm_police(struct yt921x_marker *marker, | |
| const struct flow_action_police *police, | |
| unsigned int flags, struct yt921x_priv *priv, int port, | |
| struct netlink_ext_ack *extack) | |
| { | |
| bool pkt_mode = !!police->rate_pkt_ps; | |
| u64 burst; | |
| u64 rate; | |
| rate = pkt_mode ? police->rate_pkt_ps : police->rate_bytes_ps; | |
| burst = pkt_mode ? police->burst_pkt : police->burst; | |
| if (pkt_mode) | |
| flags |= YT921X_MARKER_PKT_MODE; | |
| return yt921x_marker_tfm(marker, rate, burst, flags, | |
| priv->meter_slot_ns, YT921X_METER_CIR_MAX, | |
| YT921X_METER_CBS_MAX, YT921X_METER_UNIT_MAX, | |
| priv, port, extack); | |
| } | |
| static int | |
| yt921x_marker_tfm_shape(struct yt921x_marker *marker, u64 rate, u64 burst, | |
| unsigned int flags, struct yt921x_priv *priv, int port, | |
| struct netlink_ext_ack *extack) | |
| { | |
| return yt921x_marker_tfm(marker, rate, burst, flags, | |
| priv->port_shape_slot_ns, YT921X_SHAPE_CIR_MAX, | |
| YT921X_SHAPE_CBS_MAX, YT921X_SHAPE_UNIT_MAX, | |
| priv, port, extack); | |
| } | |
| static int | |
| yt921x_police_validate(const struct flow_action_police *police, | |
| const struct flow_action *action, | |
| const struct flow_action_entry *act, | |
| struct netlink_ext_ack *extack) | |
| { | |
| if (police->exceed.act_id != FLOW_ACTION_DROP) { | |
| NL_SET_ERR_MSG_MOD(extack, | |
| "Offload not supported when exceed action is not drop"); | |
| return -EOPNOTSUPP; | |
| } | |
| if (police->notexceed.act_id != FLOW_ACTION_PIPE && | |
| police->notexceed.act_id != FLOW_ACTION_ACCEPT) { | |
| NL_SET_ERR_MSG_MOD(extack, | |
| "Offload not supported when conform action is not pipe or ok"); | |
| return -EOPNOTSUPP; | |
| } | |
| if (police->notexceed.act_id == FLOW_ACTION_ACCEPT && action && act && | |
| !flow_action_is_last_entry(action, act)) { | |
| NL_SET_ERR_MSG_MOD(extack, | |
| "Offload not supported when conform action is ok, but action is not last"); | |
| return -EOPNOTSUPP; | |
| } | |
| /* mtu defaults to unlimited but we got 2040 here, don't know why */ | |
| if (police->peakrate_bytes_ps || police->avrate || police->overhead) { | |
| NL_SET_ERR_MSG_MOD(extack, | |
| "Offload not supported when peakrate/avrate/overhead is configured"); | |
| return -EOPNOTSUPP; | |
| } | |
| return 0; | |
| } | |
| static int | |
| yt921x_meter_config(struct yt921x_priv *priv, unsigned int id, | |
| const struct yt921x_marker *marker) | |
| { | |
| u32 ctrls[3]; | |
| ctrls[0] = 0; | |
| ctrls[1] = YT921X_METER_CTRLb_CIR(marker->cir); | |
| ctrls[2] = YT921X_METER_CTRLc_UNIT(marker->unit) | | |
| YT921X_METER_CTRLc_DROP_R | | |
| YT921X_METER_CTRLc_TOKEN_OVERFLOW_EN | | |
| YT921X_METER_CTRLc_METER_EN; | |
| if (marker->pkt_mode) | |
| ctrls[2] |= YT921X_METER_CTRLc_PKT_MODE; | |
| update_ctrls_unaligned(&ctrls[0], &ctrls[1], | |
| YT921X_METER_CTRLab_EBS_M, | |
| YT921X_METER_CTRLab_EBS(marker->ebs)); | |
| update_ctrls_unaligned(&ctrls[1], &ctrls[2], | |
| YT921X_METER_CTRLbc_CBS_M, | |
| YT921X_METER_CTRLbc_CBS(marker->cbs)); | |
| return yt921x_reg96_write(priv, YT921X_METERn_CTRL(id), ctrls); | |
| } | |
| static void yt921x_dsa_port_policer_del(struct dsa_switch *ds, int port) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| struct device *dev = to_device(priv); | |
| int res; | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_reg_write(priv, YT921X_PORTn_METER(port), 0); | |
| mutex_unlock(&priv->reg_lock); | |
| if (res) | |
| dev_err(dev, "Failed to %s port %d: %i\n", "delete policer on", | |
| port, res); | |
| } | |
| static int | |
| yt921x_dsa_port_policer_add(struct dsa_switch *ds, int port, | |
| const struct flow_action_police *police, | |
| struct netlink_ext_ack *extack) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| struct yt921x_marker marker; | |
| u32 ctrl; | |
| int res; | |
| res = yt921x_police_validate(police, NULL, NULL, extack); | |
| if (res) | |
| return res; | |
| res = yt921x_marker_tfm_police(&marker, police, 0, priv, port, extack); | |
| if (res) | |
| return res; | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_meter_config(priv, port + YT921X_METER_NUM, &marker); | |
| if (res) | |
| goto end; | |
| ctrl = YT921X_PORT_METER_ID(port) | YT921X_PORT_METER_EN; | |
| res = yt921x_reg_write(priv, YT921X_PORTn_METER(port), ctrl); | |
| end: | |
| mutex_unlock(&priv->reg_lock); | |
| return res; | |
| } | |
| static int | |
| yt921x_dsa_port_setup_tc_tbf_port(struct dsa_switch *ds, int port, | |
| const struct tc_tbf_qopt_offload *qopt) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| struct netlink_ext_ack *extack = qopt->extack; | |
| u32 ctrls[2]; | |
| int res; | |
| if (qopt->parent != TC_H_ROOT) | |
| return -EOPNOTSUPP; | |
| switch (qopt->command) { | |
| case TC_TBF_STATS: | |
| /* Unfortunately the convention for TC_*_STATS is a mess, | |
| * neither 0 nor -EOPNOTSUPP is perfect here. | |
| */ | |
| return -EOPNOTSUPP; | |
| case TC_TBF_DESTROY: | |
| ctrls[0] = 0; | |
| ctrls[1] = 0; | |
| break; | |
| case TC_TBF_REPLACE: { | |
| const struct tc_tbf_qopt_offload_replace_params *p; | |
| struct yt921x_marker marker; | |
| p = &qopt->replace_params; | |
| res = yt921x_marker_tfm_shape(&marker, p->rate.rate_bytes_ps, | |
| p->max_size, | |
| YT921X_MARKER_SINGLE_BUCKET, | |
| priv, port, extack); | |
| if (res) | |
| return res; | |
| ctrls[0] = YT921X_PORT_SHAPE_CTRLa_CIR(marker.cir) | | |
| YT921X_PORT_SHAPE_CTRLa_CBS(marker.cbs); | |
| ctrls[1] = YT921X_PORT_SHAPE_CTRLb_UNIT(marker.unit) | | |
| YT921X_PORT_SHAPE_CTRLb_EN; | |
| break; | |
| } | |
| default: | |
| return -EOPNOTSUPP; | |
| } | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_reg64_write(priv, YT921X_PORTn_SHAPE_CTRL(port), ctrls); | |
| mutex_unlock(&priv->reg_lock); | |
| return res; | |
| } | |
| static int | |
| yt921x_dsa_port_setup_tc(struct dsa_switch *ds, int port, | |
| enum tc_setup_type type, void *type_data) | |
| { | |
| switch (type) { | |
| case TC_SETUP_QDISC_TBF: { | |
| const struct tc_tbf_qopt_offload *qopt = type_data; | |
| return yt921x_dsa_port_setup_tc_tbf_port(ds, port, qopt); | |
| } | |
| default: | |
| return -EOPNOTSUPP; | |
| } | |
| } | |
| /* ACL: 48 blocks * 8 entries | |
| * | |
| * One rule can span multiple entries, but within a block. | |
| */ | |
| static void | |
| yt921x_acl_entry_set(struct yt921x_acl_entry *entry, unsigned int offset, | |
| u32 flags, bool set) | |
| { | |
| if (set) | |
| entry->key[offset] |= flags; | |
| entry->mask[offset] |= flags; | |
| } | |
| static unsigned int | |
| yt921x_acl_entries_set_is_fragment(struct yt921x_acl_entry *entries, | |
| unsigned int size, bool set) | |
| { | |
| for (unsigned int i = 0; i < size; i++) | |
| switch (FIELD_GET(YT921X_ACL_KEYb_TYPE_M, entries[i].key[1])) { | |
| case YT921X_ACL_TYPE_IPV4_DA: | |
| case YT921X_ACL_TYPE_IPV4_SA: | |
| yt921x_acl_entry_set(&entries[i], 1, | |
| YT921X_ACL_BINb_IPV4_FRAG, set); | |
| return size; | |
| case YT921X_ACL_TYPE_IPV6_DA3: | |
| case YT921X_ACL_TYPE_IPV6_SA3: | |
| yt921x_acl_entry_set(&entries[i], 1, | |
| YT921X_ACL_BINb_IPV6_xA3_FRAG, | |
| set); | |
| return size; | |
| case YT921X_ACL_TYPE_MISC: | |
| yt921x_acl_entry_set(&entries[i], 1, | |
| YT921X_ACL_BINb_MISC_FRAG, set); | |
| return size; | |
| case YT921X_ACL_TYPE_L4: | |
| yt921x_acl_entry_set(&entries[i], 1, | |
| YT921X_ACL_BINb_L4_FRAG, set); | |
| return size; | |
| } | |
| if (size >= YT921X_ACL_ENT_PER_BLK) | |
| return 0; | |
| entries[size] = (typeof(*entries)){}; | |
| entries[size].key[1] = YT921X_ACL_KEYb_TYPE(YT921X_ACL_TYPE_MISC); | |
| yt921x_acl_entry_set(&entries[size], 1, YT921X_ACL_BINb_MISC_FRAG, set); | |
| return size + 1; | |
| } | |
| static unsigned int | |
| yt921x_acl_entries_set_first_frag(struct yt921x_acl_entry *entries, | |
| unsigned int size, bool set) | |
| { | |
| for (unsigned int i = 0; i < size; i++) | |
| switch (FIELD_GET(YT921X_ACL_KEYb_TYPE_M, entries[i].key[1])) { | |
| case YT921X_ACL_TYPE_IPV6_DA2: | |
| case YT921X_ACL_TYPE_IPV6_SA2: | |
| yt921x_acl_entry_set(&entries[i], 1, | |
| YT921X_ACL_BINb_IPV6_xA2_FIRST_FRAG, | |
| set); | |
| return size; | |
| case YT921X_ACL_TYPE_MISC: | |
| yt921x_acl_entry_set(&entries[i], 0, | |
| YT921X_ACL_BINa_MISC_FIRST_FRAG, | |
| set); | |
| return size; | |
| } | |
| if (size >= YT921X_ACL_ENT_PER_BLK) | |
| return 0; | |
| entries[size] = (typeof(*entries)){}; | |
| entries[size].key[1] = YT921X_ACL_KEYb_TYPE(YT921X_ACL_TYPE_MISC); | |
| yt921x_acl_entry_set(&entries[size], 0, | |
| YT921X_ACL_BINa_MISC_FIRST_FRAG, set); | |
| return size + 1; | |
| } | |
| static unsigned int | |
| yt921x_acl_entries_set_l3_type(struct yt921x_acl_entry *entries, | |
| unsigned int size, enum yt921x_l3_type type) | |
| { | |
| for (unsigned int i = 0; i < size; i++) | |
| switch (FIELD_GET(YT921X_ACL_KEYb_TYPE_M, entries[i].key[1])) { | |
| case YT921X_ACL_TYPE_MAC_DA0: | |
| case YT921X_ACL_TYPE_MAC_SA0: | |
| entries[i].key[1] |= YT921X_ACL_BINb_MAC_xA0_L3_TYPE(type); | |
| entries[i].mask[1] |= YT921X_ACL_BINb_MAC_xA0_L3_TYPE_M; | |
| return size; | |
| case YT921X_ACL_TYPE_MISC: | |
| entries[i].key[0] |= YT921X_ACL_BINa_MISC_L3_TYPE(type); | |
| entries[i].mask[0] |= YT921X_ACL_BINa_MISC_L3_TYPE_M; | |
| return size; | |
| } | |
| if (size >= YT921X_ACL_ENT_PER_BLK) | |
| return 0; | |
| entries[size] = (typeof(*entries)){}; | |
| entries[size].key[0] = YT921X_ACL_BINa_MISC_L3_TYPE(type); | |
| entries[size].key[1] = YT921X_ACL_KEYb_TYPE(YT921X_ACL_TYPE_MISC); | |
| entries[size].mask[0] = YT921X_ACL_BINa_MISC_L3_TYPE_M; | |
| return size + 1; | |
| } | |
| static unsigned int | |
| yt921x_acl_entries_set_l4_type(struct yt921x_acl_entry *entries, | |
| unsigned int size, enum yt921x_l4_type type) | |
| { | |
| for (unsigned int i = 0; i < size; i++) | |
| switch (FIELD_GET(YT921X_ACL_KEYb_TYPE_M, entries[i].key[1])) { | |
| case YT921X_ACL_TYPE_IPV4_DA: | |
| case YT921X_ACL_TYPE_IPV4_SA: | |
| entries[i].key[1] |= YT921X_ACL_BINb_IPV4_L4_TYPE(type); | |
| entries[i].mask[1] |= YT921X_ACL_BINb_IPV4_L4_TYPE_M; | |
| return size; | |
| case YT921X_ACL_TYPE_IPV6_DA0: | |
| case YT921X_ACL_TYPE_IPV6_DA1: | |
| case YT921X_ACL_TYPE_IPV6_DA2: | |
| case YT921X_ACL_TYPE_IPV6_DA3: | |
| case YT921X_ACL_TYPE_IPV6_SA0: | |
| case YT921X_ACL_TYPE_IPV6_SA1: | |
| case YT921X_ACL_TYPE_IPV6_SA2: | |
| case YT921X_ACL_TYPE_IPV6_SA3: | |
| entries[i].key[1] |= YT921X_ACL_BINb_IPV6_L4_TYPE(type); | |
| entries[i].mask[1] |= YT921X_ACL_BINb_IPV6_L4_TYPE_M; | |
| return size; | |
| case YT921X_ACL_TYPE_L4: | |
| entries[i].key[1] |= YT921X_ACL_BINb_L4_TYPE(type); | |
| entries[i].mask[1] |= YT921X_ACL_BINb_L4_TYPE_M; | |
| return size; | |
| case YT921X_ACL_TYPE_MISC: | |
| entries[i].key[1] |= YT921X_ACL_BINb_MISC_L4_TYPE(type); | |
| entries[i].mask[1] |= YT921X_ACL_BINb_MISC_L4_TYPE_M; | |
| return size; | |
| } | |
| if (size >= YT921X_ACL_ENT_PER_BLK) | |
| return 0; | |
| entries[size] = (typeof(*entries)){}; | |
| entries[size].key[1] = YT921X_ACL_BINb_MISC_L4_TYPE(type) | | |
| YT921X_ACL_KEYb_TYPE(YT921X_ACL_TYPE_MISC); | |
| entries[size].mask[1] = YT921X_ACL_BINb_MISC_L4_TYPE_M; | |
| return size + 1; | |
| } | |
| static struct yt921x_acl_entry * | |
| yt921x_acl_entries_new(struct yt921x_acl_entry *entries, unsigned int *sizep, | |
| u32 type) | |
| { | |
| unsigned int size = *sizep; | |
| if (size >= YT921X_ACL_ENT_PER_BLK) | |
| return NULL; | |
| entries[size] = (typeof(*entries)){}; | |
| entries[size].key[1] = YT921X_ACL_KEYb_TYPE(type); | |
| (*sizep)++; | |
| return &entries[size]; | |
| } | |
| static struct yt921x_acl_entry * | |
| yt921x_acl_entries_find(struct yt921x_acl_entry *entries, unsigned int *sizep, | |
| u32 type) | |
| { | |
| for (unsigned int i = 0; i < *sizep; i++) | |
| if (FIELD_GET(YT921X_ACL_KEYb_TYPE_M, entries[i].key[1]) == | |
| type) | |
| return &entries[i]; | |
| return yt921x_acl_entries_new(entries, sizep, type); | |
| } | |
| static void | |
| yt921x_acl_rule_set_ports(struct yt921x_acl_rule *aclrule, u16 ord, | |
| u16 ports_mask) | |
| { | |
| struct yt921x_acl_entry *entries = aclrule->entries; | |
| for (unsigned int i = 0; i < hweight8(aclrule->mask); i++) { | |
| entries[i].key[1] |= YT921X_ACL_KEYb_SPORTS(ports_mask) | | |
| YT921X_ACL_KEYb_ORD(ord); | |
| } | |
| } | |
| struct yt921x_acl_rule_ext { | |
| struct yt921x_acl_rule r; | |
| struct yt921x_marker marker; | |
| }; | |
| static int | |
| yt921x_acl_rule_ext_parse_flow_entries(struct yt921x_acl_rule_ext *ruleext, | |
| const struct flow_cls_offload *cls) | |
| { | |
| const struct flow_rule *rule = flow_cls_offload_flow_rule(cls); | |
| struct yt921x_acl_entry *entries = ruleext->r.entries; | |
| struct netlink_ext_ack *extack = cls->common.extack; | |
| const struct flow_dissector *dissector; | |
| struct yt921x_acl_entry *entry; | |
| unsigned int size = 0; | |
| bool use_dport; | |
| bool use_sport; | |
| /* Incomplete and probably won't, since it supports custom u32 filters. | |
| * New adapters are welcome. | |
| */ | |
| dissector = rule->match.dissector; | |
| if (dissector->used_keys & | |
| ~(BIT_ULL(FLOW_DISSECTOR_KEY_CONTROL) | | |
| BIT_ULL(FLOW_DISSECTOR_KEY_BASIC) | | |
| BIT_ULL(FLOW_DISSECTOR_KEY_IPV4_ADDRS) | | |
| BIT_ULL(FLOW_DISSECTOR_KEY_IPV6_ADDRS) | | |
| BIT_ULL(FLOW_DISSECTOR_KEY_PORTS) | | |
| BIT_ULL(FLOW_DISSECTOR_KEY_PORTS_RANGE) | | |
| BIT_ULL(FLOW_DISSECTOR_KEY_ETH_ADDRS) | | |
| BIT_ULL(FLOW_DISSECTOR_KEY_IP) | | |
| BIT_ULL(FLOW_DISSECTOR_KEY_TCP))) { | |
| NL_SET_ERR_MSG_MOD(extack, "Unsupported keys used"); | |
| return -EOPNOTSUPP; | |
| } | |
| /* Entries */ | |
| if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IPV4_ADDRS)) { | |
| struct flow_match_ipv4_addrs match; | |
| flow_rule_match_ipv4_addrs(rule, &match); | |
| if (match.mask->dst) { | |
| entry = yt921x_acl_entries_new(entries, &size, | |
| YT921X_ACL_TYPE_IPV4_DA); | |
| if (!entry) | |
| goto err; | |
| entry->key[0] |= ntohl(match.key->dst); | |
| entry->mask[0] |= ntohl(match.mask->dst); | |
| } | |
| if (match.mask->src) { | |
| entry = yt921x_acl_entries_new(entries, &size, | |
| YT921X_ACL_TYPE_IPV4_SA); | |
| if (!entry) | |
| goto err; | |
| entry->key[0] |= ntohl(match.key->src); | |
| entry->mask[0] |= ntohl(match.mask->src); | |
| } | |
| } | |
| if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IPV6_ADDRS)) { | |
| struct flow_match_ipv6_addrs match; | |
| flow_rule_match_ipv6_addrs(rule, &match); | |
| for (unsigned int i = 0; i < 4; i++) { | |
| if (!match.mask->dst.s6_addr32[i]) | |
| continue; | |
| entry = yt921x_acl_entries_new(entries, &size, | |
| YT921X_ACL_TYPE_IPV6_DA0 + i); | |
| if (!entry) | |
| goto err; | |
| entry->key[0] |= ntohl(match.key->dst.s6_addr32[i]); | |
| entry->mask[0] |= ntohl(match.mask->dst.s6_addr32[i]); | |
| } | |
| for (unsigned int i = 0; i < 4; i++) { | |
| if (!match.mask->src.s6_addr32[i]) | |
| continue; | |
| entry = yt921x_acl_entries_new(entries, &size, | |
| YT921X_ACL_TYPE_IPV6_SA0 + i); | |
| if (!entry) | |
| goto err; | |
| entry->key[0] |= ntohl(match.key->src.s6_addr32[i]); | |
| entry->mask[0] |= ntohl(match.mask->src.s6_addr32[i]); | |
| } | |
| } | |
| use_dport = false; | |
| use_sport = false; | |
| if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_PORTS)) { | |
| struct flow_match_ports match; | |
| entry = yt921x_acl_entries_new(entries, &size, | |
| YT921X_ACL_TYPE_L4); | |
| if (!entry) | |
| goto err; | |
| flow_rule_match_ports(rule, &match); | |
| use_dport = !!match.mask->dst; | |
| use_sport = !!match.mask->src; | |
| entry->key[0] |= (ntohs(match.key->dst) << 16) | | |
| ntohs(match.key->src); | |
| entry->mask[0] |= (ntohs(match.mask->dst) << 16) | | |
| ntohs(match.mask->src); | |
| } | |
| if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_PORTS_RANGE)) { | |
| struct flow_match_ports_range match; | |
| entry = yt921x_acl_entries_find(entries, &size, | |
| YT921X_ACL_TYPE_L4); | |
| if (!entry) | |
| goto err; | |
| flow_rule_match_ports_range(rule, &match); | |
| if ((use_dport && match.mask->tp.dst) || | |
| (use_sport && match.mask->tp.src)) { | |
| NL_SET_ERR_MSG_MOD(extack, | |
| "Port mask and range are mutually exclusive"); | |
| return -EINVAL; | |
| } | |
| if (match.mask->tp.dst) { | |
| entry->key[0] |= ntohs(match.key->tp_min.dst) << 16; | |
| entry->key[1] |= YT921X_ACL_KEYb_L4_DPORT_RANGE_EN; | |
| entry->mask[0] |= ntohs(match.key->tp_max.dst) << 16; | |
| } | |
| if (match.mask->tp.src) { | |
| entry->key[0] |= ntohs(match.key->tp_min.src); | |
| entry->key[1] |= YT921X_ACL_KEYb_L4_SPORT_RANGE_EN; | |
| entry->mask[0] |= ntohs(match.key->tp_max.src); | |
| } | |
| } | |
| if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_ETH_ADDRS)) { | |
| struct flow_match_eth_addrs match; | |
| u32 mask; | |
| flow_rule_match_eth_addrs(rule, &match); | |
| mask = ethaddr_hi4_to_u32(match.mask->dst); | |
| if (mask) { | |
| entry = yt921x_acl_entries_new(entries, &size, | |
| YT921X_ACL_TYPE_MAC_DA0); | |
| if (!entry) | |
| goto err; | |
| entry->key[0] |= ethaddr_hi4_to_u32(match.key->dst); | |
| entry->mask[0] |= mask; | |
| } | |
| mask = ethaddr_hi4_to_u32(match.mask->src); | |
| if (mask) { | |
| entry = yt921x_acl_entries_new(entries, &size, | |
| YT921X_ACL_TYPE_MAC_SA0); | |
| if (!entry) | |
| goto err; | |
| entry->key[0] |= ethaddr_hi4_to_u32(match.key->src); | |
| entry->mask[0] |= mask; | |
| } | |
| mask = (ethaddr_lo2_to_u32(match.mask->dst) << 16) | | |
| ethaddr_lo2_to_u32(match.mask->src); | |
| if (mask) { | |
| entry = yt921x_acl_entries_new(entries, &size, | |
| YT921X_ACL_TYPE_MAC_DA1_SA1); | |
| if (!entry) | |
| goto err; | |
| entry->key[0] |= (ethaddr_lo2_to_u32(match.key->dst) << 16) | | |
| ethaddr_lo2_to_u32(match.key->src); | |
| entry->mask[0] |= mask; | |
| } | |
| } | |
| /* Entries + Misc */ | |
| if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_BASIC)) { | |
| struct flow_match_basic match; | |
| flow_rule_match_basic(rule, &match); | |
| if (match.mask->n_proto) { | |
| enum yt921x_l3_type l3type = YT921X_L3_TYPE_OTHER; | |
| if (match.mask->n_proto == htons(~0)) | |
| switch (match.key->n_proto) { | |
| case htons(ETH_P_IP): | |
| l3type = YT921X_L3_TYPE_IPV4; | |
| break; | |
| case htons(ETH_P_IPV6): | |
| l3type = YT921X_L3_TYPE_IPV6; | |
| break; | |
| case htons(ETH_P_ARP): | |
| l3type = YT921X_L3_TYPE_ARP; | |
| break; | |
| case htons(ETH_P_LLDP): | |
| l3type = YT921X_L3_TYPE_LLDP; | |
| break; | |
| case htons(ETH_P_PAE): | |
| l3type = YT921X_L3_TYPE_PAE; | |
| break; | |
| case htons(ETH_P_CFM): | |
| l3type = YT921X_L3_TYPE_ERP; | |
| break; | |
| } | |
| if (l3type != YT921X_L3_TYPE_OTHER) { | |
| size = yt921x_acl_entries_set_l3_type(entries, | |
| size, | |
| l3type); | |
| if (!size) | |
| goto err; | |
| } else { | |
| entry = yt921x_acl_entries_new(entries, &size, | |
| YT921X_ACL_TYPE_ETHERTYPE); | |
| if (!entry) | |
| goto err; | |
| entry->key[0] |= ntohs(match.key->n_proto); | |
| entry->mask[0] |= ntohs(match.mask->n_proto); | |
| } | |
| } | |
| if (match.mask->ip_proto) { | |
| enum yt921x_l4_type l4type = YT921X_L4_TYPE_OTHER; | |
| if (match.mask->ip_proto == (u8)~0) | |
| switch (match.key->ip_proto) { | |
| case IPPROTO_TCP: | |
| l4type = YT921X_L4_TYPE_TCP; | |
| break; | |
| case IPPROTO_UDP: | |
| l4type = YT921X_L4_TYPE_UDP; | |
| break; | |
| case IPPROTO_UDPLITE: | |
| l4type = YT921X_L4_TYPE_UDPLITE; | |
| break; | |
| case IPPROTO_ICMP: | |
| l4type = YT921X_L4_TYPE_ICMP; | |
| break; | |
| case IPPROTO_IGMP: | |
| l4type = YT921X_L4_TYPE_IGMP; | |
| break; | |
| } | |
| if (l4type != YT921X_L4_TYPE_OTHER) { | |
| size = yt921x_acl_entries_set_l4_type(entries, | |
| size, | |
| l4type); | |
| if (!size) | |
| goto err; | |
| } else { | |
| entry = yt921x_acl_entries_find(entries, &size, | |
| YT921X_ACL_TYPE_MISC); | |
| if (!entry) | |
| goto err; | |
| entry->key[0] |= YT921X_ACL_BINa_MISC_IP_PROTO(match.key->ip_proto); | |
| entry->mask[0] |= YT921X_ACL_BINa_MISC_IP_PROTO(match.mask->ip_proto); | |
| } | |
| } | |
| } | |
| if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_CONTROL)) { | |
| u32 supp_flags = FLOW_DIS_IS_FRAGMENT | FLOW_DIS_FIRST_FRAG; | |
| struct flow_match_control match; | |
| flow_rule_match_control(rule, &match); | |
| if (!flow_rule_is_supp_control_flags(supp_flags, | |
| match.mask->flags, extack)) | |
| return -EOPNOTSUPP; | |
| if (match.mask->flags & FLOW_DIS_IS_FRAGMENT) { | |
| bool set = match.key->flags & FLOW_DIS_IS_FRAGMENT; | |
| size = yt921x_acl_entries_set_is_fragment(entries, size, | |
| set); | |
| if (!size) | |
| goto err; | |
| } | |
| if (match.mask->flags & FLOW_DIS_FIRST_FRAG) { | |
| bool set = match.key->flags & FLOW_DIS_FIRST_FRAG; | |
| size = yt921x_acl_entries_set_first_frag(entries, size, | |
| set); | |
| if (!size) | |
| goto err; | |
| } | |
| } | |
| /* Misc only */ | |
| if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IP)) { | |
| struct flow_match_ip match; | |
| flow_rule_match_ip(rule, &match); | |
| if (match.mask->ttl) { | |
| NL_SET_ERR_MSG_MOD(extack, | |
| "Matching on TTL not supported"); | |
| return -EOPNOTSUPP; | |
| } | |
| if (match.mask->tos) { | |
| entry = yt921x_acl_entries_find(entries, &size, | |
| YT921X_ACL_TYPE_MISC); | |
| if (!entry) | |
| goto err; | |
| entry->key[0] |= YT921X_ACL_BINa_MISC_TOS(match.key->tos); | |
| entry->mask[0] |= YT921X_ACL_BINa_MISC_TOS(match.mask->tos); | |
| } | |
| } | |
| if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_TCP)) { | |
| struct flow_match_tcp match; | |
| flow_rule_match_tcp(rule, &match); | |
| if (match.mask->flags & htons(~0xff)) { | |
| NL_SET_ERR_MSG_MOD(extack, "Unsupported TCP flags"); | |
| return -EOPNOTSUPP; | |
| } | |
| if (match.mask->flags) { | |
| entry = yt921x_acl_entries_find(entries, &size, | |
| YT921X_ACL_TYPE_MISC); | |
| if (!entry) | |
| goto err; | |
| entry->key[0] |= YT921X_ACL_BINa_MISC_TCP_FLAGS(ntohs(match.key->flags)); | |
| entry->mask[0] |= YT921X_ACL_BINa_MISC_TCP_FLAGS(ntohs(match.mask->flags)); | |
| } | |
| } | |
| if (!size) { | |
| NL_SET_ERR_MSG_MOD(extack, "Empty rule generated, this should not happen"); | |
| return -EOPNOTSUPP; | |
| } | |
| ruleext->r.mask = (1 << size) - 1; | |
| return 0; | |
| err: | |
| NL_SET_ERR_MSG_MOD(extack, "Rule too complex"); | |
| return -EOPNOTSUPP; | |
| } | |
| static int | |
| yt921x_acl_rule_ext_parse_flow_action(struct yt921x_acl_rule_ext *ruleext, | |
| const struct flow_cls_offload *cls, | |
| struct yt921x_priv *priv, int port) | |
| { | |
| const struct flow_rule *rule = flow_cls_offload_flow_rule(cls); | |
| const struct flow_action *flow_action = &rule->action; | |
| struct netlink_ext_ack *extack = cls->common.extack; | |
| enum flow_action_id redir_act = NUM_FLOW_ACTIONS; | |
| const struct flow_action_entry *act; | |
| u32 *action = ruleext->r.action; | |
| bool seen_priority = false; | |
| const char *reason = NULL; | |
| bool seen_police = false; | |
| unsigned int i; | |
| int res; | |
| memset(action, 0, 3 * sizeof(*action)); | |
| flow_action_for_each(i, act, flow_action) | |
| switch (act->id) { | |
| case FLOW_ACTION_ACCEPT: | |
| case FLOW_ACTION_DROP: | |
| case FLOW_ACTION_REDIRECT: | |
| if (redir_act != NUM_FLOW_ACTIONS && | |
| redir_act != act->id) { | |
| reason = "Different redirect actions"; | |
| goto fallback; | |
| } | |
| redir_act = act->id; | |
| switch (act->id) { | |
| case FLOW_ACTION_ACCEPT: | |
| action[2] |= YT921X_ACL_ACTc_FWD_EN | | |
| YT921X_ACL_ACTc_FWD_FWD; | |
| break; | |
| case FLOW_ACTION_DROP: | |
| action[2] |= YT921X_ACL_ACTc_FWD_EN | | |
| YT921X_ACL_ACTc_FWD_REDIR; | |
| break; | |
| case FLOW_ACTION_REDIRECT: { | |
| struct dsa_port *to_dp; | |
| to_dp = dsa_port_from_netdev(act->dev); | |
| if (IS_ERR(to_dp) || to_dp->ds != &priv->ds) { | |
| reason = "Redirect to non-local port"; | |
| goto fallback; | |
| } | |
| action[2] |= YT921X_ACL_ACTc_FWD_EN | | |
| YT921X_ACL_ACTc_FWD_REDIR | | |
| YT921X_ACL_ACTc_FWD_REDIR_DPORTn(to_dp->index); | |
| break; | |
| } | |
| default: | |
| break; | |
| } | |
| break; | |
| case FLOW_ACTION_PRIORITY: | |
| if (seen_priority) { | |
| action[0] &= ~YT921X_ACL_ACTa_PRIO_EN; | |
| action[1] &= ~YT921X_ACL_ACTb_PRIO_M; | |
| reason = "Multiple priority actions"; | |
| goto fallback; | |
| } | |
| seen_priority = true; | |
| if (act->priority >= YT921X_PRIO_NUM) { | |
| NL_SET_ERR_MSG_MOD(extack, | |
| "Priority value is too high"); | |
| return -EOPNOTSUPP; | |
| } | |
| action[0] |= YT921X_ACL_ACTa_PRIO_EN; | |
| action[1] |= YT921X_ACL_ACTb_PRIO(act->priority); | |
| break; | |
| case FLOW_ACTION_POLICE: { | |
| const struct flow_action_police *police = &act->police; | |
| if (seen_police) { | |
| action[0] &= ~YT921X_ACL_ACTa_METER_EN; | |
| reason = "Multiple police actions"; | |
| goto fallback; | |
| } | |
| seen_police = true; | |
| res = yt921x_police_validate(police, flow_action, act, | |
| extack); | |
| if (res) | |
| return res; | |
| res = yt921x_marker_tfm_police(&ruleext->marker, police, | |
| 0, priv, port, extack); | |
| if (res) | |
| return res; | |
| action[0] |= YT921X_ACL_ACTa_METER_EN; | |
| break; | |
| } | |
| default: | |
| fallback: | |
| if (cls->common.skip_sw) { | |
| NL_SET_ERR_MSG_FMT_MOD(extack, | |
| "Action not supported when skip_sw: %s", | |
| reason); | |
| return -EOPNOTSUPP; | |
| } | |
| fallthrough; | |
| case FLOW_ACTION_TRAP: | |
| redir_act = FLOW_ACTION_TRAP; | |
| action[2] &= ~YT921X_ACL_ACTc_FWD_REDIR_DPORTS_M & | |
| ~YT921X_ACL_ACTc_FWD_M; | |
| action[2] |= YT921X_ACL_ACTc_FWD_EN | | |
| YT921X_ACL_ACTc_FWD_TRAP; | |
| break; | |
| } | |
| ruleext->r.sw_assisted = !cls->common.skip_sw; | |
| return 0; | |
| } | |
| static int | |
| yt921x_acl_rule_ext_parse_flow(struct yt921x_acl_rule_ext *ruleext, int port, | |
| const struct flow_cls_offload *cls, bool ingress, | |
| struct yt921x_priv *priv) | |
| { | |
| struct netlink_ext_ack *extack = cls->common.extack; | |
| int res; | |
| if (!ingress) { | |
| NL_SET_ERR_MSG_MOD(extack, "Only ingress is supported"); | |
| return -EOPNOTSUPP; | |
| } | |
| if (cls->common.chain_index) { | |
| NL_SET_ERR_MSG(extack, "Only chain 0 is supported"); | |
| return -EOPNOTSUPP; | |
| } | |
| res = yt921x_acl_rule_ext_parse_flow_action(ruleext, cls, priv, port); | |
| if (res) | |
| return res; | |
| res = yt921x_acl_rule_ext_parse_flow_entries(ruleext, cls); | |
| if (res) | |
| return res; | |
| yt921x_acl_rule_set_ports(&ruleext->r, 0, BIT(port)); | |
| ruleext->r.tag = cls->cookie; | |
| ruleext->r.type = TC_SETUP_CLSFLOWER; | |
| return 0; | |
| } | |
| static unsigned int | |
| yt921x_acl_find(const struct yt921x_priv *priv, enum tc_setup_type type, | |
| unsigned long tag) | |
| { | |
| for (unsigned int blkid = 0; blkid < YT921X_ACL_BLK_NUM; blkid++) { | |
| const struct yt921x_acl_blk *aclblk = priv->acl_blks[blkid]; | |
| if (!aclblk) | |
| continue; | |
| for (unsigned int i = 0; i < YT921X_ACL_ENT_PER_BLK; i++) | |
| if (aclblk->rules[i] && aclblk->rules[i]->tag == tag && | |
| aclblk->rules[i]->type == type) | |
| return YT921X_ACL_ENT_PER_BLK * blkid + i; | |
| } | |
| return UINT_MAX; | |
| } | |
| static unsigned int | |
| yt921x_acl_reserve(struct yt921x_priv *priv, unsigned int entscnt, | |
| struct netlink_ext_ack *extack) | |
| { | |
| int candidates[YT921X_ACL_ENT_PER_BLK + 1]; | |
| unsigned int acl_used_cnt = 0; | |
| if (WARN_ON(entscnt > YT921X_ACL_ENT_PER_BLK)) | |
| return UINT_MAX; | |
| for (unsigned int i = 0; i < ARRAY_SIZE(candidates); i++) | |
| candidates[i] = -1; | |
| for (unsigned int i = YT921X_ACL_BLK_NUM; i-- > 0;) { | |
| unsigned int blk_used_cnt = hweight8(priv->acl_masks[i]); | |
| candidates[blk_used_cnt] = i; | |
| acl_used_cnt += blk_used_cnt; | |
| } | |
| if (acl_used_cnt >= YT921X_ACL_NUM) { | |
| NL_SET_ERR_MSG_MOD(extack, "ACL entry limit reached"); | |
| return UINT_MAX; | |
| } | |
| if (acl_used_cnt + entscnt <= YT921X_ACL_NUM) | |
| for (unsigned int i = YT921X_ACL_ENT_PER_BLK - entscnt + 1; | |
| i-- > 0;) | |
| if (candidates[i] >= 0) | |
| return YT921X_ACL_ENT_PER_BLK * candidates[i] + | |
| ffz(priv->acl_masks[candidates[i]]); | |
| NL_SET_ERR_MSG_MOD(extack, | |
| "ACL entry allocation failed, simplify your rules or remove existing rules"); | |
| return UINT_MAX; | |
| } | |
| static int | |
| yt921x_acl_commit(struct yt921x_priv *priv, unsigned int entid, u8 entsmask) | |
| { | |
| const struct yt921x_acl_rule *aclrule; | |
| const struct yt921x_acl_blk *aclblk; | |
| unsigned int blkid; | |
| unsigned int binid; | |
| unsigned long mask; | |
| u32 zeros[3] = {}; | |
| unsigned int i; | |
| unsigned int o; | |
| u32 ctrl; | |
| int res; | |
| blkid = entid / YT921X_ACL_ENT_PER_BLK; | |
| binid = entid % YT921X_ACL_ENT_PER_BLK; | |
| aclblk = priv->acl_blks[blkid]; | |
| aclrule = aclblk->rules[binid]; | |
| /* Write actions */ | |
| res = yt921x_reg96_write(priv, YT921X_ACLn_ACT(entid), | |
| aclrule ? aclrule->action : zeros); | |
| if (res) | |
| return res; | |
| /* Select the block */ | |
| ctrl = YT921X_ACL_BLK_CMD_MODIFY | YT921X_ACL_BLK_CMD_BLKID(blkid); | |
| res = yt921x_reg_write(priv, YT921X_ACL_BLK_CMD, ctrl); | |
| if (res) | |
| return res; | |
| /* Write keys and masks */ | |
| ctrl = 0; | |
| for (unsigned int i = 0; i < YT921X_ACL_ENT_PER_BLK; i++) | |
| ctrl |= YT921X_ACL_BLK_KEEP_KEEPn(i); | |
| mask = entsmask; | |
| i = 0; | |
| for_each_set_bit(o, &mask, YT921X_ACL_ENT_PER_BLK) { | |
| res = yt921x_reg64_write(priv, YT921X_ACLn_KEYm(blkid, o), | |
| aclrule ? aclrule->entries[i].key : | |
| zeros); | |
| if (res) | |
| return res; | |
| res = yt921x_reg64_write(priv, YT921X_ACLn_MASKm(blkid, o), | |
| aclrule ? aclrule->entries[i].mask : | |
| zeros); | |
| if (res) | |
| return res; | |
| ctrl &= ~YT921X_ACL_BLK_KEEP_KEEPn(o); | |
| i++; | |
| } | |
| res = yt921x_reg_write(priv, YT921X_ACL_BLK_KEEP, ctrl); | |
| if (res) | |
| return res; | |
| ctrl = 0; | |
| for (unsigned int i = 0; i < YT921X_ACL_ENT_PER_BLK; i++) { | |
| const struct yt921x_acl_rule *other = aclblk->rules[i]; | |
| if (!other) | |
| continue; | |
| mask = other->mask; | |
| for_each_set_bit(o, &mask, YT921X_ACL_ENT_PER_BLK) | |
| ctrl |= YT921X_ACL_ENTRY_ENm(o) | | |
| YT921X_ACL_ENTRY_GRPIDm(o, i); | |
| } | |
| res = yt921x_reg_write(priv, YT921X_ACLn_ENTRY(blkid), ctrl); | |
| if (res) | |
| return res; | |
| /* Commit the block */ | |
| ctrl = YT921X_ACL_BLK_CMD_BLKID(blkid); | |
| res = yt921x_reg_write(priv, YT921X_ACL_BLK_CMD, ctrl); | |
| if (res) | |
| return res; | |
| return 0; | |
| } | |
| static int | |
| yt921x_acl_del(struct yt921x_priv *priv, enum tc_setup_type type, | |
| unsigned long tag) | |
| { | |
| struct yt921x_acl_rule *aclrule; | |
| struct yt921x_acl_blk *aclblk; | |
| unsigned int binid; | |
| unsigned int blkid; | |
| unsigned int entid; | |
| int res; | |
| entid = yt921x_acl_find(priv, type, tag); | |
| if (entid == UINT_MAX) | |
| return -ENOENT; | |
| blkid = entid / YT921X_ACL_ENT_PER_BLK; | |
| binid = entid % YT921X_ACL_ENT_PER_BLK; | |
| aclblk = priv->acl_blks[blkid]; | |
| aclrule = aclblk->rules[binid]; | |
| aclblk->rules[binid] = NULL; | |
| res = yt921x_acl_commit(priv, entid, aclrule->mask); | |
| /* the kernel never rolls back on failure */ | |
| if (aclrule->action[0] & YT921X_ACL_ACTa_METER_EN) | |
| clear_bit(FIELD_GET(YT921X_ACL_ACTa_METER_ID_M, | |
| aclrule->action[0]), | |
| priv->meters_map); | |
| priv->acl_masks[blkid] &= ~aclrule->mask; | |
| kvfree(aclrule); | |
| if (!priv->acl_masks[blkid]) { | |
| kvfree(aclblk); | |
| priv->acl_blks[blkid] = NULL; | |
| } | |
| return res; | |
| } | |
| static int | |
| yt921x_acl_add(struct yt921x_priv *priv, | |
| const struct yt921x_acl_rule_ext *ruleext, | |
| struct netlink_ext_ack *extack) | |
| { | |
| unsigned int entscnt = hweight8(ruleext->r.mask); | |
| struct yt921x_acl_rule *aclrule; | |
| struct yt921x_acl_blk *aclblk; | |
| bool use_trap = false; | |
| unsigned int meterid; | |
| unsigned long mask; | |
| unsigned int binid; | |
| unsigned int blkid; | |
| unsigned int entid; | |
| unsigned int o; | |
| int res; | |
| /* Allocate resources */ | |
| entid = yt921x_acl_reserve(priv, entscnt, extack); | |
| if (entid == UINT_MAX) | |
| return -EOPNOTSUPP; | |
| if (!(ruleext->r.action[0] & YT921X_ACL_ACTa_METER_EN)) { | |
| meterid = YT921X_METER_NUM; | |
| } else { | |
| meterid = find_first_zero_bit(priv->meters_map, | |
| YT921X_METER_NUM); | |
| if (meterid < YT921X_METER_NUM) { | |
| res = yt921x_meter_config(priv, meterid, | |
| &ruleext->marker); | |
| if (res) | |
| return res; | |
| } else if (ruleext->r.sw_assisted) { | |
| use_trap = true; | |
| } else { | |
| NL_SET_ERR_MSG_MOD(extack, | |
| "No more meters available"); | |
| return -EOPNOTSUPP; | |
| } | |
| } | |
| /* Prepare acl block ctrlblk */ | |
| blkid = entid / YT921X_ACL_ENT_PER_BLK; | |
| binid = entid % YT921X_ACL_ENT_PER_BLK; | |
| aclblk = priv->acl_blks[blkid]; | |
| if (!aclblk) { | |
| aclblk = kvzalloc_obj(*aclblk); | |
| if (!aclblk) | |
| return -ENOMEM; | |
| priv->acl_blks[blkid] = aclblk; | |
| } | |
| /* Prepare acl rule ctrlblk */ | |
| aclrule = kvmemdup(&ruleext->r, | |
| offsetof(struct yt921x_acl_rule, entries[entscnt]), | |
| GFP_KERNEL); | |
| if (!aclrule) { | |
| res = -ENOMEM; | |
| goto err; | |
| } | |
| /* Replace the placeholder resource IDs */ | |
| aclrule->mask = 0; | |
| mask = priv->acl_masks[blkid]; | |
| for_each_clear_bit(o, &mask, YT921X_ACL_ENT_PER_BLK) { | |
| aclrule->mask |= BIT(o); | |
| entscnt--; | |
| if (!entscnt) | |
| break; | |
| } | |
| if (use_trap) { | |
| aclrule->action[2] &= ~YT921X_ACL_ACTc_FWD_REDIR_DPORTS_M & | |
| ~YT921X_ACL_ACTc_FWD_M; | |
| aclrule->action[2] |= YT921X_ACL_ACTc_FWD_EN | | |
| YT921X_ACL_ACTc_FWD_TRAP; | |
| } | |
| if (meterid < YT921X_METER_NUM) | |
| aclrule->action[0] |= YT921X_ACL_ACTa_METER_ID(meterid); | |
| else | |
| aclrule->action[0] &= ~YT921X_ACL_ACTa_METER_EN; | |
| /* Write rules */ | |
| aclblk->rules[binid] = aclrule; | |
| res = yt921x_acl_commit(priv, entid, aclrule->mask); | |
| if (res) { | |
| aclblk->rules[binid] = NULL; | |
| kvfree(aclrule); | |
| goto err; | |
| } | |
| if (meterid < YT921X_METER_NUM) | |
| set_bit(meterid, priv->meters_map); | |
| priv->acl_masks[blkid] |= aclrule->mask; | |
| return 0; | |
| err: | |
| if (!priv->acl_masks[blkid]) { | |
| kvfree(aclblk); | |
| priv->acl_blks[blkid] = NULL; | |
| } | |
| return res; | |
| } | |
| static int | |
| yt921x_dsa_cls_flower_del(struct dsa_switch *ds, int port, | |
| struct flow_cls_offload *cls, bool ingress) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| int res; | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_acl_del(priv, TC_SETUP_CLSFLOWER, cls->cookie); | |
| mutex_unlock(&priv->reg_lock); | |
| return res; | |
| } | |
| static int | |
| yt921x_dsa_cls_flower_add(struct dsa_switch *ds, int port, | |
| struct flow_cls_offload *cls, bool ingress) | |
| { | |
| struct netlink_ext_ack *extack = cls->common.extack; | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| struct yt921x_acl_rule_ext ruleext; | |
| int res; | |
| res = yt921x_acl_rule_ext_parse_flow(&ruleext, port, cls, ingress, | |
| priv); | |
| if (res) | |
| return res; | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_acl_add(priv, &ruleext, extack); | |
| mutex_unlock(&priv->reg_lock); | |
| return res; | |
| } | |
| static int | |
| yt921x_mirror_del(struct yt921x_priv *priv, int port, bool ingress) | |
| { | |
| u32 mask; | |
| if (ingress) | |
| mask = YT921X_MIRROR_IGR_PORTn(port); | |
| else | |
| mask = YT921X_MIRROR_EGR_PORTn(port); | |
| return yt921x_reg_clear_bits(priv, YT921X_MIRROR, mask); | |
| } | |
| static int | |
| yt921x_mirror_add(struct yt921x_priv *priv, int port, bool ingress, | |
| int to_local_port, struct netlink_ext_ack *extack) | |
| { | |
| u32 srcs; | |
| u32 ctrl; | |
| u32 val; | |
| u32 dst; | |
| int res; | |
| if (ingress) | |
| srcs = YT921X_MIRROR_IGR_PORTn(port); | |
| else | |
| srcs = YT921X_MIRROR_EGR_PORTn(port); | |
| dst = YT921X_MIRROR_PORT(to_local_port); | |
| res = yt921x_reg_read(priv, YT921X_MIRROR, &val); | |
| if (res) | |
| return res; | |
| /* other mirror tasks & different dst port -> conflict */ | |
| if ((val & ~srcs & (YT921X_MIRROR_EGR_PORTS_M | | |
| YT921X_MIRROR_IGR_PORTS_M)) && | |
| (val & YT921X_MIRROR_PORT_M) != dst) { | |
| NL_SET_ERR_MSG_MOD(extack, | |
| "Sniffer port is already configured, delete existing rules & retry"); | |
| return -EBUSY; | |
| } | |
| ctrl = val & ~YT921X_MIRROR_PORT_M; | |
| ctrl |= srcs; | |
| ctrl |= dst; | |
| if (ctrl == val) | |
| return 0; | |
| return yt921x_reg_write(priv, YT921X_MIRROR, ctrl); | |
| } | |
| static void | |
| yt921x_dsa_port_mirror_del(struct dsa_switch *ds, int port, | |
| struct dsa_mall_mirror_tc_entry *mirror) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| struct device *dev = to_device(priv); | |
| int res; | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_mirror_del(priv, port, mirror->ingress); | |
| mutex_unlock(&priv->reg_lock); | |
| if (res) | |
| dev_err(dev, "Failed to %s port %d: %i\n", "unmirror", | |
| port, res); | |
| } | |
| static int | |
| yt921x_dsa_port_mirror_add(struct dsa_switch *ds, int port, | |
| struct dsa_mall_mirror_tc_entry *mirror, | |
| bool ingress, struct netlink_ext_ack *extack) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| int res; | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_mirror_add(priv, port, ingress, | |
| mirror->to_local_port, extack); | |
| mutex_unlock(&priv->reg_lock); | |
| return res; | |
| } | |
| static int yt921x_lag_hash(struct yt921x_priv *priv, u32 ctrl, bool unique_lag, | |
| struct netlink_ext_ack *extack) | |
| { | |
| u32 val; | |
| int res; | |
| /* Hash Mode is global. Make sure the same Hash Mode is set to all the | |
| * 2 possible lags. | |
| * If we are the unique LAG we can set whatever hash mode we want. | |
| * To change hash mode it's needed to remove all LAG and change the mode | |
| * with the latest. | |
| */ | |
| if (unique_lag) { | |
| res = yt921x_reg_write(priv, YT921X_LAG_HASH, ctrl); | |
| if (res) | |
| return res; | |
| } else { | |
| res = yt921x_reg_read(priv, YT921X_LAG_HASH, &val); | |
| if (res) | |
| return res; | |
| if (val != ctrl) { | |
| NL_SET_ERR_MSG_MOD(extack, | |
| "Mismatched Hash Mode across different lags is not supported"); | |
| return -EOPNOTSUPP; | |
| } | |
| } | |
| return 0; | |
| } | |
| static int yt921x_lag_set(struct yt921x_priv *priv, u8 index, u16 ports_mask) | |
| { | |
| unsigned long targets_mask = ports_mask; | |
| unsigned int cnt; | |
| u32 ctrl; | |
| int port; | |
| int res; | |
| cnt = 0; | |
| for_each_set_bit(port, &targets_mask, YT921X_PORT_NUM) { | |
| ctrl = YT921X_LAG_MEMBER_PORT(port); | |
| res = yt921x_reg_write(priv, YT921X_LAG_MEMBERnm(index, cnt), | |
| ctrl); | |
| if (res) | |
| return res; | |
| cnt++; | |
| } | |
| ctrl = YT921X_LAG_GROUP_PORTS(ports_mask) | | |
| YT921X_LAG_GROUP_MEMBER_NUM(cnt); | |
| return yt921x_reg_write(priv, YT921X_LAG_GROUPn(index), ctrl); | |
| } | |
| static int | |
| yt921x_dsa_port_lag_leave(struct dsa_switch *ds, int port, struct dsa_lag lag) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| struct dsa_port *dp; | |
| u32 ctrl; | |
| int res; | |
| if (!lag.id) | |
| return -EINVAL; | |
| ctrl = 0; | |
| dsa_lag_foreach_port(dp, ds->dst, &lag) | |
| ctrl |= BIT(dp->index); | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_lag_set(priv, lag.id - 1, ctrl); | |
| mutex_unlock(&priv->reg_lock); | |
| return res; | |
| } | |
| static int | |
| yt921x_dsa_port_lag_check(struct dsa_switch *ds, struct dsa_lag lag, | |
| struct netdev_lag_upper_info *info, | |
| struct netlink_ext_ack *extack) | |
| { | |
| unsigned int members; | |
| struct dsa_port *dp; | |
| if (!lag.id) | |
| return -EINVAL; | |
| members = 0; | |
| dsa_lag_foreach_port(dp, ds->dst, &lag) | |
| /* Includes the port joining the LAG */ | |
| members++; | |
| if (members > YT921X_LAG_PORT_NUM) { | |
| NL_SET_ERR_MSG_MOD(extack, | |
| "Cannot offload more than 4 LAG ports"); | |
| return -EOPNOTSUPP; | |
| } | |
| if (info->tx_type != NETDEV_LAG_TX_TYPE_HASH) { | |
| NL_SET_ERR_MSG_MOD(extack, | |
| "Can only offload LAG using hash TX type"); | |
| return -EOPNOTSUPP; | |
| } | |
| if (info->hash_type != NETDEV_LAG_HASH_L2 && | |
| info->hash_type != NETDEV_LAG_HASH_L23 && | |
| info->hash_type != NETDEV_LAG_HASH_L34) { | |
| NL_SET_ERR_MSG_MOD(extack, | |
| "Can only offload L2 or L2+L3 or L3+L4 TX hash"); | |
| return -EOPNOTSUPP; | |
| } | |
| return 0; | |
| } | |
| static int | |
| yt921x_dsa_port_lag_join(struct dsa_switch *ds, int port, struct dsa_lag lag, | |
| struct netdev_lag_upper_info *info, | |
| struct netlink_ext_ack *extack) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| struct dsa_port *dp; | |
| bool unique_lag; | |
| unsigned int i; | |
| u32 ctrl; | |
| int res; | |
| res = yt921x_dsa_port_lag_check(ds, lag, info, extack); | |
| if (res) | |
| return res; | |
| ctrl = 0; | |
| switch (info->hash_type) { | |
| case NETDEV_LAG_HASH_L34: | |
| ctrl |= YT921X_LAG_HASH_IP_DST; | |
| ctrl |= YT921X_LAG_HASH_IP_SRC; | |
| ctrl |= YT921X_LAG_HASH_IP_PROTO; | |
| ctrl |= YT921X_LAG_HASH_L4_DPORT; | |
| ctrl |= YT921X_LAG_HASH_L4_SPORT; | |
| break; | |
| case NETDEV_LAG_HASH_L23: | |
| ctrl |= YT921X_LAG_HASH_MAC_DA; | |
| ctrl |= YT921X_LAG_HASH_MAC_SA; | |
| ctrl |= YT921X_LAG_HASH_IP_DST; | |
| ctrl |= YT921X_LAG_HASH_IP_SRC; | |
| ctrl |= YT921X_LAG_HASH_IP_PROTO; | |
| break; | |
| case NETDEV_LAG_HASH_L2: | |
| ctrl |= YT921X_LAG_HASH_MAC_DA; | |
| ctrl |= YT921X_LAG_HASH_MAC_SA; | |
| break; | |
| default: | |
| return -EOPNOTSUPP; | |
| } | |
| /* Check if we are the unique configured LAG */ | |
| unique_lag = true; | |
| dsa_lags_foreach_id(i, ds->dst) | |
| if (i != lag.id && dsa_lag_by_id(ds->dst, i)) { | |
| unique_lag = false; | |
| break; | |
| } | |
| mutex_lock(&priv->reg_lock); | |
| do { | |
| res = yt921x_lag_hash(priv, ctrl, unique_lag, extack); | |
| if (res) | |
| break; | |
| ctrl = 0; | |
| dsa_lag_foreach_port(dp, ds->dst, &lag) | |
| ctrl |= BIT(dp->index); | |
| res = yt921x_lag_set(priv, lag.id - 1, ctrl); | |
| } while (0); | |
| mutex_unlock(&priv->reg_lock); | |
| return res; | |
| } | |
| static int yt921x_fdb_wait(struct yt921x_priv *priv, u32 *valp) | |
| { | |
| struct device *dev = to_device(priv); | |
| u32 val = YT921X_FDB_RESULT_DONE; | |
| int res; | |
| res = yt921x_reg_wait(priv, YT921X_FDB_RESULT, YT921X_FDB_RESULT_DONE, | |
| &val); | |
| if (res) { | |
| dev_err(dev, "FDB probably stuck\n"); | |
| return res; | |
| } | |
| *valp = val; | |
| return 0; | |
| } | |
| static int | |
| yt921x_fdb_in01(struct yt921x_priv *priv, const unsigned char *addr, | |
| u16 vid, u32 ctrl1) | |
| { | |
| u32 ctrl; | |
| int res; | |
| ctrl = ethaddr_hi4_to_u32(addr); | |
| res = yt921x_reg_write(priv, YT921X_FDB_IN0, ctrl); | |
| if (res) | |
| return res; | |
| ctrl = ctrl1 | YT921X_FDB_IO1_FID(vid) | ethaddr_lo2_to_u32(addr); | |
| return yt921x_reg_write(priv, YT921X_FDB_IN1, ctrl); | |
| } | |
| static int | |
| yt921x_fdb_has(struct yt921x_priv *priv, const unsigned char *addr, u16 vid, | |
| u16 *indexp) | |
| { | |
| u32 ctrl; | |
| u32 val; | |
| int res; | |
| res = yt921x_fdb_in01(priv, addr, vid, 0); | |
| if (res) | |
| return res; | |
| ctrl = 0; | |
| res = yt921x_reg_write(priv, YT921X_FDB_IN2, ctrl); | |
| if (res) | |
| return res; | |
| ctrl = YT921X_FDB_OP_OP_GET_ONE | YT921X_FDB_OP_START; | |
| res = yt921x_reg_write(priv, YT921X_FDB_OP, ctrl); | |
| if (res) | |
| return res; | |
| res = yt921x_fdb_wait(priv, &val); | |
| if (res) | |
| return res; | |
| if (val & YT921X_FDB_RESULT_NOTFOUND) { | |
| *indexp = YT921X_FDB_NUM; | |
| return 0; | |
| } | |
| *indexp = FIELD_GET(YT921X_FDB_RESULT_INDEX_M, val); | |
| return 0; | |
| } | |
| static int | |
| yt921x_fdb_read(struct yt921x_priv *priv, unsigned char *addr, u16 *vidp, | |
| u16 *ports_maskp, u16 *indexp, u8 *statusp) | |
| { | |
| struct device *dev = to_device(priv); | |
| u16 index; | |
| u32 data0; | |
| u32 data1; | |
| u32 data2; | |
| u32 val; | |
| int res; | |
| res = yt921x_fdb_wait(priv, &val); | |
| if (res) | |
| return res; | |
| if (val & YT921X_FDB_RESULT_NOTFOUND) { | |
| *ports_maskp = 0; | |
| return 0; | |
| } | |
| index = FIELD_GET(YT921X_FDB_RESULT_INDEX_M, val); | |
| res = yt921x_reg_read(priv, YT921X_FDB_OUT1, &data1); | |
| if (res) | |
| return res; | |
| if ((data1 & YT921X_FDB_IO1_STATUS_M) == | |
| YT921X_FDB_IO1_STATUS_INVALID) { | |
| *ports_maskp = 0; | |
| return 0; | |
| } | |
| res = yt921x_reg_read(priv, YT921X_FDB_OUT0, &data0); | |
| if (res) | |
| return res; | |
| res = yt921x_reg_read(priv, YT921X_FDB_OUT2, &data2); | |
| if (res) | |
| return res; | |
| addr[0] = data0 >> 24; | |
| addr[1] = data0 >> 16; | |
| addr[2] = data0 >> 8; | |
| addr[3] = data0; | |
| addr[4] = data1 >> 8; | |
| addr[5] = data1; | |
| *vidp = FIELD_GET(YT921X_FDB_IO1_FID_M, data1); | |
| *indexp = index; | |
| *ports_maskp = FIELD_GET(YT921X_FDB_IO2_EGR_PORTS_M, data2); | |
| *statusp = FIELD_GET(YT921X_FDB_IO1_STATUS_M, data1); | |
| dev_dbg(dev, | |
| "%s: index 0x%x, mac %02x:%02x:%02x:%02x:%02x:%02x, vid %d, ports 0x%x, status %d\n", | |
| __func__, *indexp, addr[0], addr[1], addr[2], addr[3], | |
| addr[4], addr[5], *vidp, *ports_maskp, *statusp); | |
| return 0; | |
| } | |
| static int | |
| yt921x_fdb_dump(struct yt921x_priv *priv, u16 ports_mask, | |
| dsa_fdb_dump_cb_t *cb, void *data) | |
| { | |
| unsigned char addr[ETH_ALEN]; | |
| u8 status; | |
| u16 pmask; | |
| u16 index; | |
| u32 ctrl; | |
| u16 vid; | |
| int res; | |
| ctrl = YT921X_FDB_OP_INDEX(0) | YT921X_FDB_OP_MODE_INDEX | | |
| YT921X_FDB_OP_OP_GET_ONE | YT921X_FDB_OP_START; | |
| res = yt921x_reg_write(priv, YT921X_FDB_OP, ctrl); | |
| if (res) | |
| return res; | |
| res = yt921x_fdb_read(priv, addr, &vid, &pmask, &index, &status); | |
| if (res) | |
| return res; | |
| if ((pmask & ports_mask) && !is_multicast_ether_addr(addr)) { | |
| res = cb(addr, vid, | |
| status == YT921X_FDB_ENTRY_STATUS_STATIC, data); | |
| if (res) | |
| return res; | |
| } | |
| ctrl = YT921X_FDB_IO2_EGR_PORTS(ports_mask); | |
| res = yt921x_reg_write(priv, YT921X_FDB_IN2, ctrl); | |
| if (res) | |
| return res; | |
| index = 0; | |
| do { | |
| ctrl = YT921X_FDB_OP_INDEX(index) | YT921X_FDB_OP_MODE_INDEX | | |
| YT921X_FDB_OP_NEXT_TYPE_UCAST_PORT | | |
| YT921X_FDB_OP_OP_GET_NEXT | YT921X_FDB_OP_START; | |
| res = yt921x_reg_write(priv, YT921X_FDB_OP, ctrl); | |
| if (res) | |
| return res; | |
| res = yt921x_fdb_read(priv, addr, &vid, &pmask, &index, | |
| &status); | |
| if (res) | |
| return res; | |
| if (!pmask) | |
| break; | |
| if ((pmask & ports_mask) && !is_multicast_ether_addr(addr)) { | |
| res = cb(addr, vid, | |
| status == YT921X_FDB_ENTRY_STATUS_STATIC, | |
| data); | |
| if (res) | |
| return res; | |
| } | |
| /* Never call GET_NEXT with 4095, otherwise it will hang | |
| * forever until a reset! | |
| */ | |
| } while (index < YT921X_FDB_NUM - 1); | |
| return 0; | |
| } | |
| static int | |
| yt921x_fdb_flush_raw(struct yt921x_priv *priv, u16 ports_mask, u16 vid, | |
| bool flush_static) | |
| { | |
| u32 ctrl; | |
| u32 val; | |
| int res; | |
| if (vid < 4096) { | |
| ctrl = YT921X_FDB_IO1_FID(vid); | |
| res = yt921x_reg_write(priv, YT921X_FDB_IN1, ctrl); | |
| if (res) | |
| return res; | |
| } | |
| ctrl = YT921X_FDB_IO2_EGR_PORTS(ports_mask); | |
| res = yt921x_reg_write(priv, YT921X_FDB_IN2, ctrl); | |
| if (res) | |
| return res; | |
| ctrl = YT921X_FDB_OP_OP_FLUSH | YT921X_FDB_OP_START; | |
| if (vid >= 4096) | |
| ctrl |= YT921X_FDB_OP_FLUSH_PORT; | |
| else | |
| ctrl |= YT921X_FDB_OP_FLUSH_PORT_VID; | |
| if (flush_static) | |
| ctrl |= YT921X_FDB_OP_FLUSH_STATIC; | |
| res = yt921x_reg_write(priv, YT921X_FDB_OP, ctrl); | |
| if (res) | |
| return res; | |
| res = yt921x_fdb_wait(priv, &val); | |
| if (res) | |
| return res; | |
| return 0; | |
| } | |
| static int | |
| yt921x_fdb_flush_port(struct yt921x_priv *priv, int port, bool flush_static) | |
| { | |
| return yt921x_fdb_flush_raw(priv, BIT(port), 4096, flush_static); | |
| } | |
| static int | |
| yt921x_fdb_add_index_in12(struct yt921x_priv *priv, u16 index, u16 ctrl1, | |
| u16 ctrl2) | |
| { | |
| u32 ctrl; | |
| u32 val; | |
| int res; | |
| res = yt921x_reg_write(priv, YT921X_FDB_IN1, ctrl1); | |
| if (res) | |
| return res; | |
| res = yt921x_reg_write(priv, YT921X_FDB_IN2, ctrl2); | |
| if (res) | |
| return res; | |
| ctrl = YT921X_FDB_OP_INDEX(index) | YT921X_FDB_OP_MODE_INDEX | | |
| YT921X_FDB_OP_OP_ADD | YT921X_FDB_OP_START; | |
| res = yt921x_reg_write(priv, YT921X_FDB_OP, ctrl); | |
| if (res) | |
| return res; | |
| return yt921x_fdb_wait(priv, &val); | |
| } | |
| static int | |
| yt921x_fdb_add(struct yt921x_priv *priv, const unsigned char *addr, u16 vid, | |
| u16 ports_mask) | |
| { | |
| u32 ctrl; | |
| u32 val; | |
| int res; | |
| ctrl = YT921X_FDB_IO1_STATUS_STATIC; | |
| res = yt921x_fdb_in01(priv, addr, vid, ctrl); | |
| if (res) | |
| return res; | |
| ctrl = YT921X_FDB_IO2_EGR_PORTS(ports_mask); | |
| res = yt921x_reg_write(priv, YT921X_FDB_IN2, ctrl); | |
| if (res) | |
| return res; | |
| ctrl = YT921X_FDB_OP_OP_ADD | YT921X_FDB_OP_START; | |
| res = yt921x_reg_write(priv, YT921X_FDB_OP, ctrl); | |
| if (res) | |
| return res; | |
| return yt921x_fdb_wait(priv, &val); | |
| } | |
| static int | |
| yt921x_fdb_leave(struct yt921x_priv *priv, const unsigned char *addr, | |
| u16 vid, u16 ports_mask) | |
| { | |
| u16 index; | |
| u32 ctrl1; | |
| u32 ctrl2; | |
| u32 ctrl; | |
| u32 val2; | |
| u32 val; | |
| int res; | |
| /* Check for presence */ | |
| res = yt921x_fdb_has(priv, addr, vid, &index); | |
| if (res) | |
| return res; | |
| if (index >= YT921X_FDB_NUM) | |
| return 0; | |
| /* Check if action required */ | |
| res = yt921x_reg_read(priv, YT921X_FDB_OUT2, &val2); | |
| if (res) | |
| return res; | |
| ctrl2 = val2 & ~YT921X_FDB_IO2_EGR_PORTS(ports_mask); | |
| if (ctrl2 == val2) | |
| return 0; | |
| if (!(ctrl2 & YT921X_FDB_IO2_EGR_PORTS_M)) { | |
| ctrl = YT921X_FDB_OP_OP_DEL | YT921X_FDB_OP_START; | |
| res = yt921x_reg_write(priv, YT921X_FDB_OP, ctrl); | |
| if (res) | |
| return res; | |
| return yt921x_fdb_wait(priv, &val); | |
| } | |
| res = yt921x_reg_read(priv, YT921X_FDB_OUT1, &ctrl1); | |
| if (res) | |
| return res; | |
| return yt921x_fdb_add_index_in12(priv, index, ctrl1, ctrl2); | |
| } | |
| static int | |
| yt921x_fdb_join(struct yt921x_priv *priv, const unsigned char *addr, u16 vid, | |
| u16 ports_mask) | |
| { | |
| u16 index; | |
| u32 ctrl1; | |
| u32 ctrl2; | |
| u32 val1; | |
| u32 val2; | |
| int res; | |
| /* Check for presence */ | |
| res = yt921x_fdb_has(priv, addr, vid, &index); | |
| if (res) | |
| return res; | |
| if (index >= YT921X_FDB_NUM) | |
| return yt921x_fdb_add(priv, addr, vid, ports_mask); | |
| /* Check if action required */ | |
| res = yt921x_reg_read(priv, YT921X_FDB_OUT1, &val1); | |
| if (res) | |
| return res; | |
| res = yt921x_reg_read(priv, YT921X_FDB_OUT2, &val2); | |
| if (res) | |
| return res; | |
| ctrl1 = val1 & ~YT921X_FDB_IO1_STATUS_M; | |
| ctrl1 |= YT921X_FDB_IO1_STATUS_STATIC; | |
| ctrl2 = val2 | YT921X_FDB_IO2_EGR_PORTS(ports_mask); | |
| if (ctrl1 == val1 && ctrl2 == val2) | |
| return 0; | |
| return yt921x_fdb_add_index_in12(priv, index, ctrl1, ctrl2); | |
| } | |
| static int | |
| yt921x_dsa_port_fdb_dump(struct dsa_switch *ds, int port, | |
| dsa_fdb_dump_cb_t *cb, void *data) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| int res; | |
| mutex_lock(&priv->reg_lock); | |
| /* Hardware FDB is shared for fdb and mdb, "bridge fdb show" | |
| * only wants to see unicast | |
| */ | |
| res = yt921x_fdb_dump(priv, BIT(port), cb, data); | |
| mutex_unlock(&priv->reg_lock); | |
| return res; | |
| } | |
| static void yt921x_dsa_port_fast_age(struct dsa_switch *ds, int port) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| struct device *dev = to_device(priv); | |
| int res; | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_fdb_flush_port(priv, port, false); | |
| mutex_unlock(&priv->reg_lock); | |
| if (res) | |
| dev_err(dev, "Failed to %s port %d: %i\n", "clear FDB for", | |
| port, res); | |
| } | |
| static int | |
| yt921x_dsa_set_ageing_time(struct dsa_switch *ds, unsigned int msecs) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| u32 ctrl; | |
| int res; | |
| /* AGEING reg is set in 5s step */ | |
| ctrl = clamp(msecs / 5000, 1, U16_MAX); | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_reg_write(priv, YT921X_AGEING, ctrl); | |
| mutex_unlock(&priv->reg_lock); | |
| return res; | |
| } | |
| static int | |
| yt921x_dsa_port_fdb_del(struct dsa_switch *ds, int port, | |
| const unsigned char *addr, u16 vid, struct dsa_db db) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| int res; | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_fdb_leave(priv, addr, vid, BIT(port)); | |
| mutex_unlock(&priv->reg_lock); | |
| return res; | |
| } | |
| static int | |
| yt921x_dsa_port_fdb_add(struct dsa_switch *ds, int port, | |
| const unsigned char *addr, u16 vid, struct dsa_db db) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| int res; | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_fdb_join(priv, addr, vid, BIT(port)); | |
| mutex_unlock(&priv->reg_lock); | |
| return res; | |
| } | |
| static int | |
| yt921x_dsa_port_mdb_del(struct dsa_switch *ds, int port, | |
| const struct switchdev_obj_port_mdb *mdb, | |
| struct dsa_db db) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| const unsigned char *addr = mdb->addr; | |
| u16 vid = mdb->vid; | |
| int res; | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_fdb_leave(priv, addr, vid, BIT(port)); | |
| mutex_unlock(&priv->reg_lock); | |
| return res; | |
| } | |
| static int | |
| yt921x_dsa_port_mdb_add(struct dsa_switch *ds, int port, | |
| const struct switchdev_obj_port_mdb *mdb, | |
| struct dsa_db db) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| const unsigned char *addr = mdb->addr; | |
| u16 vid = mdb->vid; | |
| int res; | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_fdb_join(priv, addr, vid, BIT(port)); | |
| mutex_unlock(&priv->reg_lock); | |
| return res; | |
| } | |
| static int | |
| yt921x_vlan_aware_set(struct yt921x_priv *priv, int port, bool vlan_aware) | |
| { | |
| u32 ctrl; | |
| /* Abuse SVLAN for PCP parsing without polluting the FDB - it just works | |
| * despite YT921X_VLAN_CTRL_SVLAN_EN never being set | |
| */ | |
| if (!vlan_aware) | |
| ctrl = YT921X_PORT_IGR_TPIDn_STAG(0); | |
| else | |
| ctrl = YT921X_PORT_IGR_TPIDn_CTAG(0); | |
| return yt921x_reg_write(priv, YT921X_PORTn_IGR_TPID(port), ctrl); | |
| } | |
| static int | |
| yt921x_port_set_pvid(struct yt921x_priv *priv, int port, u16 vid) | |
| { | |
| u32 mask; | |
| u32 ctrl; | |
| mask = YT921X_PORT_VLAN_CTRL_CVID_M; | |
| ctrl = YT921X_PORT_VLAN_CTRL_CVID(vid); | |
| return yt921x_reg_update_bits(priv, YT921X_PORTn_VLAN_CTRL(port), | |
| mask, ctrl); | |
| } | |
| static int | |
| yt921x_vlan_filtering(struct yt921x_priv *priv, int port, bool vlan_filtering) | |
| { | |
| struct dsa_port *dp = dsa_to_port(&priv->ds, port); | |
| struct net_device *bdev; | |
| u16 pvid; | |
| u32 mask; | |
| u32 ctrl; | |
| int res; | |
| bdev = dsa_port_bridge_dev_get(dp); | |
| if (!bdev || !vlan_filtering) | |
| pvid = YT921X_VID_UNWARE; | |
| else | |
| br_vlan_get_pvid(bdev, &pvid); | |
| res = yt921x_port_set_pvid(priv, port, pvid); | |
| if (res) | |
| return res; | |
| mask = YT921X_PORT_VLAN_CTRL1_CVLAN_DROP_TAGGED | | |
| YT921X_PORT_VLAN_CTRL1_CVLAN_DROP_UNTAGGED; | |
| ctrl = 0; | |
| /* Do not drop tagged frames here; let VLAN_IGR_FILTER do it */ | |
| if (vlan_filtering && !pvid) | |
| ctrl |= YT921X_PORT_VLAN_CTRL1_CVLAN_DROP_UNTAGGED; | |
| res = yt921x_reg_update_bits(priv, YT921X_PORTn_VLAN_CTRL1(port), | |
| mask, ctrl); | |
| if (res) | |
| return res; | |
| res = yt921x_reg_toggle_bits(priv, YT921X_VLAN_IGR_FILTER, | |
| YT921X_VLAN_IGR_FILTER_PORTn(port), | |
| vlan_filtering); | |
| if (res) | |
| return res; | |
| res = yt921x_vlan_aware_set(priv, port, vlan_filtering); | |
| if (res) | |
| return res; | |
| return 0; | |
| } | |
| static int yt921x_vlan_del(struct yt921x_priv *priv, int port, u16 vid) | |
| { | |
| u32 masks[2]; | |
| masks[0] = YT921X_VLAN_CTRLa_PORTn(port); | |
| masks[1] = YT921X_VLAN_CTRLb_UNTAG_PORTn(port); | |
| return yt921x_reg64_clear_bits(priv, YT921X_VLANn_CTRL(vid), masks); | |
| } | |
| static int | |
| yt921x_vlan_add(struct yt921x_priv *priv, int port, u16 vid, bool untagged) | |
| { | |
| u32 masks[2]; | |
| u32 ctrls[2]; | |
| masks[0] = YT921X_VLAN_CTRLa_PORTn(port) | | |
| YT921X_VLAN_CTRLa_PORTS(priv->cpu_ports_mask); | |
| ctrls[0] = masks[0]; | |
| masks[1] = YT921X_VLAN_CTRLb_UNTAG_PORTn(port); | |
| ctrls[1] = untagged ? masks[1] : 0; | |
| return yt921x_reg64_update_bits(priv, YT921X_VLANn_CTRL(vid), | |
| masks, ctrls); | |
| } | |
| static int | |
| yt921x_pvid_clear(struct yt921x_priv *priv, int port) | |
| { | |
| struct dsa_port *dp = dsa_to_port(&priv->ds, port); | |
| bool vlan_filtering; | |
| u32 mask; | |
| int res; | |
| vlan_filtering = dsa_port_is_vlan_filtering(dp); | |
| res = yt921x_port_set_pvid(priv, port, | |
| vlan_filtering ? 0 : YT921X_VID_UNWARE); | |
| if (res) | |
| return res; | |
| if (vlan_filtering) { | |
| mask = YT921X_PORT_VLAN_CTRL1_CVLAN_DROP_UNTAGGED; | |
| res = yt921x_reg_set_bits(priv, YT921X_PORTn_VLAN_CTRL1(port), | |
| mask); | |
| if (res) | |
| return res; | |
| } | |
| return 0; | |
| } | |
| static int | |
| yt921x_pvid_set(struct yt921x_priv *priv, int port, u16 vid) | |
| { | |
| struct dsa_port *dp = dsa_to_port(&priv->ds, port); | |
| bool vlan_filtering; | |
| u32 mask; | |
| int res; | |
| vlan_filtering = dsa_port_is_vlan_filtering(dp); | |
| if (vlan_filtering) { | |
| res = yt921x_port_set_pvid(priv, port, vid); | |
| if (res) | |
| return res; | |
| } | |
| mask = YT921X_PORT_VLAN_CTRL1_CVLAN_DROP_UNTAGGED; | |
| res = yt921x_reg_clear_bits(priv, YT921X_PORTn_VLAN_CTRL1(port), mask); | |
| if (res) | |
| return res; | |
| return 0; | |
| } | |
| static int | |
| yt921x_dsa_port_vlan_filtering(struct dsa_switch *ds, int port, | |
| bool vlan_filtering, | |
| struct netlink_ext_ack *extack) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| int res; | |
| if (dsa_is_cpu_port(ds, port)) | |
| return 0; | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_vlan_filtering(priv, port, vlan_filtering); | |
| mutex_unlock(&priv->reg_lock); | |
| return res; | |
| } | |
| static int | |
| yt921x_dsa_port_vlan_del(struct dsa_switch *ds, int port, | |
| const struct switchdev_obj_port_vlan *vlan) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| u16 vid = vlan->vid; | |
| u16 pvid; | |
| int res; | |
| if (dsa_is_cpu_port(ds, port)) | |
| return 0; | |
| mutex_lock(&priv->reg_lock); | |
| do { | |
| struct dsa_port *dp = dsa_to_port(ds, port); | |
| struct net_device *bdev; | |
| res = yt921x_vlan_del(priv, port, vid); | |
| if (res) | |
| break; | |
| bdev = dsa_port_bridge_dev_get(dp); | |
| if (bdev) { | |
| br_vlan_get_pvid(bdev, &pvid); | |
| if (pvid == vid) | |
| res = yt921x_pvid_clear(priv, port); | |
| } | |
| } while (0); | |
| mutex_unlock(&priv->reg_lock); | |
| return res; | |
| } | |
| static int | |
| yt921x_dsa_port_vlan_add(struct dsa_switch *ds, int port, | |
| const struct switchdev_obj_port_vlan *vlan, | |
| struct netlink_ext_ack *extack) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| u16 vid = vlan->vid; | |
| u16 pvid; | |
| int res; | |
| /* CPU port is supposed to be a member of every VLAN; see | |
| * yt921x_vlan_add() and yt921x_port_setup() | |
| */ | |
| if (dsa_is_cpu_port(ds, port)) | |
| return 0; | |
| mutex_lock(&priv->reg_lock); | |
| do { | |
| struct dsa_port *dp = dsa_to_port(ds, port); | |
| struct net_device *bdev; | |
| res = yt921x_vlan_add(priv, port, vid, | |
| vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED); | |
| if (res) | |
| break; | |
| bdev = dsa_port_bridge_dev_get(dp); | |
| if (bdev) { | |
| if (vlan->flags & BRIDGE_VLAN_INFO_PVID) { | |
| res = yt921x_pvid_set(priv, port, vid); | |
| } else { | |
| br_vlan_get_pvid(bdev, &pvid); | |
| if (pvid == vid) | |
| res = yt921x_pvid_clear(priv, port); | |
| } | |
| } | |
| } while (0); | |
| mutex_unlock(&priv->reg_lock); | |
| return res; | |
| } | |
| static int yt921x_userport_standalone(struct yt921x_priv *priv, int port) | |
| { | |
| u32 mask; | |
| u32 ctrl; | |
| int res; | |
| ctrl = ~priv->cpu_ports_mask; | |
| res = yt921x_reg_write(priv, YT921X_PORTn_ISOLATION(port), ctrl); | |
| if (res) | |
| return res; | |
| /* Turn off FDB learning to prevent FDB pollution */ | |
| mask = YT921X_PORT_LEARN_DIS; | |
| res = yt921x_reg_set_bits(priv, YT921X_PORTn_LEARN(port), mask); | |
| if (res) | |
| return res; | |
| /* Turn off VLAN awareness */ | |
| res = yt921x_vlan_aware_set(priv, port, false); | |
| if (res) | |
| return res; | |
| /* Unrelated since learning is off and all packets are trapped; | |
| * set it anyway | |
| */ | |
| res = yt921x_port_set_pvid(priv, port, YT921X_VID_UNWARE); | |
| if (res) | |
| return res; | |
| return 0; | |
| } | |
| static int yt921x_userport_bridge(struct yt921x_priv *priv, int port) | |
| { | |
| u32 mask; | |
| int res; | |
| mask = YT921X_PORT_LEARN_DIS; | |
| res = yt921x_reg_clear_bits(priv, YT921X_PORTn_LEARN(port), mask); | |
| if (res) | |
| return res; | |
| return 0; | |
| } | |
| static int yt921x_isolate(struct yt921x_priv *priv, int port) | |
| { | |
| u32 mask; | |
| int res; | |
| mask = BIT(port); | |
| for (int i = 0; i < YT921X_PORT_NUM; i++) { | |
| if ((BIT(i) & priv->cpu_ports_mask) || i == port) | |
| continue; | |
| res = yt921x_reg_set_bits(priv, YT921X_PORTn_ISOLATION(i), | |
| mask); | |
| if (res) | |
| return res; | |
| } | |
| return 0; | |
| } | |
| /* Make sure to include the CPU port in ports_mask, or your bridge will | |
| * not have it. | |
| */ | |
| static int yt921x_bridge(struct yt921x_priv *priv, u16 ports_mask) | |
| { | |
| unsigned long targets_mask = ports_mask & ~priv->cpu_ports_mask; | |
| u32 isolated_mask; | |
| u32 ctrl; | |
| int port; | |
| int res; | |
| isolated_mask = 0; | |
| for_each_set_bit(port, &targets_mask, YT921X_PORT_NUM) { | |
| struct yt921x_port *pp = &priv->ports[port]; | |
| if (pp->isolated) | |
| isolated_mask |= BIT(port); | |
| } | |
| /* Block from non-cpu bridge ports ... */ | |
| for_each_set_bit(port, &targets_mask, YT921X_PORT_NUM) { | |
| struct yt921x_port *pp = &priv->ports[port]; | |
| /* to non-bridge ports */ | |
| ctrl = ~ports_mask; | |
| /* to isolated ports when isolated */ | |
| if (pp->isolated) | |
| ctrl |= isolated_mask; | |
| /* to itself when non-hairpin */ | |
| if (!pp->hairpin) | |
| ctrl |= BIT(port); | |
| else | |
| ctrl &= ~BIT(port); | |
| res = yt921x_reg_write(priv, YT921X_PORTn_ISOLATION(port), | |
| ctrl); | |
| if (res) | |
| return res; | |
| } | |
| return 0; | |
| } | |
| static int yt921x_bridge_leave(struct yt921x_priv *priv, int port) | |
| { | |
| int res; | |
| res = yt921x_userport_standalone(priv, port); | |
| if (res) | |
| return res; | |
| res = yt921x_isolate(priv, port); | |
| if (res) | |
| return res; | |
| return 0; | |
| } | |
| static int | |
| yt921x_bridge_join(struct yt921x_priv *priv, int port, u16 ports_mask) | |
| { | |
| int res; | |
| res = yt921x_userport_bridge(priv, port); | |
| if (res) | |
| return res; | |
| res = yt921x_bridge(priv, ports_mask); | |
| if (res) | |
| return res; | |
| return 0; | |
| } | |
| static int | |
| yt921x_bridge_flags(struct yt921x_priv *priv, int port, | |
| struct switchdev_brport_flags flags) | |
| { | |
| struct yt921x_port *pp = &priv->ports[port]; | |
| bool do_flush; | |
| u32 mask; | |
| int res; | |
| if (flags.mask & BR_LEARNING) { | |
| bool learning = flags.val & BR_LEARNING; | |
| mask = YT921X_PORT_LEARN_DIS; | |
| res = yt921x_reg_toggle_bits(priv, YT921X_PORTn_LEARN(port), | |
| mask, !learning); | |
| if (res) | |
| return res; | |
| } | |
| /* BR_FLOOD, BR_MCAST_FLOOD: see the comment where ACT_UNK_ACTn_TRAP | |
| * is set | |
| */ | |
| /* BR_BCAST_FLOOD: we can filter bcast, but cannot trap them */ | |
| do_flush = false; | |
| if (flags.mask & BR_HAIRPIN_MODE) { | |
| pp->hairpin = flags.val & BR_HAIRPIN_MODE; | |
| do_flush = true; | |
| } | |
| if (flags.mask & BR_ISOLATED) { | |
| pp->isolated = flags.val & BR_ISOLATED; | |
| do_flush = true; | |
| } | |
| if (do_flush) { | |
| struct dsa_switch *ds = &priv->ds; | |
| struct dsa_port *dp = dsa_to_port(ds, port); | |
| struct net_device *bdev; | |
| bdev = dsa_port_bridge_dev_get(dp); | |
| if (bdev) { | |
| u32 ports_mask; | |
| ports_mask = dsa_bridge_ports(ds, bdev); | |
| ports_mask |= priv->cpu_ports_mask; | |
| res = yt921x_bridge(priv, ports_mask); | |
| if (res) | |
| return res; | |
| } | |
| } | |
| return 0; | |
| } | |
| static int | |
| yt921x_dsa_port_pre_bridge_flags(struct dsa_switch *ds, int port, | |
| struct switchdev_brport_flags flags, | |
| struct netlink_ext_ack *extack) | |
| { | |
| if (flags.mask & ~(BR_HAIRPIN_MODE | BR_LEARNING | BR_FLOOD | | |
| BR_MCAST_FLOOD | BR_ISOLATED)) | |
| return -EINVAL; | |
| return 0; | |
| } | |
| static int | |
| yt921x_dsa_port_bridge_flags(struct dsa_switch *ds, int port, | |
| struct switchdev_brport_flags flags, | |
| struct netlink_ext_ack *extack) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| int res; | |
| if (dsa_is_cpu_port(ds, port)) | |
| return 0; | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_bridge_flags(priv, port, flags); | |
| mutex_unlock(&priv->reg_lock); | |
| return res; | |
| } | |
| static void | |
| yt921x_dsa_port_bridge_leave(struct dsa_switch *ds, int port, | |
| struct dsa_bridge bridge) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| struct device *dev = to_device(priv); | |
| int res; | |
| if (dsa_is_cpu_port(ds, port)) | |
| return; | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_bridge_leave(priv, port); | |
| mutex_unlock(&priv->reg_lock); | |
| if (res) | |
| dev_err(dev, "Failed to %s port %d: %i\n", "unbridge", | |
| port, res); | |
| } | |
| static int | |
| yt921x_dsa_port_bridge_join(struct dsa_switch *ds, int port, | |
| struct dsa_bridge bridge, bool *tx_fwd_offload, | |
| struct netlink_ext_ack *extack) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| u16 ports_mask; | |
| int res; | |
| if (dsa_is_cpu_port(ds, port)) | |
| return 0; | |
| ports_mask = dsa_bridge_ports(ds, bridge.dev); | |
| ports_mask |= priv->cpu_ports_mask; | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_bridge_join(priv, port, ports_mask); | |
| mutex_unlock(&priv->reg_lock); | |
| return res; | |
| } | |
| static int | |
| yt921x_dsa_port_mst_state_set(struct dsa_switch *ds, int port, | |
| const struct switchdev_mst_state *st) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| u32 mask; | |
| u32 ctrl; | |
| int res; | |
| mask = YT921X_STP_PORTn_M(port); | |
| switch (st->state) { | |
| case BR_STATE_DISABLED: | |
| ctrl = YT921X_STP_PORTn_DISABLED(port); | |
| break; | |
| case BR_STATE_LISTENING: | |
| case BR_STATE_LEARNING: | |
| ctrl = YT921X_STP_PORTn_LEARNING(port); | |
| break; | |
| case BR_STATE_FORWARDING: | |
| default: | |
| ctrl = YT921X_STP_PORTn_FORWARD(port); | |
| break; | |
| case BR_STATE_BLOCKING: | |
| ctrl = YT921X_STP_PORTn_BLOCKING(port); | |
| break; | |
| } | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_reg_update_bits(priv, YT921X_STPn(st->msti), mask, ctrl); | |
| mutex_unlock(&priv->reg_lock); | |
| return res; | |
| } | |
| static int | |
| yt921x_dsa_vlan_msti_set(struct dsa_switch *ds, struct dsa_bridge bridge, | |
| const struct switchdev_vlan_msti *msti) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| u32 masks[2]; | |
| u32 ctrls[2]; | |
| int res; | |
| if (!msti->vid) | |
| return -EINVAL; | |
| if (!msti->msti || msti->msti >= YT921X_MSTI_NUM) | |
| return -EINVAL; | |
| masks[0] = 0; | |
| ctrls[0] = 0; | |
| masks[1] = YT921X_VLAN_CTRLb_STP_ID_M; | |
| ctrls[1] = YT921X_VLAN_CTRLb_STP_ID(msti->msti); | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_reg64_update_bits(priv, YT921X_VLANn_CTRL(msti->vid), | |
| masks, ctrls); | |
| mutex_unlock(&priv->reg_lock); | |
| return res; | |
| } | |
| static void | |
| yt921x_dsa_port_stp_state_set(struct dsa_switch *ds, int port, u8 state) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| struct dsa_port *dp = dsa_to_port(ds, port); | |
| struct device *dev = to_device(priv); | |
| bool learning; | |
| u32 mask; | |
| u32 ctrl; | |
| int res; | |
| mask = YT921X_STP_PORTn_M(port); | |
| learning = false; | |
| switch (state) { | |
| case BR_STATE_DISABLED: | |
| ctrl = YT921X_STP_PORTn_DISABLED(port); | |
| break; | |
| case BR_STATE_LISTENING: | |
| ctrl = YT921X_STP_PORTn_LEARNING(port); | |
| break; | |
| case BR_STATE_LEARNING: | |
| ctrl = YT921X_STP_PORTn_LEARNING(port); | |
| learning = dp->learning; | |
| break; | |
| case BR_STATE_FORWARDING: | |
| default: | |
| ctrl = YT921X_STP_PORTn_FORWARD(port); | |
| learning = dp->learning; | |
| break; | |
| case BR_STATE_BLOCKING: | |
| ctrl = YT921X_STP_PORTn_BLOCKING(port); | |
| break; | |
| } | |
| mutex_lock(&priv->reg_lock); | |
| do { | |
| res = yt921x_reg_update_bits(priv, YT921X_STPn(0), mask, ctrl); | |
| if (res) | |
| break; | |
| mask = YT921X_PORT_LEARN_DIS; | |
| ctrl = !learning ? YT921X_PORT_LEARN_DIS : 0; | |
| res = yt921x_reg_update_bits(priv, YT921X_PORTn_LEARN(port), | |
| mask, ctrl); | |
| } while (0); | |
| mutex_unlock(&priv->reg_lock); | |
| if (res) | |
| dev_err(dev, "Failed to %s port %d: %i\n", "set STP state for", | |
| port, res); | |
| } | |
| static int __maybe_unused | |
| yt921x_dsa_port_get_default_prio(struct dsa_switch *ds, int port) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| u32 val; | |
| int res; | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_reg_read(priv, YT921X_PORTn_QOS(port), &val); | |
| mutex_unlock(&priv->reg_lock); | |
| if (res) | |
| return res; | |
| return FIELD_GET(YT921X_PORT_QOS_PRIO_M, val); | |
| } | |
| static int __maybe_unused | |
| yt921x_dsa_port_set_default_prio(struct dsa_switch *ds, int port, u8 prio) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| u32 mask; | |
| u32 ctrl; | |
| int res; | |
| if (prio >= YT921X_PRIO_NUM) | |
| return -EINVAL; | |
| mutex_lock(&priv->reg_lock); | |
| mask = YT921X_PORT_QOS_PRIO_M | YT921X_PORT_QOS_PRIO_EN; | |
| ctrl = YT921X_PORT_QOS_PRIO(prio) | YT921X_PORT_QOS_PRIO_EN; | |
| res = yt921x_reg_update_bits(priv, YT921X_PORTn_QOS(port), mask, ctrl); | |
| mutex_unlock(&priv->reg_lock); | |
| return res; | |
| } | |
| static int __maybe_unused appprios_cmp(const void *a, const void *b) | |
| { | |
| return ((const u8 *)b)[1] - ((const u8 *)a)[1]; | |
| } | |
| static int __maybe_unused | |
| yt921x_dsa_port_get_apptrust(struct dsa_switch *ds, int port, u8 *sel, | |
| int *nselp) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| u8 appprios[2][2] = {}; | |
| int nsel; | |
| u32 val; | |
| int res; | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_reg_read(priv, YT921X_PORTn_PRIO_ORD(port), &val); | |
| mutex_unlock(&priv->reg_lock); | |
| if (res) | |
| return res; | |
| appprios[0][0] = IEEE_8021QAZ_APP_SEL_DSCP; | |
| appprios[0][1] = (val >> (3 * YT921X_APP_SEL_DSCP)) & 7; | |
| appprios[1][0] = DCB_APP_SEL_PCP; | |
| appprios[1][1] = (val >> (3 * YT921X_APP_SEL_CVLAN_PCP)) & 7; | |
| sort(appprios, ARRAY_SIZE(appprios), sizeof(appprios[0]), appprios_cmp, | |
| NULL); | |
| nsel = 0; | |
| for (int i = 0; i < ARRAY_SIZE(appprios) && appprios[i][1]; i++) { | |
| sel[nsel] = appprios[i][0]; | |
| nsel++; | |
| } | |
| *nselp = nsel; | |
| return 0; | |
| } | |
| static int __maybe_unused | |
| yt921x_dsa_port_set_apptrust(struct dsa_switch *ds, int port, const u8 *sel, | |
| int nsel) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| struct device *dev = to_device(priv); | |
| u32 ctrl; | |
| int res; | |
| if (nsel > YT921X_APP_SEL_NUM) | |
| return -EINVAL; | |
| ctrl = 0; | |
| for (int i = 0; i < nsel; i++) { | |
| switch (sel[i]) { | |
| case IEEE_8021QAZ_APP_SEL_DSCP: | |
| ctrl |= YT921X_PORT_PRIO_ORD_APPm(YT921X_APP_SEL_DSCP, | |
| 7 - i); | |
| break; | |
| case DCB_APP_SEL_PCP: | |
| ctrl |= YT921X_PORT_PRIO_ORD_APPm(YT921X_APP_SEL_CVLAN_PCP, | |
| 7 - i); | |
| ctrl |= YT921X_PORT_PRIO_ORD_APPm(YT921X_APP_SEL_SVLAN_PCP, | |
| 7 - i); | |
| break; | |
| default: | |
| dev_err(dev, | |
| "Invalid apptrust selector (at %d-th). Supported: dscp, pcp\n", | |
| i + 1); | |
| return -EOPNOTSUPP; | |
| } | |
| } | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_reg_write(priv, YT921X_PORTn_PRIO_ORD(port), ctrl); | |
| mutex_unlock(&priv->reg_lock); | |
| return res; | |
| } | |
| static int yt921x_port_down(struct yt921x_priv *priv, int port) | |
| { | |
| u32 mask; | |
| int res; | |
| mask = YT921X_PORT_LINK | YT921X_PORT_RX_MAC_EN | YT921X_PORT_TX_MAC_EN; | |
| res = yt921x_reg_clear_bits(priv, YT921X_PORTn_CTRL(port), mask); | |
| if (res) | |
| return res; | |
| if (yt921x_port_is_external(port)) { | |
| mask = YT921X_SERDES_LINK; | |
| res = yt921x_reg_clear_bits(priv, YT921X_SERDESn(port), mask); | |
| if (res) | |
| return res; | |
| mask = YT921X_XMII_LINK; | |
| res = yt921x_reg_clear_bits(priv, YT921X_XMIIn(port), mask); | |
| if (res) | |
| return res; | |
| } | |
| return 0; | |
| } | |
| static int | |
| yt921x_port_up(struct yt921x_priv *priv, int port, unsigned int mode, | |
| phy_interface_t interface, int speed, int duplex, | |
| bool tx_pause, bool rx_pause) | |
| { | |
| u32 mask; | |
| u32 ctrl; | |
| int res; | |
| switch (speed) { | |
| case SPEED_10: | |
| ctrl = YT921X_PORT_SPEED_10; | |
| break; | |
| case SPEED_100: | |
| ctrl = YT921X_PORT_SPEED_100; | |
| break; | |
| case SPEED_1000: | |
| ctrl = YT921X_PORT_SPEED_1000; | |
| break; | |
| case SPEED_2500: | |
| ctrl = YT921X_PORT_SPEED_2500; | |
| break; | |
| case SPEED_10000: | |
| ctrl = YT921X_PORT_SPEED_10000; | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| if (duplex == DUPLEX_FULL) | |
| ctrl |= YT921X_PORT_DUPLEX_FULL; | |
| if (tx_pause) | |
| ctrl |= YT921X_PORT_TX_PAUSE; | |
| if (rx_pause) | |
| ctrl |= YT921X_PORT_RX_PAUSE; | |
| ctrl |= YT921X_PORT_RX_MAC_EN | YT921X_PORT_TX_MAC_EN; | |
| res = yt921x_reg_write(priv, YT921X_PORTn_CTRL(port), ctrl); | |
| if (res) | |
| return res; | |
| if (yt921x_port_is_external(port)) { | |
| mask = YT921X_SERDES_SPEED_M; | |
| switch (speed) { | |
| case SPEED_10: | |
| ctrl = YT921X_SERDES_SPEED_10; | |
| break; | |
| case SPEED_100: | |
| ctrl = YT921X_SERDES_SPEED_100; | |
| break; | |
| case SPEED_1000: | |
| ctrl = YT921X_SERDES_SPEED_1000; | |
| break; | |
| case SPEED_2500: | |
| ctrl = YT921X_SERDES_SPEED_2500; | |
| break; | |
| case SPEED_10000: | |
| ctrl = YT921X_SERDES_SPEED_10000; | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| mask |= YT921X_SERDES_DUPLEX_FULL; | |
| if (duplex == DUPLEX_FULL) | |
| ctrl |= YT921X_SERDES_DUPLEX_FULL; | |
| mask |= YT921X_SERDES_TX_PAUSE; | |
| if (tx_pause) | |
| ctrl |= YT921X_SERDES_TX_PAUSE; | |
| mask |= YT921X_SERDES_RX_PAUSE; | |
| if (rx_pause) | |
| ctrl |= YT921X_SERDES_RX_PAUSE; | |
| mask |= YT921X_SERDES_LINK; | |
| ctrl |= YT921X_SERDES_LINK; | |
| res = yt921x_reg_update_bits(priv, YT921X_SERDESn(port), | |
| mask, ctrl); | |
| if (res) | |
| return res; | |
| mask = YT921X_XMII_LINK; | |
| res = yt921x_reg_set_bits(priv, YT921X_XMIIn(port), mask); | |
| if (res) | |
| return res; | |
| switch (speed) { | |
| case SPEED_10: | |
| ctrl = YT921X_MDIO_POLLING_SPEED_10; | |
| break; | |
| case SPEED_100: | |
| ctrl = YT921X_MDIO_POLLING_SPEED_100; | |
| break; | |
| case SPEED_1000: | |
| ctrl = YT921X_MDIO_POLLING_SPEED_1000; | |
| break; | |
| case SPEED_2500: | |
| ctrl = YT921X_MDIO_POLLING_SPEED_2500; | |
| break; | |
| case SPEED_10000: | |
| ctrl = YT921X_MDIO_POLLING_SPEED_10000; | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| if (duplex == DUPLEX_FULL) | |
| ctrl |= YT921X_MDIO_POLLING_DUPLEX_FULL; | |
| ctrl |= YT921X_MDIO_POLLING_LINK; | |
| res = yt921x_reg_write(priv, YT921X_MDIO_POLLINGn(port), ctrl); | |
| if (res) | |
| return res; | |
| } | |
| return 0; | |
| } | |
| static int | |
| yt921x_port_config(struct yt921x_priv *priv, int port, unsigned int mode, | |
| phy_interface_t interface) | |
| { | |
| struct device *dev = to_device(priv); | |
| u32 mask; | |
| u32 ctrl; | |
| int res; | |
| if (!yt921x_port_is_external(port)) { | |
| if (interface != PHY_INTERFACE_MODE_INTERNAL) { | |
| dev_err(dev, "Wrong mode %d on port %d\n", | |
| interface, port); | |
| return -EINVAL; | |
| } | |
| return 0; | |
| } | |
| switch (interface) { | |
| /* SERDES */ | |
| case PHY_INTERFACE_MODE_SGMII: | |
| case PHY_INTERFACE_MODE_100BASEX: | |
| case PHY_INTERFACE_MODE_1000BASEX: | |
| case PHY_INTERFACE_MODE_2500BASEX: | |
| mask = YT921X_SERDES_CTRL_PORTn(port); | |
| res = yt921x_reg_set_bits(priv, YT921X_SERDES_CTRL, mask); | |
| if (res) | |
| return res; | |
| mask = YT921X_XMII_CTRL_PORTn(port); | |
| res = yt921x_reg_clear_bits(priv, YT921X_XMII_CTRL, mask); | |
| if (res) | |
| return res; | |
| mask = YT921X_SERDES_MODE_M; | |
| switch (interface) { | |
| case PHY_INTERFACE_MODE_SGMII: | |
| ctrl = YT921X_SERDES_MODE_SGMII; | |
| break; | |
| case PHY_INTERFACE_MODE_100BASEX: | |
| ctrl = YT921X_SERDES_MODE_100BASEX; | |
| break; | |
| case PHY_INTERFACE_MODE_1000BASEX: | |
| ctrl = YT921X_SERDES_MODE_1000BASEX; | |
| break; | |
| case PHY_INTERFACE_MODE_2500BASEX: | |
| ctrl = YT921X_SERDES_MODE_2500BASEX; | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| res = yt921x_reg_update_bits(priv, YT921X_SERDESn(port), | |
| mask, ctrl); | |
| if (res) | |
| return res; | |
| break; | |
| /* add XMII support here */ | |
| default: | |
| return -EINVAL; | |
| } | |
| return 0; | |
| } | |
| static void | |
| yt921x_phylink_mac_link_down(struct phylink_config *config, unsigned int mode, | |
| phy_interface_t interface) | |
| { | |
| struct dsa_port *dp = dsa_phylink_to_port(config); | |
| struct yt921x_priv *priv = to_yt921x_priv(dp->ds); | |
| int port = dp->index; | |
| int res; | |
| /* No need to sync; port control block is hold until device remove */ | |
| cancel_delayed_work(&priv->ports[port].mib_read); | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_port_down(priv, port); | |
| mutex_unlock(&priv->reg_lock); | |
| if (res) | |
| dev_err(dp->ds->dev, "Failed to %s port %d: %i\n", "bring down", | |
| port, res); | |
| } | |
| static void | |
| yt921x_phylink_mac_link_up(struct phylink_config *config, | |
| struct phy_device *phydev, unsigned int mode, | |
| phy_interface_t interface, int speed, int duplex, | |
| bool tx_pause, bool rx_pause) | |
| { | |
| struct dsa_port *dp = dsa_phylink_to_port(config); | |
| struct yt921x_priv *priv = to_yt921x_priv(dp->ds); | |
| int port = dp->index; | |
| int res; | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_port_up(priv, port, mode, interface, speed, duplex, | |
| tx_pause, rx_pause); | |
| mutex_unlock(&priv->reg_lock); | |
| if (res) | |
| dev_err(dp->ds->dev, "Failed to %s port %d: %i\n", "bring up", | |
| port, res); | |
| schedule_delayed_work(&priv->ports[port].mib_read, 0); | |
| } | |
| static void | |
| yt921x_phylink_mac_config(struct phylink_config *config, unsigned int mode, | |
| const struct phylink_link_state *state) | |
| { | |
| struct dsa_port *dp = dsa_phylink_to_port(config); | |
| struct yt921x_priv *priv = to_yt921x_priv(dp->ds); | |
| int port = dp->index; | |
| int res; | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_port_config(priv, port, mode, state->interface); | |
| mutex_unlock(&priv->reg_lock); | |
| if (res) | |
| dev_err(dp->ds->dev, "Failed to %s port %d: %i\n", "config", | |
| port, res); | |
| } | |
| static void | |
| yt921x_dsa_phylink_get_caps(struct dsa_switch *ds, int port, | |
| struct phylink_config *config) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| const struct yt921x_info *info = priv->info; | |
| config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE | | |
| MAC_10 | MAC_100 | MAC_1000; | |
| if (info->internal_mask & BIT(port)) { | |
| /* Port 10 for MCU should probably go here too. But since that | |
| * is untested yet, turn it down for the moment by letting it | |
| * fall to the default branch. | |
| */ | |
| __set_bit(PHY_INTERFACE_MODE_INTERNAL, | |
| config->supported_interfaces); | |
| } else if (info->external_mask & BIT(port)) { | |
| /* TODO: external ports may support SERDES only, XMII only, or | |
| * SERDES + XMII depending on the chip. However, we can't get | |
| * the accurate config table due to lack of document, thus | |
| * we simply declare SERDES + XMII and rely on the correctness | |
| * of devicetree for now. | |
| */ | |
| /* SERDES */ | |
| __set_bit(PHY_INTERFACE_MODE_SGMII, | |
| config->supported_interfaces); | |
| /* REVSGMII (SGMII in PHY role) should go here, once | |
| * PHY_INTERFACE_MODE_REVSGMII is introduced. | |
| */ | |
| __set_bit(PHY_INTERFACE_MODE_100BASEX, | |
| config->supported_interfaces); | |
| __set_bit(PHY_INTERFACE_MODE_1000BASEX, | |
| config->supported_interfaces); | |
| __set_bit(PHY_INTERFACE_MODE_2500BASEX, | |
| config->supported_interfaces); | |
| config->mac_capabilities |= MAC_2500FD; | |
| /* XMII */ | |
| /* Not tested. To add support for XMII: | |
| * - Add proper interface modes below | |
| * - Handle them in yt921x_port_config() | |
| */ | |
| } | |
| /* no such port: empty supported_interfaces causes phylink to turn it | |
| * down | |
| */ | |
| } | |
| static int yt921x_port_setup(struct yt921x_priv *priv, int port) | |
| { | |
| struct dsa_switch *ds = &priv->ds; | |
| u32 ctrl; | |
| int res; | |
| res = yt921x_userport_standalone(priv, port); | |
| if (res) | |
| return res; | |
| /* Clear prio order (even if DCB is not enabled) to avoid unsolicited | |
| * priorities | |
| */ | |
| res = yt921x_reg_write(priv, YT921X_PORTn_PRIO_ORD(port), 0); | |
| if (res) | |
| return res; | |
| if (dsa_is_cpu_port(ds, port)) { | |
| /* Egress of CPU port is supposed to be completely controlled | |
| * via tagging, so set to oneway isolated (drop all packets | |
| * without tag). | |
| */ | |
| ctrl = ~(u32)0; | |
| res = yt921x_reg_write(priv, YT921X_PORTn_ISOLATION(port), | |
| ctrl); | |
| if (res) | |
| return res; | |
| /* To simplify FDB "isolation" simulation, we also disable | |
| * learning on the CPU port, and let software identify packets | |
| * towarding CPU (either trapped or a static FDB entry is | |
| * matched, no matter which bridge that entry is for), which is | |
| * already done by yt921x_userport_standalone(). As a result, | |
| * VLAN-awareness becomes unrelated on the CPU port (set to | |
| * VLAN-unaware by the way). | |
| */ | |
| } | |
| return 0; | |
| } | |
| static enum dsa_tag_protocol | |
| yt921x_dsa_get_tag_protocol(struct dsa_switch *ds, int port, | |
| enum dsa_tag_protocol m) | |
| { | |
| return DSA_TAG_PROTO_YT921X; | |
| } | |
| static int yt921x_dsa_port_setup(struct dsa_switch *ds, int port) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| int res; | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_port_setup(priv, port); | |
| mutex_unlock(&priv->reg_lock); | |
| return res; | |
| } | |
| /* Not "port" - DSCP mapping is global */ | |
| static int __maybe_unused | |
| yt921x_dsa_port_get_dscp_prio(struct dsa_switch *ds, int port, u8 dscp) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| u32 val; | |
| int res; | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_reg_read(priv, YT921X_IPM_DSCPn(dscp), &val); | |
| mutex_unlock(&priv->reg_lock); | |
| if (res) | |
| return res; | |
| return FIELD_GET(YT921X_IPM_PRIO_M, val); | |
| } | |
| static int __maybe_unused | |
| yt921x_dsa_port_del_dscp_prio(struct dsa_switch *ds, int port, u8 dscp, u8 prio) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| u32 val; | |
| int res; | |
| mutex_lock(&priv->reg_lock); | |
| /* During a "dcb app replace" command, the new app table entry will be | |
| * added first, then the old one will be deleted. But the hardware only | |
| * supports one QoS class per DSCP value (duh), so if we blindly delete | |
| * the app table entry for this DSCP value, we end up deleting the | |
| * entry with the new priority. Avoid that by checking whether user | |
| * space wants to delete the priority which is currently configured, or | |
| * something else which is no longer current. | |
| */ | |
| res = yt921x_reg_read(priv, YT921X_IPM_DSCPn(dscp), &val); | |
| if (!res && FIELD_GET(YT921X_IPM_PRIO_M, val) == prio) | |
| res = yt921x_reg_write(priv, YT921X_IPM_DSCPn(dscp), | |
| YT921X_IPM_PRIO(IEEE8021Q_TT_BK)); | |
| mutex_unlock(&priv->reg_lock); | |
| return res; | |
| } | |
| static int __maybe_unused | |
| yt921x_dsa_port_add_dscp_prio(struct dsa_switch *ds, int port, u8 dscp, u8 prio) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| int res; | |
| if (prio >= YT921X_PRIO_NUM) | |
| return -EINVAL; | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_reg_write(priv, YT921X_IPM_DSCPn(dscp), | |
| YT921X_IPM_PRIO(prio)); | |
| mutex_unlock(&priv->reg_lock); | |
| return res; | |
| } | |
| static int yt921x_edata_wait(struct yt921x_priv *priv, u32 *valp) | |
| { | |
| u32 val = YT921X_EDATA_DATA_IDLE; | |
| int res; | |
| res = yt921x_reg_wait(priv, YT921X_EDATA_DATA, | |
| YT921X_EDATA_DATA_STATUS_M, &val); | |
| if (res) | |
| return res; | |
| *valp = val; | |
| return 0; | |
| } | |
| static int | |
| yt921x_edata_read_cont(struct yt921x_priv *priv, u8 addr, u8 *valp) | |
| { | |
| u32 ctrl; | |
| u32 val; | |
| int res; | |
| ctrl = YT921X_EDATA_CTRL_ADDR(addr) | YT921X_EDATA_CTRL_READ; | |
| res = yt921x_reg_write(priv, YT921X_EDATA_CTRL, ctrl); | |
| if (res) | |
| return res; | |
| res = yt921x_edata_wait(priv, &val); | |
| if (res) | |
| return res; | |
| *valp = FIELD_GET(YT921X_EDATA_DATA_DATA_M, val); | |
| return 0; | |
| } | |
| static int yt921x_edata_read(struct yt921x_priv *priv, u8 addr, u8 *valp) | |
| { | |
| u32 val; | |
| int res; | |
| res = yt921x_edata_wait(priv, &val); | |
| if (res) | |
| return res; | |
| return yt921x_edata_read_cont(priv, addr, valp); | |
| } | |
| static int yt921x_chip_detect(struct yt921x_priv *priv) | |
| { | |
| struct device *dev = to_device(priv); | |
| const struct yt921x_info *info; | |
| u8 extmode; | |
| u32 chipid; | |
| u32 major; | |
| u32 mode; | |
| u32 val; | |
| int res; | |
| res = yt921x_reg_read(priv, YT921X_CHIP_ID, &chipid); | |
| if (res) | |
| return res; | |
| major = FIELD_GET(YT921X_CHIP_ID_MAJOR, chipid); | |
| for (info = yt921x_infos; info->name; info++) | |
| if (info->major == major) | |
| break; | |
| if (!info->name) { | |
| dev_err(dev, "Unexpected chipid 0x%x\n", chipid); | |
| return -ENODEV; | |
| } | |
| res = yt921x_reg_read(priv, YT921X_CHIP_MODE, &mode); | |
| if (res) | |
| return res; | |
| res = yt921x_edata_read(priv, YT921X_EDATA_EXTMODE, &extmode); | |
| if (res) | |
| return res; | |
| for (; info->name; info++) | |
| if (info->major == major && info->mode == mode && | |
| info->extmode == extmode) | |
| break; | |
| if (!info->name) { | |
| dev_err(dev, | |
| "Unsupported chipid 0x%x with chipmode 0x%x 0x%x\n", | |
| chipid, mode, extmode); | |
| return -ENODEV; | |
| } | |
| res = yt921x_reg_read(priv, YT921X_SYS_CLK, &val); | |
| if (res) | |
| return res; | |
| switch (FIELD_GET(YT921X_SYS_CLK_SEL_M, val)) { | |
| case 0: | |
| priv->cycle_ns = info->major == YT9215_MAJOR ? 8 : 6; | |
| break; | |
| case YT921X_SYS_CLK_143M: | |
| priv->cycle_ns = 7; | |
| break; | |
| default: | |
| priv->cycle_ns = 8; | |
| } | |
| /* Print chipid here since we are interested in lower 16 bits */ | |
| dev_info(dev, | |
| "Motorcomm %s ethernet switch, chipid: 0x%x, chipmode: 0x%x 0x%x\n", | |
| info->name, chipid, mode, extmode); | |
| priv->info = info; | |
| return 0; | |
| } | |
| static int yt921x_chip_reset(struct yt921x_priv *priv) | |
| { | |
| struct device *dev = to_device(priv); | |
| u16 eth_p_tag; | |
| u32 val; | |
| int res; | |
| res = yt921x_chip_detect(priv); | |
| if (res) | |
| return res; | |
| /* Reset */ | |
| res = yt921x_reg_write(priv, YT921X_RST, YT921X_RST_HW); | |
| if (res) | |
| return res; | |
| /* RST_HW is almost same as GPIO hard reset, so we need this delay. */ | |
| fsleep(YT921X_RST_DELAY_US); | |
| val = 0; | |
| res = yt921x_reg_wait(priv, YT921X_RST, ~0, &val); | |
| if (res) | |
| return res; | |
| /* Check for tag EtherType; do it after reset in case you messed it up | |
| * before. | |
| */ | |
| res = yt921x_reg_read(priv, YT921X_CPU_TAG_TPID, &val); | |
| if (res) | |
| return res; | |
| eth_p_tag = FIELD_GET(YT921X_CPU_TAG_TPID_TPID_M, val); | |
| if (eth_p_tag != ETH_P_YT921X) { | |
| dev_err(dev, "Tag type 0x%x != 0x%x\n", eth_p_tag, | |
| ETH_P_YT921X); | |
| /* Despite being possible, we choose not to set CPU_TAG_TPID, | |
| * since there is no way it can be different unless you have the | |
| * wrong chip. | |
| */ | |
| return -EINVAL; | |
| } | |
| return 0; | |
| } | |
| static int yt921x_chip_setup_dsa(struct yt921x_priv *priv) | |
| { | |
| struct dsa_switch *ds = &priv->ds; | |
| unsigned long cpu_ports_mask; | |
| u32 ctrls[2]; | |
| u32 ctrl; | |
| int port; | |
| int res; | |
| /* Enable DSA */ | |
| priv->cpu_ports_mask = dsa_cpu_ports(ds); | |
| ctrl = YT921X_EXT_CPU_PORT_TAG_EN | YT921X_EXT_CPU_PORT_PORT_EN | | |
| YT921X_EXT_CPU_PORT_PORT(__ffs(priv->cpu_ports_mask)); | |
| res = yt921x_reg_write(priv, YT921X_EXT_CPU_PORT, ctrl); | |
| if (res) | |
| return res; | |
| /* Setup software switch */ | |
| ctrl = YT921X_CPU_COPY_TO_EXT_CPU; | |
| res = yt921x_reg_write(priv, YT921X_CPU_COPY, ctrl); | |
| if (res) | |
| return res; | |
| ctrl = GENMASK(10, 0); | |
| res = yt921x_reg_write(priv, YT921X_FILTER_UNK_UCAST, ctrl); | |
| if (res) | |
| return res; | |
| res = yt921x_reg_write(priv, YT921X_FILTER_UNK_MCAST, ctrl); | |
| if (res) | |
| return res; | |
| /* YT921x does not support native DSA port bridging, so we use port | |
| * isolation to emulate it. However, be especially careful that port | |
| * isolation takes _after_ FDB lookups, i.e. if an FDB entry (from | |
| * another bridge) is matched and the destination port (in another | |
| * bridge) is blocked, the packet will be dropped instead of flooding to | |
| * the "bridged" ports, thus we need to trap and handle those packets by | |
| * software. | |
| * | |
| * If there is no more than one bridge, we might be able to drop them | |
| * directly given some conditions are met, but we trap them in all cases | |
| * for now. | |
| */ | |
| ctrl = 0; | |
| for (int i = 0; i < YT921X_PORT_NUM; i++) | |
| ctrl |= YT921X_ACT_UNK_ACTn_TRAP(i); | |
| /* Except for CPU ports, if any packets are sent via CPU ports without | |
| * tag, they should be dropped. | |
| */ | |
| cpu_ports_mask = priv->cpu_ports_mask; | |
| for_each_set_bit(port, &cpu_ports_mask, YT921X_PORT_NUM) { | |
| ctrl &= ~YT921X_ACT_UNK_ACTn_M(port); | |
| ctrl |= YT921X_ACT_UNK_ACTn_DROP(port); | |
| } | |
| res = yt921x_reg_write(priv, YT921X_ACT_UNK_UCAST, ctrl); | |
| if (res) | |
| return res; | |
| res = yt921x_reg_write(priv, YT921X_ACT_UNK_MCAST, ctrl); | |
| if (res) | |
| return res; | |
| /* Tagged VID 0 should be treated as untagged, which confuses the | |
| * hardware a lot | |
| */ | |
| ctrls[0] = YT921X_VLAN_CTRLa_LEARN_DIS | YT921X_VLAN_CTRLa_PORTS_M; | |
| ctrls[1] = 0; | |
| res = yt921x_reg64_write(priv, YT921X_VLANn_CTRL(0), ctrls); | |
| if (res) | |
| return res; | |
| return 0; | |
| } | |
| static int yt921x_chip_setup_tc(struct yt921x_priv *priv) | |
| { | |
| unsigned int op_ns; | |
| u32 ctrl; | |
| int res; | |
| op_ns = 8 * priv->cycle_ns; | |
| ctrl = max(priv->meter_slot_ns / op_ns, YT921X_METER_SLOT_MIN); | |
| res = yt921x_reg_write(priv, YT921X_METER_SLOT, ctrl); | |
| if (res) | |
| return res; | |
| priv->meter_slot_ns = ctrl * op_ns; | |
| ctrl = max(priv->port_shape_slot_ns / op_ns, | |
| YT921X_PORT_SHAPE_SLOT_MIN); | |
| res = yt921x_reg_write(priv, YT921X_PORT_SHAPE_SLOT, ctrl); | |
| if (res) | |
| return res; | |
| priv->port_shape_slot_ns = ctrl * op_ns; | |
| return 0; | |
| } | |
| static int yt921x_chip_setup_acl(struct yt921x_priv *priv) | |
| { | |
| u32 ctrl; | |
| int res; | |
| ctrl = YT921X_ACL_PERMIT_UNMATCH_PORTS_M; | |
| res = yt921x_reg_write(priv, YT921X_ACL_PERMIT_UNMATCH, ctrl); | |
| if (res) | |
| return res; | |
| ctrl = YT921X_ACL_PORT_PORTS_M; | |
| res = yt921x_reg_write(priv, YT921X_ACL_PORT, ctrl); | |
| if (res) | |
| return res; | |
| return 0; | |
| } | |
| static int __maybe_unused yt921x_chip_setup_qos(struct yt921x_priv *priv) | |
| { | |
| u32 ctrl; | |
| int res; | |
| /* DSCP to internal priorities */ | |
| for (u8 dscp = 0; dscp < DSCP_MAX; dscp++) { | |
| int prio = ietf_dscp_to_ieee8021q_tt(dscp); | |
| if (prio < 0) | |
| return prio; | |
| res = yt921x_reg_write(priv, YT921X_IPM_DSCPn(dscp), | |
| YT921X_IPM_PRIO(prio)); | |
| if (res) | |
| return res; | |
| } | |
| /* 802.1Q QoS to internal priorities */ | |
| for (u8 pcp = 0; pcp < 8; pcp++) | |
| for (u8 dei = 0; dei < 2; dei++) { | |
| ctrl = YT921X_IPM_PRIO(pcp); | |
| if (dei) | |
| /* "Red" almost means drop, so it's not that | |
| * useful. Note that tc police does not support | |
| * Three-Color very well | |
| */ | |
| ctrl |= YT921X_IPM_COLOR_YELLOW; | |
| for (u8 svlan = 0; svlan < 2; svlan++) { | |
| u32 reg = YT921X_IPM_PCPn(svlan, dei, pcp); | |
| res = yt921x_reg_write(priv, reg, ctrl); | |
| if (res) | |
| return res; | |
| } | |
| } | |
| return 0; | |
| } | |
| static int yt921x_chip_setup(struct yt921x_priv *priv) | |
| { | |
| u32 ctrl; | |
| int res; | |
| ctrl = YT921X_FUNC_MIB | YT921X_FUNC_ACL | YT921X_FUNC_METER; | |
| res = yt921x_reg_set_bits(priv, YT921X_FUNC, ctrl); | |
| if (res) | |
| return res; | |
| res = yt921x_chip_setup_dsa(priv); | |
| if (res) | |
| return res; | |
| res = yt921x_chip_setup_tc(priv); | |
| if (res) | |
| return res; | |
| res = yt921x_chip_setup_acl(priv); | |
| if (res) | |
| return res; | |
| #if IS_ENABLED(CONFIG_DCB) | |
| res = yt921x_chip_setup_qos(priv); | |
| if (res) | |
| return res; | |
| #endif | |
| /* Clear MIB */ | |
| ctrl = YT921X_MIB_CTRL_CLEAN | YT921X_MIB_CTRL_ALL_PORT; | |
| res = yt921x_reg_write(priv, YT921X_MIB_CTRL, ctrl); | |
| if (res) | |
| return res; | |
| /* Miscellaneous */ | |
| res = yt921x_reg_set_bits(priv, YT921X_SENSOR, YT921X_SENSOR_TEMP); | |
| if (res) | |
| return res; | |
| return 0; | |
| } | |
| static int yt921x_dsa_setup(struct dsa_switch *ds) | |
| { | |
| struct yt921x_priv *priv = to_yt921x_priv(ds); | |
| struct device *dev = to_device(priv); | |
| struct device_node *np = dev->of_node; | |
| struct device_node *child; | |
| int res; | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_chip_reset(priv); | |
| mutex_unlock(&priv->reg_lock); | |
| if (res) | |
| return res; | |
| /* Register the internal mdio bus. Nodes for internal ports should have | |
| * proper phy-handle pointing to their PHYs. Not enabling the internal | |
| * bus is possible, though pretty wired, if internal ports are not used. | |
| */ | |
| child = of_get_child_by_name(np, "mdio"); | |
| if (child) { | |
| res = yt921x_mbus_int_init(priv, child); | |
| of_node_put(child); | |
| if (res) | |
| return res; | |
| } | |
| /* External mdio bus is optional */ | |
| child = of_get_child_by_name(np, "mdio-external"); | |
| if (child) { | |
| res = yt921x_mbus_ext_init(priv, child); | |
| of_node_put(child); | |
| if (res) | |
| return res; | |
| dev_err(dev, "Untested external mdio bus\n"); | |
| return -ENODEV; | |
| } | |
| mutex_lock(&priv->reg_lock); | |
| res = yt921x_chip_setup(priv); | |
| mutex_unlock(&priv->reg_lock); | |
| if (res) | |
| return res; | |
| return 0; | |
| } | |
| static const struct phylink_mac_ops yt921x_phylink_mac_ops = { | |
| .mac_link_down = yt921x_phylink_mac_link_down, | |
| .mac_link_up = yt921x_phylink_mac_link_up, | |
| .mac_config = yt921x_phylink_mac_config, | |
| }; | |
| static const struct dsa_switch_ops yt921x_dsa_switch_ops = { | |
| /* mib */ | |
| .get_strings = yt921x_dsa_get_strings, | |
| .get_ethtool_stats = yt921x_dsa_get_ethtool_stats, | |
| .get_sset_count = yt921x_dsa_get_sset_count, | |
| .get_eth_mac_stats = yt921x_dsa_get_eth_mac_stats, | |
| .get_eth_ctrl_stats = yt921x_dsa_get_eth_ctrl_stats, | |
| .get_rmon_stats = yt921x_dsa_get_rmon_stats, | |
| .get_stats64 = yt921x_dsa_get_stats64, | |
| .get_pause_stats = yt921x_dsa_get_pause_stats, | |
| /* eee */ | |
| .support_eee = dsa_supports_eee, | |
| .set_mac_eee = yt921x_dsa_set_mac_eee, | |
| /* mtu */ | |
| .port_change_mtu = yt921x_dsa_port_change_mtu, | |
| .port_max_mtu = yt921x_dsa_port_max_mtu, | |
| /* rate */ | |
| .port_policer_del = yt921x_dsa_port_policer_del, | |
| .port_policer_add = yt921x_dsa_port_policer_add, | |
| .port_setup_tc = yt921x_dsa_port_setup_tc, | |
| /* acl */ | |
| .cls_flower_del = yt921x_dsa_cls_flower_del, | |
| .cls_flower_add = yt921x_dsa_cls_flower_add, | |
| /* hsr */ | |
| .port_hsr_leave = dsa_port_simple_hsr_leave, | |
| .port_hsr_join = dsa_port_simple_hsr_join, | |
| /* mirror */ | |
| .port_mirror_del = yt921x_dsa_port_mirror_del, | |
| .port_mirror_add = yt921x_dsa_port_mirror_add, | |
| /* lag */ | |
| .port_lag_leave = yt921x_dsa_port_lag_leave, | |
| .port_lag_join = yt921x_dsa_port_lag_join, | |
| /* fdb */ | |
| .port_fdb_dump = yt921x_dsa_port_fdb_dump, | |
| .port_fast_age = yt921x_dsa_port_fast_age, | |
| .set_ageing_time = yt921x_dsa_set_ageing_time, | |
| .port_fdb_del = yt921x_dsa_port_fdb_del, | |
| .port_fdb_add = yt921x_dsa_port_fdb_add, | |
| .port_mdb_del = yt921x_dsa_port_mdb_del, | |
| .port_mdb_add = yt921x_dsa_port_mdb_add, | |
| /* vlan */ | |
| .port_vlan_filtering = yt921x_dsa_port_vlan_filtering, | |
| .port_vlan_del = yt921x_dsa_port_vlan_del, | |
| .port_vlan_add = yt921x_dsa_port_vlan_add, | |
| /* bridge */ | |
| .port_pre_bridge_flags = yt921x_dsa_port_pre_bridge_flags, | |
| .port_bridge_flags = yt921x_dsa_port_bridge_flags, | |
| .port_bridge_leave = yt921x_dsa_port_bridge_leave, | |
| .port_bridge_join = yt921x_dsa_port_bridge_join, | |
| /* mst */ | |
| .port_mst_state_set = yt921x_dsa_port_mst_state_set, | |
| .vlan_msti_set = yt921x_dsa_vlan_msti_set, | |
| .port_stp_state_set = yt921x_dsa_port_stp_state_set, | |
| #if IS_ENABLED(CONFIG_DCB) | |
| /* dcb */ | |
| .port_get_default_prio = yt921x_dsa_port_get_default_prio, | |
| .port_set_default_prio = yt921x_dsa_port_set_default_prio, | |
| .port_get_apptrust = yt921x_dsa_port_get_apptrust, | |
| .port_set_apptrust = yt921x_dsa_port_set_apptrust, | |
| #endif | |
| /* port */ | |
| .get_tag_protocol = yt921x_dsa_get_tag_protocol, | |
| .phylink_get_caps = yt921x_dsa_phylink_get_caps, | |
| .port_setup = yt921x_dsa_port_setup, | |
| #if IS_ENABLED(CONFIG_DCB) | |
| /* dscp */ | |
| .port_get_dscp_prio = yt921x_dsa_port_get_dscp_prio, | |
| .port_del_dscp_prio = yt921x_dsa_port_del_dscp_prio, | |
| .port_add_dscp_prio = yt921x_dsa_port_add_dscp_prio, | |
| #endif | |
| /* chip */ | |
| .setup = yt921x_dsa_setup, | |
| }; | |
| static void yt921x_mdio_shutdown(struct mdio_device *mdiodev) | |
| { | |
| struct yt921x_priv *priv = mdiodev_get_drvdata(mdiodev); | |
| if (!priv) | |
| return; | |
| dsa_switch_shutdown(&priv->ds); | |
| } | |
| static void yt921x_mdio_remove(struct mdio_device *mdiodev) | |
| { | |
| struct yt921x_priv *priv = mdiodev_get_drvdata(mdiodev); | |
| if (!priv) | |
| return; | |
| for (size_t i = ARRAY_SIZE(priv->ports); i-- > 0; ) { | |
| struct yt921x_port *pp = &priv->ports[i]; | |
| disable_delayed_work_sync(&pp->mib_read); | |
| } | |
| dsa_unregister_switch(&priv->ds); | |
| for (unsigned int i = 0; i < ARRAY_SIZE(priv->acl_blks); i++) { | |
| struct yt921x_acl_blk *aclblk = priv->acl_blks[i]; | |
| if (!aclblk) | |
| continue; | |
| for (unsigned int j = 0; j < ARRAY_SIZE(aclblk->rules); j++) { | |
| struct yt921x_acl_rule *aclrule = aclblk->rules[j]; | |
| if (!aclrule) | |
| continue; | |
| kvfree(aclrule); | |
| } | |
| kvfree(aclblk); | |
| } | |
| mutex_destroy(&priv->reg_lock); | |
| } | |
| static int yt921x_mdio_probe(struct mdio_device *mdiodev) | |
| { | |
| struct device *dev = &mdiodev->dev; | |
| struct yt921x_reg_mdio *mdio; | |
| struct yt921x_priv *priv; | |
| struct dsa_switch *ds; | |
| priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | |
| if (!priv) | |
| return -ENOMEM; | |
| mdio = devm_kzalloc(dev, sizeof(*mdio), GFP_KERNEL); | |
| if (!mdio) | |
| return -ENOMEM; | |
| mdio->bus = mdiodev->bus; | |
| mdio->addr = mdiodev->addr; | |
| mdio->switchid = 0; | |
| mutex_init(&priv->reg_lock); | |
| priv->reg_ops = &yt921x_reg_ops_mdio; | |
| priv->reg_ctx = mdio; | |
| for (size_t i = 0; i < ARRAY_SIZE(priv->ports); i++) { | |
| struct yt921x_port *pp = &priv->ports[i]; | |
| pp->index = i; | |
| INIT_DELAYED_WORK(&pp->mib_read, yt921x_poll_mib); | |
| } | |
| ds = &priv->ds; | |
| ds->dev = dev; | |
| ds->assisted_learning_on_cpu_port = true; | |
| ds->dscp_prio_mapping_is_global = true; | |
| ds->priv = priv; | |
| ds->ops = &yt921x_dsa_switch_ops; | |
| ds->ageing_time_min = 1 * 5000; | |
| ds->ageing_time_max = U16_MAX * 5000; | |
| ds->phylink_mac_ops = &yt921x_phylink_mac_ops; | |
| ds->num_lag_ids = YT921X_LAG_NUM; | |
| ds->num_ports = YT921X_PORT_NUM; | |
| mdiodev_set_drvdata(mdiodev, priv); | |
| return dsa_register_switch(ds); | |
| } | |
| static const struct of_device_id yt921x_of_match[] = { | |
| { .compatible = "motorcomm,yt9215" }, | |
| {} | |
| }; | |
| MODULE_DEVICE_TABLE(of, yt921x_of_match); | |
| static struct mdio_driver yt921x_mdio_driver = { | |
| .probe = yt921x_mdio_probe, | |
| .remove = yt921x_mdio_remove, | |
| .shutdown = yt921x_mdio_shutdown, | |
| .mdiodrv.driver = { | |
| .name = YT921X_NAME, | |
| .of_match_table = yt921x_of_match, | |
| }, | |
| }; | |
| mdio_module_driver(yt921x_mdio_driver); | |
| MODULE_AUTHOR("David Yang <mmyangfl@gmail.com>"); | |
| MODULE_DESCRIPTION("Driver for Motorcomm YT921x Switch"); | |
| MODULE_LICENSE("GPL"); |