mirror of
https://github.com/openwrt/openwrt.git
synced 2024-12-24 15:56:49 +00:00
385 lines
10 KiB
Diff
385 lines
10 KiB
Diff
|
From e9bbf019af44b204b71ef8edf224002550aab641 Mon Sep 17 00:00:00 2001
|
||
|
From: Christian Marangi <ansuelsmth@gmail.com>
|
||
|
Date: Wed, 27 Jul 2022 13:35:22 +0200
|
||
|
Subject: [PATCH 13/14] net: dsa: qca8k: move port LAG functions to common code
|
||
|
|
||
|
The same port LAG functions are used by drivers based on qca8k family
|
||
|
switch. Move them to common code to make them accessible also by other
|
||
|
drivers.
|
||
|
|
||
|
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
|
||
|
Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
|
||
|
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
|
||
|
---
|
||
|
drivers/net/dsa/qca/qca8k-8xxx.c | 168 -----------------------------
|
||
|
drivers/net/dsa/qca/qca8k-common.c | 165 ++++++++++++++++++++++++++++
|
||
|
drivers/net/dsa/qca/qca8k.h | 6 ++
|
||
|
3 files changed, 171 insertions(+), 168 deletions(-)
|
||
|
|
||
|
--- a/drivers/net/dsa/qca/qca8k-8xxx.c
|
||
|
+++ b/drivers/net/dsa/qca/qca8k-8xxx.c
|
||
|
@@ -1743,178 +1743,6 @@ qca8k_get_tag_protocol(struct dsa_switch
|
||
|
return DSA_TAG_PROTO_QCA;
|
||
|
}
|
||
|
|
||
|
-static bool
|
||
|
-qca8k_lag_can_offload(struct dsa_switch *ds,
|
||
|
- struct net_device *lag,
|
||
|
- struct netdev_lag_upper_info *info)
|
||
|
-{
|
||
|
- struct dsa_port *dp;
|
||
|
- int id, members = 0;
|
||
|
-
|
||
|
- id = dsa_lag_id(ds->dst, lag);
|
||
|
- if (id < 0 || id >= ds->num_lag_ids)
|
||
|
- return false;
|
||
|
-
|
||
|
- dsa_lag_foreach_port(dp, ds->dst, lag)
|
||
|
- /* Includes the port joining the LAG */
|
||
|
- members++;
|
||
|
-
|
||
|
- if (members > QCA8K_NUM_PORTS_FOR_LAG)
|
||
|
- return false;
|
||
|
-
|
||
|
- if (info->tx_type != NETDEV_LAG_TX_TYPE_HASH)
|
||
|
- return false;
|
||
|
-
|
||
|
- if (info->hash_type != NETDEV_LAG_HASH_L2 &&
|
||
|
- info->hash_type != NETDEV_LAG_HASH_L23)
|
||
|
- return false;
|
||
|
-
|
||
|
- return true;
|
||
|
-}
|
||
|
-
|
||
|
-static int
|
||
|
-qca8k_lag_setup_hash(struct dsa_switch *ds,
|
||
|
- struct net_device *lag,
|
||
|
- struct netdev_lag_upper_info *info)
|
||
|
-{
|
||
|
- struct qca8k_priv *priv = ds->priv;
|
||
|
- bool unique_lag = true;
|
||
|
- u32 hash = 0;
|
||
|
- int i, id;
|
||
|
-
|
||
|
- id = dsa_lag_id(ds->dst, lag);
|
||
|
-
|
||
|
- switch (info->hash_type) {
|
||
|
- case NETDEV_LAG_HASH_L23:
|
||
|
- hash |= QCA8K_TRUNK_HASH_SIP_EN;
|
||
|
- hash |= QCA8K_TRUNK_HASH_DIP_EN;
|
||
|
- fallthrough;
|
||
|
- case NETDEV_LAG_HASH_L2:
|
||
|
- hash |= QCA8K_TRUNK_HASH_SA_EN;
|
||
|
- hash |= QCA8K_TRUNK_HASH_DA_EN;
|
||
|
- break;
|
||
|
- default: /* We should NEVER reach this */
|
||
|
- return -EOPNOTSUPP;
|
||
|
- }
|
||
|
-
|
||
|
- /* Check if we are the unique configured LAG */
|
||
|
- dsa_lags_foreach_id(i, ds->dst)
|
||
|
- if (i != id && dsa_lag_dev(ds->dst, i)) {
|
||
|
- unique_lag = false;
|
||
|
- break;
|
||
|
- }
|
||
|
-
|
||
|
- /* Hash Mode is global. Make sure the same Hash Mode
|
||
|
- * is set to all the 4 possible lag.
|
||
|
- * 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) {
|
||
|
- priv->lag_hash_mode = hash;
|
||
|
- } else if (priv->lag_hash_mode != hash) {
|
||
|
- netdev_err(lag, "Error: Mismateched Hash Mode across different lag is not supported\n");
|
||
|
- return -EOPNOTSUPP;
|
||
|
- }
|
||
|
-
|
||
|
- return regmap_update_bits(priv->regmap, QCA8K_TRUNK_HASH_EN_CTRL,
|
||
|
- QCA8K_TRUNK_HASH_MASK, hash);
|
||
|
-}
|
||
|
-
|
||
|
-static int
|
||
|
-qca8k_lag_refresh_portmap(struct dsa_switch *ds, int port,
|
||
|
- struct net_device *lag, bool delete)
|
||
|
-{
|
||
|
- struct qca8k_priv *priv = ds->priv;
|
||
|
- int ret, id, i;
|
||
|
- u32 val;
|
||
|
-
|
||
|
- id = dsa_lag_id(ds->dst, lag);
|
||
|
-
|
||
|
- /* Read current port member */
|
||
|
- ret = regmap_read(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL0, &val);
|
||
|
- if (ret)
|
||
|
- return ret;
|
||
|
-
|
||
|
- /* Shift val to the correct trunk */
|
||
|
- val >>= QCA8K_REG_GOL_TRUNK_SHIFT(id);
|
||
|
- val &= QCA8K_REG_GOL_TRUNK_MEMBER_MASK;
|
||
|
- if (delete)
|
||
|
- val &= ~BIT(port);
|
||
|
- else
|
||
|
- val |= BIT(port);
|
||
|
-
|
||
|
- /* Update port member. With empty portmap disable trunk */
|
||
|
- ret = regmap_update_bits(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL0,
|
||
|
- QCA8K_REG_GOL_TRUNK_MEMBER(id) |
|
||
|
- QCA8K_REG_GOL_TRUNK_EN(id),
|
||
|
- !val << QCA8K_REG_GOL_TRUNK_SHIFT(id) |
|
||
|
- val << QCA8K_REG_GOL_TRUNK_SHIFT(id));
|
||
|
-
|
||
|
- /* Search empty member if adding or port on deleting */
|
||
|
- for (i = 0; i < QCA8K_NUM_PORTS_FOR_LAG; i++) {
|
||
|
- ret = regmap_read(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL(id), &val);
|
||
|
- if (ret)
|
||
|
- return ret;
|
||
|
-
|
||
|
- val >>= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i);
|
||
|
- val &= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_MASK;
|
||
|
-
|
||
|
- if (delete) {
|
||
|
- /* If port flagged to be disabled assume this member is
|
||
|
- * empty
|
||
|
- */
|
||
|
- if (val != QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN_MASK)
|
||
|
- continue;
|
||
|
-
|
||
|
- val &= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT_MASK;
|
||
|
- if (val != port)
|
||
|
- continue;
|
||
|
- } else {
|
||
|
- /* If port flagged to be enabled assume this member is
|
||
|
- * already set
|
||
|
- */
|
||
|
- if (val == QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN_MASK)
|
||
|
- continue;
|
||
|
- }
|
||
|
-
|
||
|
- /* We have found the member to add/remove */
|
||
|
- break;
|
||
|
- }
|
||
|
-
|
||
|
- /* Set port in the correct port mask or disable port if in delete mode */
|
||
|
- return regmap_update_bits(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL(id),
|
||
|
- QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN(id, i) |
|
||
|
- QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT(id, i),
|
||
|
- !delete << QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i) |
|
||
|
- port << QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i));
|
||
|
-}
|
||
|
-
|
||
|
-static int
|
||
|
-qca8k_port_lag_join(struct dsa_switch *ds, int port,
|
||
|
- struct net_device *lag,
|
||
|
- struct netdev_lag_upper_info *info)
|
||
|
-{
|
||
|
- int ret;
|
||
|
-
|
||
|
- if (!qca8k_lag_can_offload(ds, lag, info))
|
||
|
- return -EOPNOTSUPP;
|
||
|
-
|
||
|
- ret = qca8k_lag_setup_hash(ds, lag, info);
|
||
|
- if (ret)
|
||
|
- return ret;
|
||
|
-
|
||
|
- return qca8k_lag_refresh_portmap(ds, port, lag, false);
|
||
|
-}
|
||
|
-
|
||
|
-static int
|
||
|
-qca8k_port_lag_leave(struct dsa_switch *ds, int port,
|
||
|
- struct net_device *lag)
|
||
|
-{
|
||
|
- return qca8k_lag_refresh_portmap(ds, port, lag, true);
|
||
|
-}
|
||
|
-
|
||
|
static void
|
||
|
qca8k_master_change(struct dsa_switch *ds, const struct net_device *master,
|
||
|
bool operational)
|
||
|
--- a/drivers/net/dsa/qca/qca8k-common.c
|
||
|
+++ b/drivers/net/dsa/qca/qca8k-common.c
|
||
|
@@ -1009,3 +1009,169 @@ int qca8k_port_vlan_del(struct dsa_switc
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
+
|
||
|
+static bool qca8k_lag_can_offload(struct dsa_switch *ds,
|
||
|
+ struct net_device *lag,
|
||
|
+ struct netdev_lag_upper_info *info)
|
||
|
+{
|
||
|
+ struct dsa_port *dp;
|
||
|
+ int id, members = 0;
|
||
|
+
|
||
|
+ id = dsa_lag_id(ds->dst, lag);
|
||
|
+ if (id < 0 || id >= ds->num_lag_ids)
|
||
|
+ return false;
|
||
|
+
|
||
|
+ dsa_lag_foreach_port(dp, ds->dst, lag)
|
||
|
+ /* Includes the port joining the LAG */
|
||
|
+ members++;
|
||
|
+
|
||
|
+ if (members > QCA8K_NUM_PORTS_FOR_LAG)
|
||
|
+ return false;
|
||
|
+
|
||
|
+ if (info->tx_type != NETDEV_LAG_TX_TYPE_HASH)
|
||
|
+ return false;
|
||
|
+
|
||
|
+ if (info->hash_type != NETDEV_LAG_HASH_L2 &&
|
||
|
+ info->hash_type != NETDEV_LAG_HASH_L23)
|
||
|
+ return false;
|
||
|
+
|
||
|
+ return true;
|
||
|
+}
|
||
|
+
|
||
|
+static int qca8k_lag_setup_hash(struct dsa_switch *ds,
|
||
|
+ struct net_device *lag,
|
||
|
+ struct netdev_lag_upper_info *info)
|
||
|
+{
|
||
|
+ struct qca8k_priv *priv = ds->priv;
|
||
|
+ bool unique_lag = true;
|
||
|
+ u32 hash = 0;
|
||
|
+ int i, id;
|
||
|
+
|
||
|
+ id = dsa_lag_id(ds->dst, lag);
|
||
|
+
|
||
|
+ switch (info->hash_type) {
|
||
|
+ case NETDEV_LAG_HASH_L23:
|
||
|
+ hash |= QCA8K_TRUNK_HASH_SIP_EN;
|
||
|
+ hash |= QCA8K_TRUNK_HASH_DIP_EN;
|
||
|
+ fallthrough;
|
||
|
+ case NETDEV_LAG_HASH_L2:
|
||
|
+ hash |= QCA8K_TRUNK_HASH_SA_EN;
|
||
|
+ hash |= QCA8K_TRUNK_HASH_DA_EN;
|
||
|
+ break;
|
||
|
+ default: /* We should NEVER reach this */
|
||
|
+ return -EOPNOTSUPP;
|
||
|
+ }
|
||
|
+
|
||
|
+ /* Check if we are the unique configured LAG */
|
||
|
+ dsa_lags_foreach_id(i, ds->dst)
|
||
|
+ if (i != id && dsa_lag_dev(ds->dst, i)) {
|
||
|
+ unique_lag = false;
|
||
|
+ break;
|
||
|
+ }
|
||
|
+
|
||
|
+ /* Hash Mode is global. Make sure the same Hash Mode
|
||
|
+ * is set to all the 4 possible lag.
|
||
|
+ * 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) {
|
||
|
+ priv->lag_hash_mode = hash;
|
||
|
+ } else if (priv->lag_hash_mode != hash) {
|
||
|
+ netdev_err(lag, "Error: Mismatched Hash Mode across different lag is not supported\n");
|
||
|
+ return -EOPNOTSUPP;
|
||
|
+ }
|
||
|
+
|
||
|
+ return regmap_update_bits(priv->regmap, QCA8K_TRUNK_HASH_EN_CTRL,
|
||
|
+ QCA8K_TRUNK_HASH_MASK, hash);
|
||
|
+}
|
||
|
+
|
||
|
+static int qca8k_lag_refresh_portmap(struct dsa_switch *ds, int port,
|
||
|
+ struct net_device *lag, bool delete)
|
||
|
+{
|
||
|
+ struct qca8k_priv *priv = ds->priv;
|
||
|
+ int ret, id, i;
|
||
|
+ u32 val;
|
||
|
+
|
||
|
+ id = dsa_lag_id(ds->dst, lag);
|
||
|
+
|
||
|
+ /* Read current port member */
|
||
|
+ ret = regmap_read(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL0, &val);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ /* Shift val to the correct trunk */
|
||
|
+ val >>= QCA8K_REG_GOL_TRUNK_SHIFT(id);
|
||
|
+ val &= QCA8K_REG_GOL_TRUNK_MEMBER_MASK;
|
||
|
+ if (delete)
|
||
|
+ val &= ~BIT(port);
|
||
|
+ else
|
||
|
+ val |= BIT(port);
|
||
|
+
|
||
|
+ /* Update port member. With empty portmap disable trunk */
|
||
|
+ ret = regmap_update_bits(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL0,
|
||
|
+ QCA8K_REG_GOL_TRUNK_MEMBER(id) |
|
||
|
+ QCA8K_REG_GOL_TRUNK_EN(id),
|
||
|
+ !val << QCA8K_REG_GOL_TRUNK_SHIFT(id) |
|
||
|
+ val << QCA8K_REG_GOL_TRUNK_SHIFT(id));
|
||
|
+
|
||
|
+ /* Search empty member if adding or port on deleting */
|
||
|
+ for (i = 0; i < QCA8K_NUM_PORTS_FOR_LAG; i++) {
|
||
|
+ ret = regmap_read(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL(id), &val);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ val >>= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i);
|
||
|
+ val &= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_MASK;
|
||
|
+
|
||
|
+ if (delete) {
|
||
|
+ /* If port flagged to be disabled assume this member is
|
||
|
+ * empty
|
||
|
+ */
|
||
|
+ if (val != QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN_MASK)
|
||
|
+ continue;
|
||
|
+
|
||
|
+ val &= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT_MASK;
|
||
|
+ if (val != port)
|
||
|
+ continue;
|
||
|
+ } else {
|
||
|
+ /* If port flagged to be enabled assume this member is
|
||
|
+ * already set
|
||
|
+ */
|
||
|
+ if (val == QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN_MASK)
|
||
|
+ continue;
|
||
|
+ }
|
||
|
+
|
||
|
+ /* We have found the member to add/remove */
|
||
|
+ break;
|
||
|
+ }
|
||
|
+
|
||
|
+ /* Set port in the correct port mask or disable port if in delete mode */
|
||
|
+ return regmap_update_bits(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL(id),
|
||
|
+ QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN(id, i) |
|
||
|
+ QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT(id, i),
|
||
|
+ !delete << QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i) |
|
||
|
+ port << QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i));
|
||
|
+}
|
||
|
+
|
||
|
+int qca8k_port_lag_join(struct dsa_switch *ds, int port, struct net_device *lag,
|
||
|
+ struct netdev_lag_upper_info *info)
|
||
|
+{
|
||
|
+ int ret;
|
||
|
+
|
||
|
+ if (!qca8k_lag_can_offload(ds, lag, info))
|
||
|
+ return -EOPNOTSUPP;
|
||
|
+
|
||
|
+ ret = qca8k_lag_setup_hash(ds, lag, info);
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ return qca8k_lag_refresh_portmap(ds, port, lag, false);
|
||
|
+}
|
||
|
+
|
||
|
+int qca8k_port_lag_leave(struct dsa_switch *ds, int port,
|
||
|
+ struct net_device *lag)
|
||
|
+{
|
||
|
+ return qca8k_lag_refresh_portmap(ds, port, lag, true);
|
||
|
+}
|
||
|
--- a/drivers/net/dsa/qca/qca8k.h
|
||
|
+++ b/drivers/net/dsa/qca/qca8k.h
|
||
|
@@ -495,4 +495,10 @@ int qca8k_port_vlan_add(struct dsa_switc
|
||
|
int qca8k_port_vlan_del(struct dsa_switch *ds, int port,
|
||
|
const struct switchdev_obj_port_vlan *vlan);
|
||
|
|
||
|
+/* Common port LAG function */
|
||
|
+int qca8k_port_lag_join(struct dsa_switch *ds, int port, struct net_device *lag,
|
||
|
+ struct netdev_lag_upper_info *info);
|
||
|
+int qca8k_port_lag_leave(struct dsa_switch *ds, int port,
|
||
|
+ struct net_device *lag);
|
||
|
+
|
||
|
#endif /* __QCA8K_H */
|