[PATCH net-next v3 5/8] net: qualcomm: ipqess: add bridge offloading features to the IPQESS driver

Wojciech Drewek wojciech.drewek at intel.com
Thu Nov 16 05:23:29 PST 2023



On 14.11.2023 11:55, Romain Gantois wrote:
> The IPQ4019 ESS switch is capable of offloading various bridge features.
> Add netdev and switchdev notifiers to offload bridge uppers, link state
> changes, FDB and MDB accesses and VLANs.
> 
> Signed-off-by: Romain Gantois <romain.gantois at bootlin.com>
> ---
>  drivers/net/dsa/qca/qca8k-8xxx.c              |   49 +-
>  drivers/net/dsa/qca/qca8k-common.c            |   42 +-
>  drivers/net/ethernet/qualcomm/Kconfig         |    1 +
>  drivers/net/ethernet/qualcomm/ipqess/Makefile |    2 +-
>  .../ethernet/qualcomm/ipqess/ipqess_edma.c    |    7 +
>  .../qualcomm/ipqess/ipqess_notifiers.c        |  306 +++++
>  .../ethernet/qualcomm/ipqess/ipqess_port.c    | 1050 +++++++++++++++--
>  .../ethernet/qualcomm/ipqess/ipqess_port.h    |   33 +
>  .../ethernet/qualcomm/ipqess/ipqess_switch.c  |   15 +-
>  include/linux/dsa/qca8k.h                     |   16 +-
>  10 files changed, 1391 insertions(+), 130 deletions(-)
>  create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_notifiers.c
> 

<...>

>  
> diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_notifiers.c b/drivers/net/ethernet/qualcomm/ipqess/ipqess_notifiers.c
> new file mode 100644
> index 000000000000..77f6d79c2ff6
> --- /dev/null
> +++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_notifiers.c
> @@ -0,0 +1,306 @@
> +// SPDX-License-Identifier: GPL-2.0 OR ISC
> +/*
> + * Copyright (c) 2023, Romain Gantois <romain.gantois at bootlin.com>
> + * Based on net/dsa/slave.c
> + */
> +
> +#include <net/switchdev.h>
> +
> +#include <linux/etherdevice.h>
> +#include <linux/if_vlan.h>
> +#include <linux/if_hsr.h>
> +
> +#include "ipqess_notifiers.h"
> +#include "ipqess_port.h"
> +
> +static struct workqueue_struct *ipqess_owq;
> +
> +static bool ipqess_schedule_work(struct work_struct *work)
> +{
> +	return queue_work(ipqess_owq, work);
> +}
> +
> +void ipqess_flush_workqueue(void)
> +{
> +	flush_workqueue(ipqess_owq);
> +}

Those two functions look like overkill to me

