summaryrefslogtreecommitdiff
path: root/drivers/net/dsa/yt921x.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/dsa/yt921x.c')
-rw-r--r--drivers/net/dsa/yt921x.c511
1 files changed, 483 insertions, 28 deletions
diff --git a/drivers/net/dsa/yt921x.c b/drivers/net/dsa/yt921x.c
index 1c511f5dc6ab..904613f4694a 100644
--- a/drivers/net/dsa/yt921x.c
+++ b/drivers/net/dsa/yt921x.c
@@ -8,6 +8,7 @@
* Copyright (c) 2025 David Yang
*/
+#include <linux/dcbnl.h>
#include <linux/etherdevice.h>
#include <linux/if_bridge.h>
#include <linux/if_hsr.h>
@@ -18,8 +19,11 @@
#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 "yt921x.h"
@@ -682,21 +686,22 @@ static int yt921x_read_mib(struct yt921x_priv *priv, int port)
const struct yt921x_mib_desc *desc = &yt921x_mib_descs[i];
u32 reg = YT921X_MIBn_DATA0(port) + desc->offset;
u64 *valp = &((u64 *)mib)[i];
- u64 val = *valp;
u32 val0;
- u32 val1;
+ u64 val;
res = yt921x_reg_read(priv, reg, &val0);
if (res)
break;
if (desc->size <= 1) {
- if (val < (u32)val)
- /* overflow */
- val += (u64)U32_MAX + 1;
- val &= ~U32_MAX;
- val |= val0;
+ 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;
@@ -1117,6 +1122,188 @@ yt921x_dsa_port_mirror_add(struct dsa_switch *ds, int port,
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);
@@ -1587,6 +1774,21 @@ yt921x_dsa_port_mdb_add(struct dsa_switch *ds, int port,
}
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;
@@ -1635,14 +1837,7 @@ yt921x_vlan_filtering(struct yt921x_priv *priv, int port, bool vlan_filtering)
if (res)
return res;
- /* Turn on / off VLAN awareness */
- mask = YT921X_PORT_IGR_TPIDn_CTAG_M;
- if (!vlan_filtering)
- ctrl = 0;
- else
- ctrl = YT921X_PORT_IGR_TPIDn_CTAG(0);
- res = yt921x_reg_update_bits(priv, YT921X_PORTn_IGR_TPID(port),
- mask, ctrl);
+ res = yt921x_vlan_aware_set(priv, port, vlan_filtering);
if (res)
return res;
@@ -1839,8 +2034,7 @@ static int yt921x_userport_standalone(struct yt921x_priv *priv, int port)
return res;
/* Turn off VLAN awareness */
- mask = YT921X_PORT_IGR_TPIDn_CTAG_M;
- res = yt921x_reg_clear_bits(priv, YT921X_PORTn_IGR_TPID(port), mask);
+ res = yt921x_vlan_aware_set(priv, port, false);
if (res)
return res;
@@ -2209,6 +2403,122 @@ yt921x_dsa_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
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;
@@ -2533,6 +2843,13 @@ static int yt921x_port_setup(struct yt921x_priv *priv, int 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
@@ -2576,6 +2893,66 @@ static int yt921x_dsa_port_setup(struct dsa_switch *ds, int port)
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;
@@ -2715,7 +3092,7 @@ static int yt921x_chip_reset(struct yt921x_priv *priv)
return 0;
}
-static int yt921x_chip_setup(struct yt921x_priv *priv)
+static int yt921x_chip_setup_dsa(struct yt921x_priv *priv)
{
struct dsa_switch *ds = &priv->ds;
unsigned long cpu_ports_mask;
@@ -2733,16 +3110,6 @@ static int yt921x_chip_setup(struct yt921x_priv *priv)
if (res)
return res;
- /* Enable and clear MIB */
- res = yt921x_reg_set_bits(priv, YT921X_FUNC, YT921X_FUNC_MIB);
- if (res)
- return res;
-
- ctrl = YT921X_MIB_CTRL_CLEAN | YT921X_MIB_CTRL_ALL_PORT;
- res = yt921x_reg_write(priv, YT921X_MIB_CTRL, ctrl);
- if (res)
- return res;
-
/* Setup software switch */
ctrl = YT921X_CPU_COPY_TO_EXT_CPU;
res = yt921x_reg_write(priv, YT921X_CPU_COPY, ctrl);
@@ -2795,6 +3162,76 @@ static int yt921x_chip_setup(struct yt921x_priv *priv)
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;
+ res = yt921x_reg_set_bits(priv, YT921X_FUNC, ctrl);
+ if (res)
+ return res;
+
+ res = yt921x_chip_setup_dsa(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)
@@ -2880,6 +3317,9 @@ static const struct dsa_switch_ops yt921x_dsa_switch_ops = {
/* 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,
@@ -2901,10 +3341,23 @@ static const struct dsa_switch_ops yt921x_dsa_switch_ops = {
.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,
};
@@ -2971,11 +3424,13 @@ static int yt921x_mdio_probe(struct mdio_device *mdiodev)
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);