 7ace30aeb6
			
		
	
	7ace30aeb6
	
	
	
		
			
			Backport upstream code split patch for qca8k needed for ipq40xx target to correctly implement a DSA driver. Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
		
			
				
	
	
		
			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 */
 |