> +
> +/* switchdev */
> +
> +static int ipqess_port_fdb_event(struct net_device *netdev,
> +				 struct net_device *orig_netdev,
> +				 unsigned long event, const void *ctx,
> +				 const struct switchdev_notifier_fdb_info *fdb_info)
> +{
> +	struct ipqess_switchdev_event_work *switchdev_work;
> +	struct ipqess_port *port = netdev_priv(netdev);
> +	bool host_addr = fdb_info->is_local;
> +
> +	if (ctx && ctx != port)
> +		return 0;
> +
> +	if (!port->bridge)
> +		return 0;
> +
> +	if (switchdev_fdb_is_dynamically_learned(fdb_info) &&
> +	    ipqess_port_offloads_bridge_port(port, orig_netdev))
> +		return 0;
> +
> +	/* Also treat FDB entries on foreign interfaces bridged with us as host
> +	 * addresses.
> +	 */
> +	if (ipqess_port_dev_is_foreign(netdev, orig_netdev))
> +		host_addr = true;
> +
> +	switchdev_work = kzalloc(sizeof(*switchdev_work), GFP_ATOMIC);
> +	if (!switchdev_work)
> +		return -ENOMEM;
> +
> +	netdev_dbg(netdev, "%s FDB entry towards %s, addr %pM vid %d%s\n",
> +		   event == SWITCHDEV_FDB_ADD_TO_DEVICE ? "Adding" : "Deleting",
> +		   orig_netdev->name, fdb_info->addr, fdb_info->vid,
> +		   host_addr ? " as host address" : "");
> +
> +	INIT_WORK(&switchdev_work->work, ipqess_port_switchdev_event_work);
> +	switchdev_work->event = event;
> +	switchdev_work->netdev = netdev;
> +	switchdev_work->orig_netdev = orig_netdev;
> +
> +	ether_addr_copy(switchdev_work->addr, fdb_info->addr);
> +	switchdev_work->vid = fdb_info->vid;
> +	switchdev_work->host_addr = host_addr;
> +
> +	ipqess_schedule_work(&switchdev_work->work);
> +
> +	return 0;
> +}
> +
> +/* Called under rcu_read_lock() */
> +static int ipqess_switchdev_event(struct notifier_block *unused,
> +				  unsigned long event, void *ptr)
> +{
> +	struct net_device *netdev = switchdev_notifier_info_to_dev(ptr);
> +	int err;
> +
> +	switch (event) {
> +	case SWITCHDEV_PORT_ATTR_SET:
> +		err = switchdev_handle_port_attr_set(netdev, ptr,
> +						     ipqess_port_recognize_netdev,
> +						     ipqess_port_attr_set);
> +		return notifier_from_errno(err);
> +	case SWITCHDEV_FDB_ADD_TO_DEVICE:
> +	case SWITCHDEV_FDB_DEL_TO_DEVICE:
> +		err = switchdev_handle_fdb_event_to_device(netdev, event, ptr,
> +							   ipqess_port_recognize_netdev,
> +							   ipqess_port_dev_is_foreign,
> +							   ipqess_port_fdb_event);
> +		return notifier_from_errno(err);
> +	default:
> +		return NOTIFY_DONE;
> +	}
> +
> +	return NOTIFY_OK;
> +}
> +
> +static int ipqess_switchdev_blocking_event(struct notifier_block *unused,
> +					   unsigned long event, void *ptr)
> +{
> +	struct net_device *netdev = switchdev_notifier_info_to_dev(ptr);
> +	int err;
> +
> +	switch (event) {
> +	case SWITCHDEV_PORT_OBJ_ADD:
> +		err = switchdev_handle_port_obj_add_foreign(netdev, ptr,
> +							    ipqess_port_recognize_netdev,
> +							    ipqess_port_dev_is_foreign,
> +							    ipqess_port_obj_add);
> +		return notifier_from_errno(err);
> +	case SWITCHDEV_PORT_OBJ_DEL:
> +		err = switchdev_handle_port_obj_del_foreign(netdev, ptr,
> +							    ipqess_port_recognize_netdev,
> +							    ipqess_port_dev_is_foreign,
> +							    ipqess_port_obj_del);
> +		return notifier_from_errno(err);
> +	case SWITCHDEV_PORT_ATTR_SET:
> +		err = switchdev_handle_port_attr_set(netdev, ptr,
> +						     ipqess_port_recognize_netdev,
> +						     ipqess_port_attr_set);
> +		return notifier_from_errno(err);
> +	}
> +
> +	return NOTIFY_DONE;
> +}
> +
> +/* netdevice */
> +
> +static int ipqess_port_changeupper(struct net_device *netdev,
> +				   struct netdev_notifier_changeupper_info *info)
> +{
> +	struct ipqess_port *port = netdev_priv(netdev);
> +	struct netlink_ext_ack *extack;
> +	int err = NOTIFY_DONE;
> +
> +	if (!ipqess_port_recognize_netdev(netdev))

This already done in ipqess_netdevice_event

> +		return err;
> +
> +	extack = netdev_notifier_info_to_extack(&info->info);
> +
> +	if (netif_is_bridge_master(info->upper_dev)) {
> +		if (info->linking) {
> +			err = ipqess_port_bridge_join(port, info->upper_dev, extack);
> +			if (err == -EOPNOTSUPP) {
> +				NL_SET_ERR_MSG_WEAK_MOD(extack,
> +							"Offloading not supported");
> +				err = NOTIFY_DONE;
> +			}
> +			err = notifier_from_errno(err);
> +		} else {
> +			ipqess_port_bridge_leave(port, info->upper_dev);
> +			err = NOTIFY_OK;
> +		}
> +	} else if (netif_is_lag_master(info->upper_dev)) {
> +		/* LAG offloading is not supported by this driver */
> +		NL_SET_ERR_MSG_WEAK_MOD(extack,
> +					"Offloading not supported");
> +		err = NOTIFY_DONE;
> +	} else if (is_hsr_master(info->upper_dev)) {
> +		if (info->linking) {
> +			NL_SET_ERR_MSG_WEAK_MOD(extack,
> +						"Offloading not supported");
> +			err = NOTIFY_DONE;
> +		} else {
> +			err = NOTIFY_OK;
> +		}
> +	}
> +
> +	return err;
> +}
> +
> +static int ipqess_port_prechangeupper(struct net_device *netdev,
> +				      struct netdev_notifier_changeupper_info *info)
> +{
> +	struct ipqess_port *port = netdev_priv(netdev);
> +	struct net_device *brport_dev;
> +	int err;
> +
> +	/* sanity check */
> +	if (is_vlan_dev(info->upper_dev)) {
> +		err = ipqess_port_check_8021q_upper(netdev, info);
> +		if (notifier_to_errno(err))
> +			return err;
> +	}
> +
> +	/* prechangeupper */
> +	if (netif_is_bridge_master(info->upper_dev) && !info->linking)
> +		brport_dev = ipqess_port_get_bridged_netdev(port);
> +	else
> +		return NOTIFY_DONE;
> +
> +	if (!brport_dev)
> +		return NOTIFY_DONE;
> +
> +	switchdev_bridge_port_unoffload(brport_dev, port,
> +					&ipqess_switchdev_notifier,
> +					&ipqess_switchdev_blocking_notifier);
> +
> +	ipqess_flush_workqueue();
> +
> +	return NOTIFY_DONE;
> +}
> +
> +static int ipqess_netdevice_event(struct notifier_block *nb,
> +				  unsigned long event, void *ptr)
> +{
> +	struct net_device *netdev = netdev_notifier_info_to_dev(ptr);
> +	int err;
> +
> +	if (!ipqess_port_recognize_netdev(netdev))
> +		return NOTIFY_DONE;
> +
> +	switch (event) {
> +	case NETDEV_PRECHANGEUPPER: {
> +		err = ipqess_port_prechangeupper(netdev, ptr);
> +		if (notifier_to_errno(err))
> +			return err;
> +
> +		break;
> +	}
> +
> +	case NETDEV_CHANGEUPPER: {
> +		err = ipqess_port_changeupper(netdev, ptr);
> +		if (notifier_to_errno(err))
> +			return err;
> +
> +		break;
> +	}
> +
> +	/* Handling this is only useful for LAG offloading, which this driver
> +	 * doesn't support
> +	 */
> +	case NETDEV_CHANGELOWERSTATE:
> +		return NOTIFY_DONE;
> +	case NETDEV_CHANGE:
> +	case NETDEV_UP:
> +	case NETDEV_GOING_DOWN:
> +	default:
> +		break;
> +	}
> +
> +	return NOTIFY_OK;
> +}
> +
> +struct notifier_block ipqess_switchdev_notifier = {
> +	.notifier_call = ipqess_switchdev_event,
> +};
> +
> +struct notifier_block ipqess_switchdev_blocking_notifier = {
> +	.notifier_call = ipqess_switchdev_blocking_event,
> +};
> +
> +static struct notifier_block ipqess_nb __read_mostly = {
> +	.notifier_call = ipqess_netdevice_event,
> +};
> +
> +int ipqess_notifiers_register(void)
> +{
> +	int err;
> +
> +	ipqess_owq = alloc_ordered_workqueue("ipqess_ordered",
> +					     WQ_MEM_RECLAIM);
> +	if (!ipqess_owq)
> +		return -ENOMEM;
> +
> +	err = register_netdevice_notifier(&ipqess_nb);
> +	if (err)
> +		goto err_netdev_nb;
> +
> +	err = register_switchdev_notifier(&ipqess_switchdev_notifier);
> +	if (err)
> +		goto err_switchdev_nb;
> +
> +	err = register_switchdev_blocking_notifier(&ipqess_switchdev_blocking_notifier);
> +	if (err)
> +		goto err_switchdev_blocking_nb;
> +
> +	return 0;
> +
> +err_switchdev_blocking_nb:
> +	unregister_switchdev_notifier(&ipqess_switchdev_notifier);
> +err_switchdev_nb:
> +	unregister_netdevice_notifier(&ipqess_nb);
> +err_netdev_nb:
> +	destroy_workqueue(ipqess_owq);
> +
> +	return err;
> +}
> +EXPORT_SYMBOL(ipqess_notifiers_register);
> +
> +void ipqess_notifiers_unregister(void)
> +{
> +	unregister_switchdev_blocking_notifier(&ipqess_switchdev_blocking_notifier);
> +	unregister_switchdev_notifier(&ipqess_switchdev_notifier);
> +	unregister_netdevice_notifier(&ipqess_nb);
> +
> +	destroy_workqueue(ipqess_owq);
> +}
> +EXPORT_SYMBOL(ipqess_notifiers_unregister);
> diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
> index 52d7baa7cae0..29420820c3d8 100644
> --- a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
> +++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
> @@ -23,50 +23,50 @@ static struct device_type ipqess_port_type = {
>  	.name	= "switch",
>  };
>  
> +struct net_device *ipqess_port_get_bridged_netdev(const struct ipqess_port *port)
> +{
> +	if (!port->bridge)
> +		return NULL;
> +
> +	return port->netdev;
> +}
> +
>  /* netdev ops */
>  
> +static void ipqess_port_notify_bridge_fdb_flush(const struct ipqess_port *port,
> +						u16 vid)
> +{
> +	struct net_device *brport_dev = ipqess_port_get_bridged_netdev(port);
> +	struct switchdev_notifier_fdb_info info = {
> +		.vid = vid,
> +	};
> +
> +	/* When the port becomes standalone it has already left the bridge.
> +	 * Don't notify the bridge in that case.
> +	 */
> +	if (!brport_dev)
> +		return;
> +
> +	call_switchdev_notifiers(SWITCHDEV_FDB_FLUSH_TO_BRIDGE,
> +				 brport_dev, &info.info, NULL);
> +}
> +
>  static void ipqess_port_fast_age(const struct ipqess_port *port)
>  {
>  	struct qca8k_priv *priv = port->sw->priv;
>  
> -	mutex_lock(&priv->reg_mutex);
> -	qca8k_fdb_access(priv, QCA8K_FDB_FLUSH_PORT, port->index);
> -	mutex_unlock(&priv->reg_mutex);
> +	qca8k_port_fast_age(priv, port->index);
> +
> +	/* Flush all VLANs */
> +	ipqess_port_notify_bridge_fdb_flush(port, 0);
>  }
>  
>  static void ipqess_port_stp_state_set(struct ipqess_port *port,
>  				      u8 state)
>  {
>  	struct qca8k_priv *priv = port->sw->priv;
> -	u32 stp_state;
> -	int err;
>  
> -	switch (state) {
> -	case BR_STATE_DISABLED:
> -		stp_state = QCA8K_PORT_LOOKUP_STATE_DISABLED;
> -		break;
> -	case BR_STATE_BLOCKING:
> -		stp_state = QCA8K_PORT_LOOKUP_STATE_BLOCKING;
> -		break;
> -	case BR_STATE_LISTENING:
> -		stp_state = QCA8K_PORT_LOOKUP_STATE_LISTENING;
> -		break;
> -	case BR_STATE_LEARNING:
> -		stp_state = QCA8K_PORT_LOOKUP_STATE_LEARNING;
> -		break;
> -	case BR_STATE_FORWARDING:
> -	default:
> -		stp_state = QCA8K_PORT_LOOKUP_STATE_FORWARD;
> -		break;
> -	}
> -
> -	err = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port->index),
> -			QCA8K_PORT_LOOKUP_STATE_MASK, stp_state);
> -
> -	if (err)
> -		dev_warn(priv->dev,
> -			 "failed to set STP state %d for port %d: err %d\n",
> -			 stp_state, port->index, err);
> +	qca8k_port_stp_state_set(priv, port->index, state, false, false);
>  }
>  
>  static void ipqess_port_set_state_now(struct ipqess_port *port,
> @@ -93,7 +93,8 @@ static int ipqess_port_enable_rt(struct ipqess_port *port,
>  
>  	phy_support_asym_pause(phy);
>  
> -	ipqess_port_set_state_now(port, BR_STATE_FORWARDING, false);
> +	if (!port->bridge)
> +		ipqess_port_set_state_now(port, BR_STATE_FORWARDING, false);
>  
>  	if (port->pl)
>  		phylink_start(port->pl);
> @@ -108,7 +109,8 @@ static void ipqess_port_disable_rt(struct ipqess_port *port)
>  	if (port->pl)
>  		phylink_stop(port->pl);
>  
> -	ipqess_port_set_state_now(port, BR_STATE_DISABLED, false);
> +	if (!port->bridge)
> +		ipqess_port_set_state_now(port, BR_STATE_DISABLED, false);
>  
>  	qca8k_port_set_status(priv, port->index, 0);
>  	priv->port_enabled_map &= ~BIT(port->index);
> @@ -204,34 +206,9 @@ static int ipqess_port_change_mtu(struct net_device *dev, int new_mtu)
>  	return 0;
>  }
>  
> -static int ipqess_port_do_vlan_add(struct qca8k_priv *priv, int port_index,
> -				   const struct switchdev_obj_port_vlan *vlan,
> -				   struct netlink_ext_ack *extack)
> +static inline struct net_device *ipqess_port_bridge_dev_get(struct ipqess_port *port)
>  {
> -	bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
> -	bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;
> -	int ret;
> -
> -	ret = qca8k_vlan_add(priv, port_index, vlan->vid, untagged);
> -	if (ret) {
> -		dev_err(priv->dev, "Failed to add VLAN to port %d (%d)", port_index,
> -			ret);
> -		return ret;
> -	}
> -
> -	if (pvid) {
> -		ret = qca8k_rmw(priv, QCA8K_EGRESS_VLAN(port_index),
> -				QCA8K_EGREES_VLAN_PORT_MASK(port_index),
> -				QCA8K_EGREES_VLAN_PORT(port_index, vlan->vid));
> -		if (ret)
> -			return ret;
> -
> -		ret = qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(port_index),
> -				  QCA8K_PORT_VLAN_CVID(vlan->vid) |
> -				  QCA8K_PORT_VLAN_SVID(vlan->vid));
> -	}
> -
> -	return ret;
> +	return port->bridge ? port->bridge->netdev : NULL;
>  }
>  
>  static int ipqess_port_vlan_rx_add_vid(struct net_device *dev, __be16 proto,
> @@ -248,7 +225,7 @@ static int ipqess_port_vlan_rx_add_vid(struct net_device *dev, __be16 proto,
>  	int ret;
>  
>  	/* User port... */
> -	ret = ipqess_port_do_vlan_add(port->sw->priv, port->index, &vlan, &extack);
> +	ret = qca8k_port_vlan_add(port->sw->priv, port->index, &vlan, &extack);
>  	if (ret) {
>  		if (extack._msg)
>  			netdev_err(dev, "%s\n", extack._msg);
> @@ -256,7 +233,7 @@ static int ipqess_port_vlan_rx_add_vid(struct net_device *dev, __be16 proto,
>  	}
>  
>  	/* And CPU port... */
> -	ret = ipqess_port_do_vlan_add(port->sw->priv, 0, &vlan, &extack);
> +	ret = qca8k_port_vlan_add(port->sw->priv, 0, &vlan, &extack);
>  	if (ret) {
>  		if (extack._msg)
>  			netdev_err(dev, "CPU port %d: %s\n", 0, extack._msg);
> @@ -340,24 +317,13 @@ ipqess_port_fdb_dump(struct sk_buff *skb, struct netlink_callback *cb,
>  		.cb = cb,
>  		.idx = *idx,
>  	};
> -	int cnt = QCA8K_NUM_FDB_RECORDS;
> -	struct qca8k_fdb _fdb = { 0 };
> -	bool is_static;
>  	int ret = 0;
>  
> -	mutex_lock(&priv->reg_mutex);
> -	while (cnt-- && !qca8k_fdb_next(priv, &_fdb, port->index)) {
> -		if (!_fdb.aging)
> -			break;
> -		is_static = (_fdb.aging == QCA8K_ATU_STATUS_STATIC);
> -		ret = ipqess_port_fdb_do_dump(_fdb.mac, _fdb.vid, is_static, &dump);
> -		if (ret)
> -			break;
> -	}
> -	mutex_unlock(&priv->reg_mutex);
> -
>  	*idx = dump.idx;
>  
> +	ret = qca8k_port_fdb_dump(priv, port->index, ipqess_port_fdb_do_dump,
> +				  &dump);
> +
>  	return ret;
>  }
>  
> @@ -374,6 +340,882 @@ static const struct net_device_ops ipqess_port_netdev_ops = {
>  	.ndo_fdb_dump           = ipqess_port_fdb_dump,
>  };
>  
> +/* Bridge ops */
> +
> +static int ipqess_port_bridge_alloc(struct ipqess_port *port,
> +				    struct net_device *br,
> +				    struct netlink_ext_ack *extack)
> +{
> +	struct ipqess_bridge *bridge;
> +
> +	bridge = kzalloc(sizeof(*bridge), GFP_KERNEL);
> +	if (!bridge)
> +		return -ENOMEM;
> +
> +	refcount_set(&bridge->refcount, 1);
> +
> +	bridge->netdev = br;
> +
> +	port->bridge = bridge;
> +
> +	return 0;
> +}
> +
> +/* Must be called under rcu_read_lock() */
> +static bool ipqess_port_can_apply_vlan_filtering(struct ipqess_port *port,
> +						 bool vlan_filtering,
> +						 struct netlink_ext_ack *extack)
> +{
> +	int err;
> +
> +	/* VLAN awareness was off, so the question is "can we turn it on".
> +	 * We may have had 8021q uppers, those need to go. Make sure we don't
> +	 * enter an inconsistent state: deny changing the VLAN awareness state
> +	 * as long as we have 8021q uppers.
> +	 */
> +	if (vlan_filtering) {

	if (!vlan_filtering)
		return true;

One indentation level less

> +		struct net_device *br = ipqess_port_bridge_dev_get(port);
> +		struct net_device *upper_dev, *netdev = port->netdev;
> +		struct list_head *iter;
> +
> +		netdev_for_each_upper_dev_rcu(netdev, upper_dev, iter) {
> +			struct bridge_vlan_info br_info;
> +			u16 vid;
> +
> +			if (!is_vlan_dev(upper_dev))
> +				continue;
> +
> +			vid = vlan_dev_vlan_id(upper_dev);
> +
> +			/* br_vlan_get_info() returns -EINVAL or -ENOENT if the
> +			 * device, respectively the VID is not found, returning
> +			 * 0 means success, which is a failure for us here.
> +			 */
> +			err = br_vlan_get_info(br, vid, &br_info);
> +			if (err == 0) {
> +				NL_SET_ERR_MSG_MOD(extack,
> +						   "Must first remove VLAN uppers having VIDs also present in bridge");
> +				return false;
> +			}
> +		}
> +	}
> +
> +	/* VLAN filtering is not global so we can just return true here */
> +	return true;
> +}
> +
> +static int ipqess_port_restore_vlan(struct net_device *vdev, int vid, void *arg)
> +{
> +	__be16 proto = vdev ? vlan_dev_vlan_proto(vdev) : htons(ETH_P_8021Q);
> +
> +	return ipqess_port_vlan_rx_add_vid(arg, proto, vid);
> +}
> +
> +static int ipqess_port_clear_vlan(struct net_device *vdev, int vid, void *arg)
> +{
> +	__be16 proto = vdev ? vlan_dev_vlan_proto(vdev) : htons(ETH_P_8021Q);
> +
> +	return ipqess_port_vlan_rx_kill_vid(arg, proto, vid);
> +}
> +
> +/* Keep the VLAN RX filtering list in sync with the hardware only if VLAN
> + * filtering is enabled.
> + */
> +static int ipqess_port_manage_vlan_filtering(struct net_device *netdev,
> +					     bool vlan_filtering)
> +{
> +	int err;
> +
> +	if (vlan_filtering) {
> +		netdev->features |= NETIF_F_HW_VLAN_CTAG_FILTER;
> +
> +		err = vlan_for_each(netdev, ipqess_port_restore_vlan, netdev);
> +		if (err) {
> +			netdev_err(netdev,
> +				   "Failed to restore all VLAN's successfully, error %d\n",
> +				   err);
> +			vlan_for_each(netdev, ipqess_port_clear_vlan, netdev);
> +			netdev->features &= ~NETIF_F_HW_VLAN_CTAG_FILTER;
> +			return err;
> +		}
> +	} else {
> +		err = vlan_for_each(netdev, ipqess_port_clear_vlan, netdev);
> +		if (err)
> +			return err;
> +
> +		netdev->features &= ~NETIF_F_HW_VLAN_CTAG_FILTER;
> +	}
> +
> +	return 0;
> +}
> +
> +static int ipqess_port_vlan_filtering(struct ipqess_port *port,
> +				      bool vlan_filtering,
> +				      struct netlink_ext_ack *extack)
> +{
> +	bool old_vlan_filtering = port->vlan_filtering;
> +	bool apply;
> +	int err;
> +
> +	/* We are called from ipqess_port_switchdev_blocking_event(),
> +	 * which is not under rcu_read_lock(), unlike
> +	 * ipqess_port_switchdev_event().
> +	 */
> +	rcu_read_lock();

Again, I would move locking inside ipqess_port_can_apply_vlan_filtering

> +	apply = ipqess_port_can_apply_vlan_filtering(port, vlan_filtering, extack);
> +	rcu_read_unlock();
> +	if (!apply)
> +		return -EINVAL;
> +
> +	if (old_vlan_filtering == vlan_filtering)
> +		return 0;
> +
> +	err = qca8k_port_vlan_filtering(port->sw->priv, port->index,
> +					vlan_filtering);
> +
> +	if (err)
> +		return err;
> +
> +	port->vlan_filtering = vlan_filtering;
> +
> +	err = ipqess_port_manage_vlan_filtering(port->netdev,
> +						vlan_filtering);
> +	if (err)
> +		goto restore;
> +
> +	return 0;
> +
> +restore:
> +	err = qca8k_port_vlan_filtering(port->sw->priv, port->index,
> +					old_vlan_filtering);
> +	port->vlan_filtering = old_vlan_filtering;
> +
> +	return err;
> +}
> +
> +static void ipqess_port_reset_vlan_filtering(struct ipqess_port *port,
> +					     struct ipqess_bridge *bridge)
> +{
> +	struct netlink_ext_ack extack = {0};
> +	bool change_vlan_filtering = false;
> +	bool vlan_filtering;
> +	int err;
> +
> +	if (br_vlan_enabled(bridge->netdev)) {
> +		change_vlan_filtering = true;
> +		vlan_filtering = false;
> +	}
> +
> +	if (!change_vlan_filtering)
> +		return;
> +
> +	err = ipqess_port_vlan_filtering(port, vlan_filtering, &extack);
> +	if (extack._msg) {
> +		dev_err(&port->netdev->dev, "port %d: %s\n", port->index,
> +			extack._msg);
> +	}

No need for braces

> +	if (err && err != -EOPNOTSUPP) {
> +		dev_err(&port->netdev->dev,
> +			"port %d failed to reset VLAN filtering to %d: %pe\n",
> +			port->index, vlan_filtering, ERR_PTR(err));
> +	}

Same

> +}
> +
> +static int ipqess_port_ageing_time(struct ipqess_port *port,
> +				   clock_t ageing_clock)
> +{
> +	unsigned long ageing_jiffies = clock_t_to_jiffies(ageing_clock);
> +	unsigned int ageing_time = jiffies_to_msecs(ageing_jiffies);
> +
> +	if (ageing_time < IPQESS_SWITCH_AGEING_TIME_MIN ||
> +	    ageing_time > IPQESS_SWITCH_AGEING_TIME_MAX)
> +		return -ERANGE;
> +
> +	/* Program the fastest ageing time in case of multiple bridges */
> +	ageing_time = ipqess_switch_fastest_ageing_time(port->sw, ageing_time);
> +
> +	port->ageing_time = ageing_time;
> +	return ipqess_set_ageing_time(port->sw, ageing_time);
> +}
> +
> +static int ipqess_port_switchdev_sync_attrs(struct ipqess_port *port,
> +					    struct netlink_ext_ack *extack)
> +{
> +	struct net_device *brport_dev = ipqess_port_get_bridged_netdev(port);
> +	struct net_device *br = ipqess_port_bridge_dev_get(port);
> +	int err;
> +
> +	ipqess_port_set_state_now(port, br_port_get_stp_state(brport_dev), false);
> +
> +	err = ipqess_port_vlan_filtering(port, br_vlan_enabled(br), extack);
> +	if (err)
> +		return err;
> +
> +	err = ipqess_port_ageing_time(port, br_get_ageing_time(br));
> +	if (err && err != -EOPNOTSUPP)
> +		return err;
> +
> +	return 0;
> +}
> +
> +static void ipqess_port_switchdev_unsync_attrs(struct ipqess_port *port,
> +					       struct ipqess_bridge *bridge)
> +{
> +	/* Port left the bridge, put in BR_STATE_DISABLED by the bridge layer,
> +	 * so allow it to be in BR_STATE_FORWARDING to be kept functional
> +	 */
> +	ipqess_port_set_state_now(port, BR_STATE_FORWARDING, true);
> +
> +	ipqess_port_reset_vlan_filtering(port, bridge);
> +
> +	/* Ageing time is global to the switch chip, so don't change it
> +	 * here because we have no good reason (or value) to change it to.
> +	 */
> +}
> +
> +static inline bool ipqess_port_offloads_bridge(struct ipqess_port *port,
> +					       const struct ipqess_bridge *bridge)
> +{
> +	return ipqess_port_bridge_dev_get(port) == bridge->netdev;
> +}
> +
> +bool ipqess_port_offloads_bridge_port(struct ipqess_port *port,
> +				      const struct net_device *netdev)
> +{
> +	return ipqess_port_get_bridged_netdev(port) == netdev;
> +}
> +
> +static inline bool
> +ipqess_port_offloads_bridge_dev(struct ipqess_port *port,
> +				const struct net_device *bridge_dev)
> +{
> +	/* QCA8K ports connected to a bridge, and event was emitted
> +	 * for the bridge.
> +	 */
> +	return ipqess_port_bridge_dev_get(port) == bridge_dev;
> +}
> +
> +static void ipqess_port_bridge_destroy(struct ipqess_port *port,
> +				       const struct net_device *br)
> +{
> +	struct ipqess_bridge *bridge = port->bridge;
> +
> +	port->bridge = NULL;
> +
> +	if (!refcount_dec_and_test(&bridge->refcount))
> +		return;
> +
> +	kfree(bridge);
> +}
> +
> +int ipqess_port_bridge_join(struct ipqess_port *port, struct net_device *br,
> +			    struct netlink_ext_ack *extack)
> +{
> +	struct ipqess_switch *sw = port->sw;
> +	struct ipqess_bridge *bridge = NULL;
> +	struct qca8k_priv *priv = sw->priv;
> +	struct ipqess_port *other_port;
> +	struct net_device *brport_dev;
> +	int port_id = port->index;
> +	int port_mask = 0;
> +	int i, err;
> +
> +	/* QCA8K doesn't support MST */
> +	if (br_mst_enabled(br)) {
> +		err = -EOPNOTSUPP;
> +		goto out_err;
> +	}
> +
> +	/* Check if we already registered this bridge with
> +	 * another switch port
> +	 */
> +	for (i = 0; i < IPQESS_SWITCH_MAX_PORTS; i++) {
> +		other_port = sw->port_list[i];
> +		if (other_port && other_port->bridge &&
> +		    other_port->bridge->netdev == br)
> +			bridge = other_port->bridge;
> +	}
> +
> +	if (bridge) {
> +		refcount_inc(&bridge->refcount);
> +		port->bridge = bridge;
> +	} else {
> +		err = ipqess_port_bridge_alloc(port, br, extack);
> +		if (err)
> +			goto out_err;

You already have ipqess_port_bridge_destroy, this could be moved to the
ipqess_port_bridge_create, 

> +	}
> +	bridge = port->bridge;
> +
> +	for (i = 1; i <= IPQESS_SWITCH_MAX_PORTS; i++) {
> +		other_port = sw->port_list[i - 1];
> +		if (!other_port || !ipqess_port_offloads_bridge(other_port, bridge))
> +			continue;
> +		/* Add this port to the portvlan mask of the other ports
> +		 * in the bridge
> +		 */
> +		err = regmap_set_bits(priv->regmap,
> +				      QCA8K_PORT_LOOKUP_CTRL(i),
> +				      BIT(port_id));
> +		if (err)
> +			goto out_rollback;
> +		if (i != port_id)
> +			port_mask |= BIT(i);
> +	}
> +	/* Also add the CPU port */
> +	err = regmap_set_bits(priv->regmap,
> +			      QCA8K_PORT_LOOKUP_CTRL(0),
> +			      BIT(port_id));
> +	port_mask |= BIT(0);
> +
> +	/* Add all other ports to this ports portvlan mask */
> +	err = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port_id),
> +			QCA8K_PORT_LOOKUP_MEMBER, port_mask);
> +	if (err)
> +		goto out_rollback;
> +
> +	brport_dev = ipqess_port_get_bridged_netdev(port);
> +
> +	err = switchdev_bridge_port_offload(brport_dev, port->netdev, port,
> +					    &ipqess_switchdev_notifier,
> +					    &ipqess_switchdev_blocking_notifier,
> +					    false, extack);
> +	if (err)
> +		goto out_rollback_unbridge;
> +
> +	err = ipqess_port_switchdev_sync_attrs(port, extack);
> +	if (err)
> +		goto out_rollback_unoffload;
> +
> +	return 0;
> +
> +out_rollback_unoffload:
> +	switchdev_bridge_port_unoffload(brport_dev, port,
> +					&ipqess_switchdev_notifier,
> +					&ipqess_switchdev_blocking_notifier);
> +	ipqess_flush_workqueue();
> +out_rollback_unbridge:
> +	for (i = 1; i <= IPQESS_SWITCH_MAX_PORTS; i++) {
> +		other_port = sw->port_list[i - 1];
> +		if (!other_port ||
> +		    !ipqess_port_offloads_bridge(other_port, port->bridge))
> +			continue;
> +		/* Remove this port from the portvlan mask of the other ports
> +		 * in the bridge
> +		 */
> +		regmap_clear_bits(priv->regmap,
> +				  QCA8K_PORT_LOOKUP_CTRL(i),
> +				  BIT(port_id));
> +	}
> +
> +	/* Set the cpu port to be the only one in the portvlan mask of
> +	 * this port
> +	 */
> +	qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port_id),
> +		  QCA8K_PORT_LOOKUP_MEMBER, BIT(0));
> +out_rollback:
> +	ipqess_port_bridge_destroy(port, br);
> +out_err:
> +	dev_err(&port->netdev->dev, "Failed to join bridge: errno %d\n", err);
> +	return err;
> +}
> +
> +void ipqess_port_bridge_leave(struct ipqess_port *port, struct net_device *br)
> +{
> +	struct ipqess_bridge *bridge = port->bridge;
> +	struct ipqess_switch *sw = port->sw;
> +	struct qca8k_priv *priv = sw->priv;
> +	struct ipqess_port *other_port;
> +	int port_id = port->index;
> +	int i;
> +
> +	/* If the port could not be offloaded to begin with, then
> +	 * there is nothing to do.
> +	 */
> +	if (!bridge)
> +		return;
> +
> +	for (i = 1; i <= IPQESS_SWITCH_MAX_PORTS; i++) {
> +		other_port = sw->port_list[i - 1];
> +		if (!other_port || !ipqess_port_offloads_bridge(other_port, bridge))
> +			continue;
> +		/* Remove this port from the portvlan mask of the other ports
> +		 * in the bridge
> +		 */
> +		regmap_clear_bits(priv->regmap,
> +				  QCA8K_PORT_LOOKUP_CTRL(i),
> +				  BIT(port_id));
> +	}
> +
> +	/* Set the cpu port to be the only one in the portvlan mask of
> +	 * this port
> +	 */
> +	qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port_id),
> +		  QCA8K_PORT_LOOKUP_MEMBER, BIT(0));
> +
> +	ipqess_port_switchdev_unsync_attrs(port, bridge);
> +
> +	/* Here the port is already unbridged. Reflect the current configuration. */
> +
> +	ipqess_port_bridge_destroy(port, br);
> +}
> +
> +int ipqess_port_attr_set(struct net_device *dev, const void *ctx,
> +			 const struct switchdev_attr *attr,
> +			 struct netlink_ext_ack *extack)
> +{
> +	struct ipqess_port *port = netdev_priv(dev);
> +	int ret;
> +
> +	if (ctx && ctx != port)
> +		return 0;
> +
> +	switch (attr->id) {
> +	case SWITCHDEV_ATTR_ID_PORT_STP_STATE:
> +		if (!ipqess_port_offloads_bridge_port(port, attr->orig_dev))
> +			return -EOPNOTSUPP;
> +
> +		ipqess_port_set_state_now(port, attr->u.stp_state, true);
> +		return 0;
> +	case SWITCHDEV_ATTR_ID_BRIDGE_VLAN_FILTERING:
> +		if (!ipqess_port_offloads_bridge_dev(port, attr->orig_dev))
> +			return -EOPNOTSUPP;
> +
> +		ret = ipqess_port_vlan_filtering(port, attr->u.vlan_filtering,
> +						 extack);
> +		break;
> +	case SWITCHDEV_ATTR_ID_BRIDGE_AGEING_TIME:
> +		if (!ipqess_port_offloads_bridge_dev(port, attr->orig_dev))
> +			return -EOPNOTSUPP;
> +
> +		ret = ipqess_port_ageing_time(port, attr->u.ageing_time);
> +		break;
> +	case SWITCHDEV_ATTR_ID_PORT_PRE_BRIDGE_FLAGS:
> +		if (!ipqess_port_offloads_bridge_port(port, attr->orig_dev))
> +			return -EOPNOTSUPP;
> +
> +		return -EINVAL;
> +	case SWITCHDEV_ATTR_ID_BRIDGE_MST:
> +	case SWITCHDEV_ATTR_ID_PORT_MST_STATE:
> +	case SWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGS:
> +	case SWITCHDEV_ATTR_ID_VLAN_MSTI:
> +	default:
> +		ret = -EOPNOTSUPP;
> +		break;
> +	}
> +
> +	return ret;
> +}
> +
> +static int ipqess_port_vlan_check_for_8021q_uppers(struct net_device *netdev,
> +						   const struct switchdev_obj_port_vlan *vlan)
> +{
> +	struct net_device *upper_dev;
> +	struct list_head *iter;
> +
> +	netdev_for_each_upper_dev_rcu(netdev, upper_dev, iter) {
> +		u16 vid;
> +
> +		if (!is_vlan_dev(upper_dev))
> +			continue;
> +
> +		vid = vlan_dev_vlan_id(upper_dev);
> +		if (vid == vlan->vid)
> +			return -EBUSY;

EBUSY feels odd to me

> +	}
> +
> +	return 0;
> +}
> +
> +static int ipqess_port_host_vlan_del(struct net_device *netdev,
> +				     const struct switchdev_obj *obj)
> +{
> +	struct ipqess_port *port = netdev_priv(netdev);
> +	struct net_device *br = ipqess_port_bridge_dev_get(port);
> +	struct switchdev_obj_port_vlan *vlan;
> +
> +	/* Do nothing if this is a software bridge */
> +	if (!port->bridge)
> +		return -EOPNOTSUPP;
> +
> +	if (br && !br_vlan_enabled(br))
> +		return 0;
> +
> +	vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);
> +
> +	return qca8k_vlan_del(port->sw->priv, 0, vlan->vid);
> +}
> +
> +static int ipqess_port_vlan_del(struct net_device *netdev,
> +				const struct switchdev_obj *obj)
> +{
> +	struct ipqess_port *port = netdev_priv(netdev);
> +	struct net_device *br = ipqess_port_bridge_dev_get(port);
> +	struct qca8k_priv *priv = port->sw->priv;
> +	struct switchdev_obj_port_vlan *vlan;
> +	int ret;
> +
> +	if (br && !br_vlan_enabled(br))
> +		return 0;
> +
> +	vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);
> +
> +	ret = qca8k_vlan_del(priv, port->index, vlan->vid);
> +
> +	if (ret)
> +		dev_err(priv->dev, "Failed to delete VLAN from port %d (%d)\n",
> +			port->index, ret);
> +
> +	return ret;
> +}
> +
> +static int ipqess_port_host_vlan_add(struct net_device *netdev,
> +				     const struct switchdev_obj *obj,
> +				     struct netlink_ext_ack *extack)
> +{
> +	struct ipqess_port *port = netdev_priv(netdev);
> +	struct switchdev_obj_port_vlan *vlan;
> +	struct net_device *br;
> +
> +	br = ipqess_port_bridge_dev_get(port);
> +	/* Do nothing is this is a software bridge */

Do nothing IF this is a software bridge

> +	if (!port->bridge)
> +		return -EOPNOTSUPP;
> +
> +	if (br && !br_vlan_enabled(br)) {
> +		NL_SET_ERR_MSG_MOD(extack, "skipping configuration of VLAN");
> +		return 0;
> +	}
> +
> +	vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);
> +
> +	vlan->flags &= ~BRIDGE_VLAN_INFO_PVID;
> +
> +	/* Add vid to CPU port */
> +	return qca8k_port_vlan_add(port->sw->priv, 0, vlan, extack);
> +}
> +
> +static int ipqess_port_vlan_add(struct net_device *netdev,
> +				const struct switchdev_obj *obj,
> +				struct netlink_ext_ack *extack)
> +{
> +	struct ipqess_port *port = netdev_priv(netdev);
> +	struct net_device *br = ipqess_port_bridge_dev_get(port);
> +	struct switchdev_obj_port_vlan *vlan;
> +	int err;
> +
> +	if (br && !br_vlan_enabled(br)) {
> +		NL_SET_ERR_MSG_MOD(extack, "skipping configuration of VLAN");
> +		return 0;
> +	}
> +
> +	vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);
> +
> +	/* Deny adding a bridge VLAN when there is already an 802.1Q upper with
> +	 * the same VID.
> +	 */
> +	if (br && br_vlan_enabled(br)) {
> +		rcu_read_lock();

I'd move locking inside ipqess_port_vlan_check_for_8021q_uppers. Someone could
use in the future and forget about locks.

> +		err = ipqess_port_vlan_check_for_8021q_uppers(netdev, vlan);
> +		rcu_read_unlock();
> +		if (err) {
> +			NL_SET_ERR_MSG_MOD(extack,
> +					   "Port already has a VLAN upper with this VID");
> +			return err;
> +		}
> +	}
> +
> +	err = qca8k_port_vlan_add(port->sw->priv, port->index, vlan, extack);
> +	return err;
> +}
> +
> +static int ipqess_port_host_mdb_del(struct ipqess_port *port,
> +				    const struct switchdev_obj_port_mdb *mdb)
> +{
> +	struct qca8k_priv *priv = port->sw->priv;
> +	const u8 *addr = mdb->addr;
> +	u16 vid = mdb->vid;
> +
> +	return qca8k_fdb_search_and_del(priv, BIT(0), addr, vid);
> +}
> +
> +static int ipqess_port_host_mdb_add(struct ipqess_port *port,
> +				    const struct switchdev_obj_port_mdb *mdb)
> +{
> +	struct qca8k_priv *priv = port->sw->priv;
> +	const u8 *addr = mdb->addr;
> +	u16 vid = mdb->vid;
> +
> +	return qca8k_fdb_search_and_insert(priv, BIT(0), addr, vid,
> +					   QCA8K_ATU_STATUS_STATIC);
> +}
> +
> +static int ipqess_port_mdb_del(struct ipqess_port *port,
> +			       const struct switchdev_obj_port_mdb *mdb)
> +{
> +	struct qca8k_priv *priv = port->sw->priv;
> +	const u8 *addr = mdb->addr;
> +	u16 vid = mdb->vid;
> +
> +	return qca8k_fdb_search_and_del(priv, BIT(port->index), addr, vid);
> +}
> +
> +static int ipqess_port_mdb_add(struct ipqess_port *port,
> +			       const struct switchdev_obj_port_mdb *mdb)
> +{
> +	struct qca8k_priv *priv = port->sw->priv;
> +	const u8 *addr = mdb->addr;
> +	u16 vid = mdb->vid;
> +
> +	return qca8k_fdb_search_and_insert(priv, BIT(port->index), addr, vid,
> +					   QCA8K_ATU_STATUS_STATIC);
> +}
> +
> +int ipqess_port_obj_add(struct net_device *netdev, const void *ctx,
> +			const struct switchdev_obj *obj,
> +			struct netlink_ext_ack *extack)
> +{
> +	struct ipqess_port *port = netdev_priv(netdev);
> +	int err;
> +
> +	if (ctx && ctx != port)
> +		return 0;
> +
> +	switch (obj->id) {
> +	case SWITCHDEV_OBJ_ID_PORT_MDB:
> +		if (!ipqess_port_offloads_bridge_port(port, obj->orig_dev))
> +			return -EOPNOTSUPP;
> +
> +		err = ipqess_port_mdb_add(port, SWITCHDEV_OBJ_PORT_MDB(obj));
> +		break;
> +	case SWITCHDEV_OBJ_ID_HOST_MDB:
> +		if (!ipqess_port_offloads_bridge_dev(port, obj->orig_dev))
> +			return -EOPNOTSUPP;
> +
> +		err = ipqess_port_host_mdb_add(port, SWITCHDEV_OBJ_PORT_MDB(obj));
> +		break;
> +	case SWITCHDEV_OBJ_ID_PORT_VLAN:
> +		if (ipqess_port_offloads_bridge_port(port, obj->orig_dev))
> +			err = ipqess_port_vlan_add(netdev, obj, extack);
> +		else
> +			err = ipqess_port_host_vlan_add(netdev, obj, extack);
> +		break;
> +	case SWITCHDEV_OBJ_ID_MRP:
> +	case SWITCHDEV_OBJ_ID_RING_ROLE_MRP:
> +	default:
> +		err = -EOPNOTSUPP;
> +		break;
> +	}
> +
> +	return err;
> +}
> +
> +int ipqess_port_obj_del(struct net_device *netdev, const void *ctx,
> +			const struct switchdev_obj *obj)
> +{
> +	struct ipqess_port *port = netdev_priv(netdev);
> +	int err;
> +
> +	if (ctx && ctx != port)
> +		return 0;
> +
> +	switch (obj->id) {
> +	case SWITCHDEV_OBJ_ID_PORT_MDB:
> +		if (!ipqess_port_offloads_bridge_port(port, obj->orig_dev))
> +			return -EOPNOTSUPP;
> +
> +		err = ipqess_port_mdb_del(port, SWITCHDEV_OBJ_PORT_MDB(obj));
> +		break;
> +	case SWITCHDEV_OBJ_ID_HOST_MDB:
> +		if (!ipqess_port_offloads_bridge_dev(port, obj->orig_dev))
> +			return -EOPNOTSUPP;
> +
> +		err = ipqess_port_host_mdb_del(port, SWITCHDEV_OBJ_PORT_MDB(obj));
> +		break;
> +	case SWITCHDEV_OBJ_ID_PORT_VLAN:
> +		if (ipqess_port_offloads_bridge_port(port, obj->orig_dev))
> +			err = ipqess_port_vlan_del(netdev, obj);
> +		else
> +			err = ipqess_port_host_vlan_del(netdev, obj);
> +		break;
> +	case SWITCHDEV_OBJ_ID_MRP:
> +	case SWITCHDEV_OBJ_ID_RING_ROLE_MRP:
> +	default:
> +		err = -EOPNOTSUPP;
> +		break;
> +	}
> +
> +	return err;
> +}
> +
> +static int ipqess_cpu_port_fdb_del(struct ipqess_port *port,
> +				   const unsigned char *addr, u16 vid)
> +{
> +	struct ipqess_mac_addr *mac_addr = NULL;
> +	struct ipqess_mac_addr *other_mac_addr;
> +	struct ipqess_switch *sw = port->sw;
> +	int err = 0;
> +
> +	mutex_lock(&sw->addr_lists_lock);
> +
> +	list_for_each_entry(other_mac_addr, &sw->fdbs, list)
> +		if (ether_addr_equal(other_mac_addr->addr, addr) && other_mac_addr->vid == vid)
> +			mac_addr = other_mac_addr;

Consider storing the fdbs in rhashtable, it's faster when you want to retrieve one given fdb.

> +
> +	if (!mac_addr) {
> +		err = -ENOENT;
> +		goto out;
> +	}
> +
> +	if (!refcount_dec_and_test(&mac_addr->refcount))
> +		goto out;
> +
> +	err = qca8k_fdb_del(sw->priv, addr, BIT(IPQESS_SWITCH_CPU_PORT), vid);
> +	if (err) {
> +		refcount_set(&mac_addr->refcount, 1);
> +		goto out;
> +	}
> +
> +	list_del(&mac_addr->list);
> +	kfree(mac_addr);
> +
> +out:
> +	mutex_unlock(&sw->addr_lists_lock);
> +
> +	return err;
> +}
> +
> +static int ipqess_cpu_port_fdb_add(struct ipqess_port *port,
> +				   const unsigned char *addr, u16 vid)
> +{
> +	struct ipqess_switch *sw = port->sw;
> +	struct ipqess_mac_addr *other_a = NULL;
> +	struct ipqess_mac_addr *a = NULL;
> +	int err = 0;
> +
> +	mutex_lock(&sw->addr_lists_lock);
> +
> +	list_for_each_entry(other_a, &sw->fdbs, list)
> +		if (ether_addr_equal(other_a->addr, addr) && other_a->vid == vid)
> +			a = other_a;
> +
> +	if (a) {
> +		refcount_inc(&a->refcount);
> +		goto out;
> +	}
> +
> +	a = kzalloc(sizeof(*a), GFP_KERNEL);
> +	if (!a) {
> +		err = -ENOMEM;
> +		goto out;
> +	}
> +
> +	err = qca8k_port_fdb_insert(port->sw->priv, addr,
> +				    BIT(IPQESS_SWITCH_CPU_PORT), vid);
> +	if (err) {
> +		kfree(a);
> +		goto out;
> +	}
> +
> +	ether_addr_copy(a->addr, addr);
> +	a->vid = vid;
> +	refcount_set(&a->refcount, 1);
> +	list_add_tail(&a->list, &sw->fdbs);
> +
> +out:
> +	mutex_unlock(&sw->addr_lists_lock);
> +
> +	return err;
> +}
> +
> +static void
> +ipqess_fdb_offload_notify(struct ipqess_switchdev_event_work *switchdev_work)
> +{
> +	struct switchdev_notifier_fdb_info info = {};
> +
> +	info.addr = switchdev_work->addr;
> +	info.vid = switchdev_work->vid;
> +	info.offloaded = true;
> +	call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED,
> +				 switchdev_work->orig_netdev, &info.info, NULL);
> +}
> +
> +void ipqess_port_switchdev_event_work(struct work_struct *work)
> +{
> +	struct ipqess_switchdev_event_work *switchdev_work =
> +		container_of(work, struct ipqess_switchdev_event_work, work);
> +	struct net_device *netdev = switchdev_work->netdev;
> +	const unsigned char *addr = switchdev_work->addr;
> +	struct ipqess_port *port = netdev_priv(netdev);
> +	struct ipqess_switch *sw = port->sw;
> +	struct qca8k_priv *priv = sw->priv;
> +	u16 vid = switchdev_work->vid;
> +	int err;
> +
> +	if (!vid)
> +		vid = QCA8K_PORT_VID_DEF;
> +
> +	switch (switchdev_work->event) {
> +	case SWITCHDEV_FDB_ADD_TO_DEVICE:
> +		if (switchdev_work->host_addr)
> +			err = ipqess_cpu_port_fdb_add(port, addr, vid);
> +		else
> +			err = qca8k_port_fdb_insert(priv, addr, BIT(port->index), vid);
> +		if (err) {
> +			dev_err(&port->netdev->dev,
> +				"port %d failed to add %pM vid %d to fdb: %d\n",
> +				port->index, addr, vid, err);
> +			break;
> +		}
> +		ipqess_fdb_offload_notify(switchdev_work);
> +		break;
> +
> +	case SWITCHDEV_FDB_DEL_TO_DEVICE:
> +		if (switchdev_work->host_addr)
> +			err = ipqess_cpu_port_fdb_del(port, addr, vid);
> +		else
> +			err = qca8k_fdb_del(priv, addr, BIT(port->index), vid);
> +		if (err) {
> +			dev_err(&port->netdev->dev,
> +				"port %d failed to delete %pM vid %d from fdb: %d\n",
> +				port->index, addr, vid, err);
> +		}
> +
> +		break;
> +	}
> +
> +	kfree(switchdev_work);
> +}
> +
> +int ipqess_port_check_8021q_upper(struct net_device *netdev,
> +				  struct netdev_notifier_changeupper_info *info)
> +{
> +	struct ipqess_port *port = netdev_priv(netdev);
> +	struct net_device *br = ipqess_port_bridge_dev_get(port);
> +	struct bridge_vlan_info br_info;
> +	struct netlink_ext_ack *extack;
> +	int err = NOTIFY_DONE;
> +	u16 vid;

RCT

> +
> +	if (!br || !br_vlan_enabled(br))
> +		return NOTIFY_DONE;
> +
> +	extack = netdev_notifier_info_to_extack(&info->info);
> +	vid = vlan_dev_vlan_id(info->upper_dev);
> +
> +	/* br_vlan_get_info() returns -EINVAL or -ENOENT if the
> +	 * device, respectively the VID is not found, returning
> +	 * 0 means success, which is a failure for us here.
> +	 */
> +	err = br_vlan_get_info(br, vid, &br_info);
> +	if (err == 0) {

Could be if (!err)

> +		NL_SET_ERR_MSG_MOD(extack,
> +				   "This VLAN is already configured by the bridge");
> +		return notifier_from_errno(-EBUSY);
> +	}
> +
> +	return NOTIFY_DONE;
> +}
> +
>  /* phylink ops */
>  
>  static void
> @@ -669,6 +1511,7 @@ int ipqess_port_register(struct ipqess_switch *sw,
>  	port->edma = NULL; /* Assigned during edma initialization */
>  	port->qid = port->index - 1;
>  	port->sw = sw;
> +	port->bridge = NULL;
>  
>  	of_get_mac_address(port_node, port->mac);
>  	if (!is_zero_ether_addr(port->mac))
> @@ -756,3 +1599,58 @@ void ipqess_port_unregister(struct ipqess_port *port)
>  	free_netdev(netdev);
>  }
>  
> +/* Utilities */
> +
> +/* Returns true if any port of this switch offloads the given net_device */
> +static bool ipqess_switch_offloads_bridge_port(struct ipqess_switch *sw,
> +					       const struct net_device *netdev)
> +{
> +	struct ipqess_port *port;
> +	int i;
> +
> +	for (i = 0; i < IPQESS_SWITCH_MAX_PORTS; i++) {
> +		port = sw->port_list[i];
> +		if (port && ipqess_port_offloads_bridge_port(port, netdev))
> +			return true;
> +	}
> +
> +	return false;
> +}
> +
> +/* Returns true if any port of this switch offloads the given bridge */
> +static inline bool
> +ipqess_switch_offloads_bridge_dev(struct ipqess_switch *sw,
> +				  const struct net_device *bridge_dev)
> +{
> +	struct ipqess_port *port;
> +	int i;
> +
> +	for (i = 0; i < IPQESS_SWITCH_MAX_PORTS; i++) {
> +		port = sw->port_list[i];
> +		if (port && ipqess_port_offloads_bridge_dev(port, bridge_dev))
> +			return true;
> +	}
> +
> +	return false;
> +}
> +
> +bool ipqess_port_recognize_netdev(const struct net_device *netdev)
> +{
> +	return netdev->netdev_ops == &ipqess_port_netdev_ops;
> +}
> +
> +bool ipqess_port_dev_is_foreign(const struct net_device *netdev,
> +				const struct net_device *foreign_netdev)
> +{
> +	struct ipqess_port *port = netdev_priv(netdev);
> +	struct ipqess_switch *sw = port->sw;
> +
> +	if (netif_is_bridge_master(foreign_netdev))
> +		return !ipqess_switch_offloads_bridge_dev(sw, foreign_netdev);
> +
> +	if (netif_is_bridge_port(foreign_netdev))
> +		return !ipqess_switch_offloads_bridge_port(sw, foreign_netdev);
> +
> +	/* Everything else is foreign */
> +	return true;
> +}
> diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
> index 19d4b5d73625..00f0dff9c39d 100644
> --- a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
> +++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
> @@ -9,6 +9,11 @@
>  #include "ipqess_edma.h"
>  #include "ipqess_switch.h"
>  
> +struct ipqess_bridge {
> +	struct net_device *netdev;
> +	refcount_t refcount;
> +};
> +
>  struct ipqess_port {
>  	u16 index;
>  	u16 qid;
> @@ -20,6 +25,7 @@ struct ipqess_port {
>  	struct device_node *dn;
>  	struct mii_bus *mii_bus;
>  	struct net_device *netdev;
> +	struct ipqess_bridge *bridge;
>  	struct devlink_port devlink_port;
>  
>  	u8       stp_state;
> @@ -62,4 +68,31 @@ void ipqess_port_unregister(struct ipqess_port *port);
>  /* Defined in ipqess_ethtool.c */
>  void ipqess_port_set_ethtool_ops(struct net_device *netdev);
>  
> +bool ipqess_port_recognize_netdev(const struct net_device *netdev);
> +bool ipqess_port_dev_is_foreign(const struct net_device *netdev,
> +				const struct net_device *foreign_netdev);
> +
> +int ipqess_port_bridge_join(struct ipqess_port *port, struct net_device *br,
> +			    struct netlink_ext_ack *extack);
> +void ipqess_port_bridge_leave(struct ipqess_port *port, struct net_device *br);
> +
> +int ipqess_port_attr_set(struct net_device *dev, const void *ctx,
> +			 const struct switchdev_attr *attr,
> +			 struct netlink_ext_ack *extack);
> +
> +void ipqess_port_switchdev_event_work(struct work_struct *work);
> +
> +int ipqess_port_check_8021q_upper(struct net_device *netdev,
> +				  struct netdev_notifier_changeupper_info *info);
> +
> +struct net_device *ipqess_port_get_bridged_netdev(const struct ipqess_port *port);
> +
> +int ipqess_port_obj_add(struct net_device *netdev, const void *ctx,
> +			const struct switchdev_obj *obj,
> +			struct netlink_ext_ack *extack);
> +int ipqess_port_obj_del(struct net_device *netdev, const void *ctx,
> +			const struct switchdev_obj *obj);
> +
> +bool ipqess_port_offloads_bridge_port(struct ipqess_port *port,
> +				      const struct net_device *netdev);
>  #endif
> diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.c b/drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.c
> index 927f834a62bc..d09d0aa8314f 100644
> --- a/drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.c
> +++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_switch.c
> @@ -80,21 +80,8 @@ unsigned int ipqess_switch_fastest_ageing_time(struct ipqess_switch *sw,
>  int ipqess_set_ageing_time(struct ipqess_switch *sw, unsigned int msecs)
>  {
>  	struct qca8k_priv *priv = sw->priv;
> -	unsigned int secs = msecs / 1000;
> -	u32 val;
>  
> -	/* AGE_TIME reg is set in 7s step */
> -	val = secs / 7;
> -
> -	/* Handle case with 0 as val to NOT disable
> -	 * learning
> -	 */
> -	if (!val)
> -		val = 1;
> -
> -	return regmap_update_bits(priv->regmap, QCA8K_REG_ATU_CTRL,
> -				  QCA8K_ATU_AGE_TIME_MASK,
> -				  QCA8K_ATU_AGE_TIME(val));
> +	return qca8k_set_ageing_time(priv, msecs);
>  }
>  
>  static struct qca8k_pcs *pcs_to_qca8k_pcs(struct phylink_pcs *pcs)
> diff --git a/include/linux/dsa/qca8k.h b/include/linux/dsa/qca8k.h
> index cafb727f4e8b..9ad016f7201e 100644
> --- a/include/linux/dsa/qca8k.h
> +++ b/include/linux/dsa/qca8k.h
> @@ -553,7 +553,8 @@ int qca8k_set_mac_eee(struct dsa_switch *ds, int port, struct ethtool_eee *eee);
>  int qca8k_get_mac_eee(struct dsa_switch *ds, int port, struct ethtool_eee *e);
>  
>  /* Common bridge function */
> -void qca8k_port_stp_state_set(struct dsa_switch *ds, int port, u8 state);
> +void qca8k_port_stp_state_set(struct qca8k_priv *priv, int port, u8 state,
> +			      bool port_learning, int set_learning);
>  int qca8k_port_pre_bridge_flags(struct dsa_switch *ds, int port,
>  				struct switchdev_brport_flags flags,
>  				struct netlink_ext_ack *extack);
> @@ -577,8 +578,8 @@ int qca8k_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu);
>  int qca8k_port_max_mtu(struct dsa_switch *ds, int port);
>  
>  /* Common fast age function */
> -void qca8k_port_fast_age(struct dsa_switch *ds, int port);
> -int qca8k_set_ageing_time(struct dsa_switch *ds, unsigned int msecs);
> +void qca8k_port_fast_age(struct qca8k_priv *priv, int port);
> +int qca8k_set_ageing_time(struct qca8k_priv *priv, unsigned int msecs);
>  
>  /* Common FDB function */
>  int qca8k_port_fdb_insert(struct qca8k_priv *priv, const u8 *addr,
> @@ -589,7 +590,7 @@ int qca8k_port_fdb_add(struct dsa_switch *ds, int port,
>  int qca8k_port_fdb_del(struct dsa_switch *ds, int port,
>  		       const unsigned char *addr, u16 vid,
>  		       struct dsa_db db);
> -int qca8k_port_fdb_dump(struct dsa_switch *ds, int port,
> +int qca8k_port_fdb_dump(struct qca8k_priv *priv, int port,
>  			dsa_fdb_dump_cb_t *cb, void *data);
>  int qca8k_fdb_del(struct qca8k_priv *priv, const u8 *mac,
>  		  u16 port_mask, u16 vid);
> @@ -618,13 +619,12 @@ void qca8k_port_mirror_del(struct dsa_switch *ds, int port,
>  			   struct dsa_mall_mirror_tc_entry *mirror);
>  
>  /* Common port VLAN function */
> -int qca8k_port_vlan_filtering(struct dsa_switch *ds, int port,
> -			      bool vlan_filtering,
> -			      struct netlink_ext_ack *extack);
> +int qca8k_port_vlan_filtering(struct qca8k_priv *priv, int port,
> +			      bool vlan_filtering);
>  int qca8k_vlan_add(struct qca8k_priv *priv, u8 port, u16 vid,
>  		   bool untagged);
>  int qca8k_vlan_del(struct qca8k_priv *priv, u8 port, u16 vid);
> -int qca8k_port_vlan_add(struct dsa_switch *ds, int port,
> +int qca8k_port_vlan_add(struct qca8k_priv *priv, int port,
>  			const struct switchdev_obj_port_vlan *vlan,
>  			struct netlink_ext_ack *extack);
>  int qca8k_port_vlan_del(struct dsa_switch *ds, int port,



More information about the linux-arm-kernel mailing list