mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-28 19:14:35 -04:00
Enable filtering of incoming traffics. Note that custom filters are yet to be utilized, and thus not all flow dissectors are implemented. Tested-by: hong son Nguyen <hongson.hn@gmail.com> Signed-off-by: David Yang <mmyangfl@gmail.com> Link: https://patch.msgid.link/20260606130011.307812-3-mmyangfl@gmail.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
4983 lines
120 KiB
C
4983 lines
120 KiB
C
// 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");
|