mbox series

[net-next,v5,0/9] net: lan966x: Add switchdev and vlan support

Message ID 20211215121309.3669119-1-horatiu.vultur@microchip.com
Headers show
Series net: lan966x: Add switchdev and vlan support | expand

Message

Horatiu Vultur Dec. 15, 2021, 12:13 p.m. UTC
This patch series extends lan966x with switchdev and vlan support.
The first patches just adds new registers and extend the MAC table to
handle the interrupts when a new address is learn/forget.

v4->v5:
- make the notifier_block from lan966x to be singletones
- use switchdev_handle_port_obj_add and switchdev_handle_fdb_event_to_device
  when getting callbacks in the lan966x
- merge the two vlan patches in a single one

v3->v4:
- split the last patch in multiple patches
- replace spin_lock_irqsave/restore with spin_lock/spin_unlock
- remove lan966x_port_change_rx_flags because it was copying all the frames to
  the CPU instead of removing all RX filters.
- implement SWITCHDEV_ATTR_ID_PORT_PRE_BRIDGE_FLAGS
- remove calls to __dev_mc_unsync/sync as they are not needed
- replace 0/1 with false/true
- make sure that the lan966x ports are not added to bridges that have other
  interfaces except lan966x
- and allow the lan966x ports to be part of only the same bridge.

v2->v3:
- separate the PVID used when the port is in host mode or vlan unaware
- fix issue when the port was leaving the bridge

v1->v2:
- when allocating entries for the mac table use kzalloc instead of
  devm_kzalloc
- also use GFP_KERNEL instead of GFP_ATOMIC, because is never called
  in atomic context
- when deleting an mac table entry, the order of operations was wrong
- if ana irq is enabled make sure it gets disabled when the driver is
  removed

Horatiu Vultur (9):
  net: lan966x: Add registers that are used for switch and vlan
    functionality
  dt-bindings: net: lan966x: Extend with the analyzer interrupt
  net: lan966x: add support for interrupts from analyzer
  net: lan966x: More MAC table functionality
  net: lan966x: Remove .ndo_change_rx_flags
  net: lan966x: Add support to offload the forwarding.
  net: lan966x: Add vlan support.
  net: lan966x: Extend switchdev bridge flags
  net: lan966x: Extend switchdev with fdb support

 .../net/microchip,lan966x-switch.yaml         |   2 +
 .../net/ethernet/microchip/lan966x/Kconfig    |   1 +
 .../net/ethernet/microchip/lan966x/Makefile   |   3 +-
 .../ethernet/microchip/lan966x/lan966x_fdb.c  | 246 ++++++++
 .../ethernet/microchip/lan966x/lan966x_mac.c  | 342 +++++++++++
 .../ethernet/microchip/lan966x/lan966x_main.c | 109 +++-
 .../ethernet/microchip/lan966x/lan966x_main.h |  80 ++-
 .../ethernet/microchip/lan966x/lan966x_regs.h | 129 ++++
 .../microchip/lan966x/lan966x_switchdev.c     | 557 ++++++++++++++++++
 .../ethernet/microchip/lan966x/lan966x_vlan.c | 448 ++++++++++++++
 10 files changed, 1886 insertions(+), 31 deletions(-)
 create mode 100644 drivers/net/ethernet/microchip/lan966x/lan966x_fdb.c
 create mode 100644 drivers/net/ethernet/microchip/lan966x/lan966x_switchdev.c
 create mode 100644 drivers/net/ethernet/microchip/lan966x/lan966x_vlan.c

Comments

Vladimir Oltean Dec. 15, 2021, 11:50 p.m. UTC | #1
On Wed, Dec 15, 2021 at 01:13:06PM +0100, Horatiu Vultur wrote:
> This patch adds basic support to offload in the HW the forwarding of the
> frames. The driver registers to the switchdev callbacks and implements
> the callbacks for attributes SWITCHDEV_ATTR_ID_PORT_STP_STATE and
> SWITCHDEV_ATTR_ID_BRIDGE_AGEING_TIME.
> It is not allowed to add a lan966x port to a bridge that contains a
> different interface than lan966x.
> 
> Signed-off-by: Horatiu Vultur <horatiu.vultur@microchip.com>
> ---
>  .../net/ethernet/microchip/lan966x/Kconfig    |   1 +
>  .../net/ethernet/microchip/lan966x/Makefile   |   2 +-
>  .../ethernet/microchip/lan966x/lan966x_main.c |  16 +-
>  .../ethernet/microchip/lan966x/lan966x_main.h |  11 +
>  .../microchip/lan966x/lan966x_switchdev.c     | 393 ++++++++++++++++++
>  5 files changed, 419 insertions(+), 4 deletions(-)
>  create mode 100644 drivers/net/ethernet/microchip/lan966x/lan966x_switchdev.c
> 
> diff --git a/drivers/net/ethernet/microchip/lan966x/Kconfig b/drivers/net/ethernet/microchip/lan966x/Kconfig
> index 2860a8c9923d..ac273f84b69e 100644
> --- a/drivers/net/ethernet/microchip/lan966x/Kconfig
> +++ b/drivers/net/ethernet/microchip/lan966x/Kconfig
> @@ -2,6 +2,7 @@ config LAN966X_SWITCH
>  	tristate "Lan966x switch driver"
>  	depends on HAS_IOMEM
>  	depends on OF
> +	depends on NET_SWITCHDEV
>  	select PHYLINK
>  	select PACKING
>  	help
> diff --git a/drivers/net/ethernet/microchip/lan966x/Makefile b/drivers/net/ethernet/microchip/lan966x/Makefile
> index 2989ba528236..974229c51f55 100644
> --- a/drivers/net/ethernet/microchip/lan966x/Makefile
> +++ b/drivers/net/ethernet/microchip/lan966x/Makefile
> @@ -6,4 +6,4 @@
>  obj-$(CONFIG_LAN966X_SWITCH) += lan966x-switch.o
>  
>  lan966x-switch-objs  := lan966x_main.o lan966x_phylink.o lan966x_port.o \
> -			lan966x_mac.o lan966x_ethtool.o
> +			lan966x_mac.o lan966x_ethtool.o lan966x_switchdev.o
> diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_main.c b/drivers/net/ethernet/microchip/lan966x/lan966x_main.c
> index dc40ac2eb246..ee453967da71 100644
> --- a/drivers/net/ethernet/microchip/lan966x/lan966x_main.c
> +++ b/drivers/net/ethernet/microchip/lan966x/lan966x_main.c
> @@ -355,6 +355,11 @@ static const struct net_device_ops lan966x_port_netdev_ops = {
>  	.ndo_get_port_parent_id		= lan966x_port_get_parent_id,
>  };
>  
> +bool lan966x_netdevice_check(const struct net_device *dev)
> +{
> +	return dev->netdev_ops == &lan966x_port_netdev_ops;
> +}
> +
>  static int lan966x_port_xtr_status(struct lan966x *lan966x, u8 grp)
>  {
>  	return lan_rd(lan966x, QS_XTR_RD(grp));
> @@ -491,6 +496,9 @@ static irqreturn_t lan966x_xtr_irq_handler(int irq, void *args)
>  
>  		skb->protocol = eth_type_trans(skb, dev);
>  
> +		if (lan966x->bridge_mask & BIT(src_port))
> +			skb->offload_fwd_mark = 1;
> +
>  		netif_rx_ni(skb);
>  		dev->stats.rx_bytes += len;
>  		dev->stats.rx_packets++;
> @@ -578,9 +586,6 @@ static int lan966x_probe_port(struct lan966x *lan966x, u32 p,
>  
>  	eth_hw_addr_gen(dev, lan966x->base_mac, p + 1);
>  
> -	lan966x_mac_learn(lan966x, PGID_CPU, dev->dev_addr, port->pvid,
> -			  ENTRYTYPE_LOCKED);
> -
>  	port->phylink_config.dev = &port->dev->dev;
>  	port->phylink_config.type = PHYLINK_NETDEV;
>  	port->phylink_pcs.poll = true;
> @@ -897,6 +902,8 @@ static int lan966x_probe(struct platform_device *pdev)
>  		lan966x_port_init(lan966x->ports[p]);
>  	}
>  
> +	lan966x_register_notifier_blocks(lan966x);

To be clear, "singleton" would mean that irrespective of the number of
driver instances, this function would be called once. So calling it from
lan966x_probe() isn't exactly a good choice, since every instance of the
driver "probes".

int dsa_slave_register_notifier(void)
{
	struct notifier_block *nb;
	int err;

	err = register_netdevice_notifier(&dsa_slave_nb);
	if (err)
		return err;

	err = register_switchdev_notifier(&dsa_slave_switchdev_notifier);
	if (err)
		goto err_switchdev_nb;

	nb = &dsa_slave_switchdev_blocking_notifier;
	err = register_switchdev_blocking_notifier(nb);
	if (err)
		goto err_switchdev_blocking_nb;
}

static int __init dsa_init_module(void)
{
	rc = dsa_slave_register_notifier();
}
module_init(dsa_init_module);

> +
>  	return 0;
>  
>  cleanup_ports:
> @@ -915,6 +922,8 @@ static int lan966x_remove(struct platform_device *pdev)
>  {
>  	struct lan966x *lan966x = platform_get_drvdata(pdev);
>  
> +	lan966x_unregister_notifier_blocks(lan966x);
> +
>  	lan966x_cleanup_ports(lan966x);
>  
>  	cancel_delayed_work_sync(&lan966x->stats_work);
> @@ -922,6 +931,7 @@ static int lan966x_remove(struct platform_device *pdev)
>  	mutex_destroy(&lan966x->stats_lock);
>  
>  	lan966x_mac_purge_entries(lan966x);
> +	lan966x_ext_purge_entries();

Broken with multiple lan966x driver instances - you'd erase all other
drivers' tabs keps on bridges in the system as soon as one single switch
is unbound from its driver.

>  
>  	return 0;
>  }
> diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_main.h b/drivers/net/ethernet/microchip/lan966x/lan966x_main.h
> index fcd5d09a070c..3d228c9c0521 100644
> --- a/drivers/net/ethernet/microchip/lan966x/lan966x_main.h
> +++ b/drivers/net/ethernet/microchip/lan966x/lan966x_main.h
> @@ -75,6 +75,10 @@ struct lan966x {
>  
>  	u8 base_mac[ETH_ALEN];
>  
> +	struct net_device *bridge;
> +	u16 bridge_mask;
> +	u16 bridge_fwd_mask;
> +
>  	struct list_head mac_entries;
>  	spinlock_t mac_lock; /* lock for mac_entries list */
>  
> @@ -122,6 +126,11 @@ extern const struct phylink_mac_ops lan966x_phylink_mac_ops;
>  extern const struct phylink_pcs_ops lan966x_phylink_pcs_ops;
>  extern const struct ethtool_ops lan966x_ethtool_ops;
>  
> +bool lan966x_netdevice_check(const struct net_device *dev);
> +
> +void lan966x_register_notifier_blocks(struct lan966x *lan966x);
> +void lan966x_unregister_notifier_blocks(struct lan966x *lan966x);
> +
>  void lan966x_stats_get(struct net_device *dev,
>  		       struct rtnl_link_stats64 *stats);
>  int lan966x_stats_init(struct lan966x *lan966x);
> @@ -157,6 +166,8 @@ int lan966x_mac_add_entry(struct lan966x *lan966x,
>  void lan966x_mac_purge_entries(struct lan966x *lan966x);
>  irqreturn_t lan966x_mac_irq_handler(struct lan966x *lan966x);
>  
> +void lan966x_ext_purge_entries(void);
> +
>  static inline void __iomem *lan_addr(void __iomem *base[],
>  				     int id, int tinst, int tcnt,
>  				     int gbase, int ginst,
> diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_switchdev.c b/drivers/net/ethernet/microchip/lan966x/lan966x_switchdev.c
> new file mode 100644
> index 000000000000..722ce7cb61b3
> --- /dev/null
> +++ b/drivers/net/ethernet/microchip/lan966x/lan966x_switchdev.c
> @@ -0,0 +1,393 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +
> +#include <linux/if_bridge.h>
> +#include <net/switchdev.h>
> +
> +#include "lan966x_main.h"
> +
> +static struct notifier_block lan966x_netdevice_nb __read_mostly;
> +static struct notifier_block lan966x_switchdev_nb __read_mostly;
> +static struct notifier_block lan966x_switchdev_blocking_nb __read_mostly;
> +
> +static LIST_HEAD(ext_entries);
> +
> +struct lan966x_ext_entry {
> +	struct list_head list;
> +	struct net_device *dev;
> +	u32 ports;
> +	struct lan966x *lan966x;
> +};
> +
> +static void lan966x_update_fwd_mask(struct lan966x *lan966x)
> +{
> +	int i;
> +
> +	for (i = 0; i < lan966x->num_phys_ports; i++) {
> +		struct lan966x_port *port = lan966x->ports[i];
> +		unsigned long mask = 0;
> +
> +		if (port && lan966x->bridge_fwd_mask & BIT(i))
> +			mask = lan966x->bridge_fwd_mask & ~BIT(i);
> +
> +		mask |= BIT(CPU_PORT);
> +
> +		lan_wr(ANA_PGID_PGID_SET(mask),
> +		       lan966x, ANA_PGID(PGID_SRC + i));
> +	}

I vaguely remember this was implemented better in previous versions of
the patch set, and the restriction to not allow multiple bridges
spanning the same switch wasn't there. Why do you keep disallowing
multiple bridges for all the Microchip hardware? There are very real use
cases that need them.

> +}
> +
> +static void lan966x_port_stp_state_set(struct lan966x_port *port, u8 state)
> +{
> +	struct lan966x *lan966x = port->lan966x;
> +	bool learn_ena = false;
> +
> +	if (state == BR_STATE_FORWARDING || state == BR_STATE_LEARNING)
> +		learn_ena = true;
> +
> +	if (state == BR_STATE_FORWARDING)
> +		lan966x->bridge_fwd_mask |= BIT(port->chip_port);
> +	else
> +		lan966x->bridge_fwd_mask &= ~BIT(port->chip_port);
> +
> +	lan_rmw(ANA_PORT_CFG_LEARN_ENA_SET(learn_ena),
> +		ANA_PORT_CFG_LEARN_ENA,
> +		lan966x, ANA_PORT_CFG(port->chip_port));
> +
> +	lan966x_update_fwd_mask(lan966x);
> +}
> +
> +static void lan966x_port_ageing_set(struct lan966x_port *port,
> +				    unsigned long ageing_clock_t)
> +{
> +	unsigned long ageing_jiffies = clock_t_to_jiffies(ageing_clock_t);
> +	u32 ageing_time = jiffies_to_msecs(ageing_jiffies) / 1000;
> +
> +	lan966x_mac_set_ageing(port->lan966x, ageing_time);
> +}
> +
> +static int lan966x_port_attr_set(struct net_device *dev, const void *ctx,
> +				 const struct switchdev_attr *attr,
> +				 struct netlink_ext_ack *extack)
> +{
> +	struct lan966x_port *port = netdev_priv(dev);
> +	int err = 0;
> +
> +	if (ctx && ctx != port)
> +		return 0;
> +
> +	switch (attr->id) {
> +	case SWITCHDEV_ATTR_ID_PORT_STP_STATE:
> +		lan966x_port_stp_state_set(port, attr->u.stp_state);
> +		break;
> +	case SWITCHDEV_ATTR_ID_BRIDGE_AGEING_TIME:
> +		lan966x_port_ageing_set(port, attr->u.ageing_time);
> +		break;
> +	default:
> +		err = -EOPNOTSUPP;
> +		break;
> +	}
> +
> +	return err;
> +}
> +
> +static int lan966x_port_bridge_join(struct lan966x_port *port,
> +				    struct net_device *bridge,
> +				    struct netlink_ext_ack *extack)
> +{
> +	struct lan966x *lan966x = port->lan966x;
> +	struct net_device *dev = port->dev;
> +	int err;
> +
> +	if (!lan966x->bridge_mask) {
> +		lan966x->bridge = bridge;
> +	} else {
> +		if (lan966x->bridge != bridge)

NL_SET_ERR_MSG_MOD(extack, "<excuse>");

> +			return -ENODEV;
> +	}
> +
> +	err = switchdev_bridge_port_offload(dev, dev, port,
> +					    &lan966x_switchdev_nb,
> +					    &lan966x_switchdev_blocking_nb,
> +					    false, extack);
> +	if (err)
> +		return err;
> +
> +	lan966x->bridge_mask |= BIT(port->chip_port);
> +
> +	return 0;
> +}
> +
> +static void lan966x_port_bridge_leave(struct lan966x_port *port,
> +				      struct net_device *bridge)
> +{
> +	struct lan966x *lan966x = port->lan966x;
> +
> +	lan966x->bridge_mask &= ~BIT(port->chip_port);
> +
> +	if (!lan966x->bridge_mask)
> +		lan966x->bridge = NULL;
> +
> +	lan966x_mac_cpu_learn(lan966x, port->dev->dev_addr, PORT_PVID);
> +}
> +
> +static int lan966x_port_changeupper(struct net_device *dev,
> +				    struct netdev_notifier_changeupper_info *info)
> +{
> +	struct lan966x_port *port = netdev_priv(dev);
> +	struct netlink_ext_ack *extack;
> +	int err = 0;
> +
> +	extack = netdev_notifier_info_to_extack(&info->info);
> +
> +	if (netif_is_bridge_master(info->upper_dev)) {
> +		if (info->linking)
> +			err = lan966x_port_bridge_join(port, info->upper_dev,
> +						       extack);
> +		else
> +			lan966x_port_bridge_leave(port, info->upper_dev);
> +	}
> +
> +	return err;
> +}
> +
> +static int lan966x_port_prechangeupper(struct net_device *dev,
> +				       struct netdev_notifier_changeupper_info *info)
> +{
> +	struct lan966x_port *port = netdev_priv(dev);
> +
> +	if (netif_is_bridge_master(info->upper_dev) && !info->linking)
> +		switchdev_bridge_port_unoffload(port->dev, port,
> +						&lan966x_switchdev_nb,
> +						&lan966x_switchdev_blocking_nb);
> +
> +	return NOTIFY_DONE;
> +}
> +
> +static int lan966x_port_add_addr(struct net_device *dev, bool up)
> +{
> +	struct lan966x_port *port = netdev_priv(dev);
> +	struct lan966x *lan966x = port->lan966x;
> +	u16 vid;
> +
> +	vid = port->pvid;
> +
> +	if (up)
> +		lan966x_mac_cpu_learn(lan966x, dev->dev_addr, vid);
> +	else
> +		lan966x_mac_cpu_forget(lan966x, dev->dev_addr, vid);
> +
> +	return 0;
> +}
> +
> +static struct lan966x_ext_entry *lan966x_ext_find_entry(struct net_device *dev)
> +{
> +	struct lan966x_ext_entry *ext_entry;
> +
> +	list_for_each_entry(ext_entry, &ext_entries, list) {
> +		if (ext_entry->dev == dev)
> +			return ext_entry;
> +	}
> +
> +	return NULL;
> +}
> +
> +static bool lan966x_ext_add_entry(struct net_device *dev, void *lan966x)
> +{
> +	struct lan966x_ext_entry *ext_entry;
> +
> +	ext_entry = lan966x_ext_find_entry(dev);
> +	if (ext_entry) {
> +		if (ext_entry->lan966x)
> +			return false;
> +
> +		ext_entry->ports++;
> +		return true;
> +	}
> +
> +	ext_entry = kzalloc(sizeof(*ext_entry), GFP_KERNEL);
> +	if (!ext_entry)
> +		return false;
> +
> +	ext_entry->dev = dev;
> +	ext_entry->ports = 1;
> +	ext_entry->lan966x = lan966x;
> +	list_add_tail(&ext_entry->list, &ext_entries);
> +	return true;
> +}
> +
> +static void lan966x_ext_remove_entry(struct net_device *dev)
> +{
> +	struct lan966x_ext_entry *ext_entry;
> +
> +	ext_entry = lan966x_ext_find_entry(dev);
> +	if (!ext_entry)
> +		return;
> +
> +	ext_entry->ports--;
> +	if (!ext_entry->ports) {
> +		list_del(&ext_entry->list);
> +		kfree(ext_entry);
> +	}
> +}
> +
> +void lan966x_ext_purge_entries(void)
> +{
> +	struct lan966x_ext_entry *ext_entry, *tmp;
> +
> +	list_for_each_entry_safe(ext_entry, tmp, &ext_entries, list) {
> +		list_del(&ext_entry->list);
> +		kfree(ext_entry);
> +	}
> +}
> +
> +static int lan966x_ext_check_entry(struct net_device *dev,
> +				   unsigned long event,
> +				   void *ptr)
> +{
> +	struct netdev_notifier_changeupper_info *info;
> +
> +	if (event != NETDEV_PRECHANGEUPPER)
> +		return 0;
> +
> +	info = ptr;
> +	if (!netif_is_bridge_master(info->upper_dev))
> +		return 0;
> +
> +	if (info->linking) {
> +		if (!lan966x_ext_add_entry(info->upper_dev, NULL))
> +			return -EOPNOTSUPP;
> +	} else {
> +		lan966x_ext_remove_entry(info->upper_dev);
> +	}
> +
> +	return NOTIFY_DONE;
> +}
> +
> +static bool lan966x_port_ext_check_entry(struct net_device *dev,
> +					 struct netdev_notifier_changeupper_info *info)
> +{
> +	struct lan966x_port *port = netdev_priv(dev);
> +	struct lan966x *lan966x = port->lan966x;
> +	struct lan966x_ext_entry *entry;
> +
> +	if (!netif_is_bridge_master(info->upper_dev))
> +		return true;
> +
> +	entry = lan966x_ext_find_entry(info->upper_dev);

"entry" is unused in the "else" block below, so logically speaking it
could be moved inside the "if" block.

Anyway, this piece of code is objectively speaking very obscure: convoluted
(lan966x_port_ext_check_entry calls lan966x_ext_find_entry _twice_, once
here and once in lan966x_ext_add_entry ?!), no comments and poorly named
(a lan966x_ext_entry represents a _bridge_ ?! what does "ext_entry"
stand for?). Plus, with your design where the "ext_entries" list is
global, and there are two instances of the driver, each driver would do
this work twice and allocate memory twice. Although, I didn't really
understand why you need to allocate memory to keep a tab on every bridge
in the system in the first place.

If you move your check from NETDEV_PRECHANGEUPPER to NETDEV_CHANGEUPPER,
you allow the upper/lower adjacency list relationship to have formed
(allowing the use of netdev_for_each_lower_dev, and the newly joining
interface will be a lower of the bridge). But you can still reject the
bridge join.

So you can do something like this, and it should produce an equivalent
effect (not compiled, not tested, written straight in the email body):

static int lan966x_foreign_bridging_check(struct net_device *bridge,
					  struct netlink_ext_ack *extack)
{
	struct lan966x *lan966x = NULL;
	bool has_foreign = false;
	struct net_device *dev;
	struct list_head *iter;

	netdev_for_each_lower_dev(bridge, dev, iter) {
		if (lan966x_netdevice_check(dev)) {
			struct lan966x_port *port = netdev_priv(dev);

			if (lan996x) {
				/* Bridge already has at least one port
				 * of a lan966x switch inside it, check
				 * that it's the same instance of the
				 * driver.
				 */
				if (port->lan966x != lan996x) {
					NL_SET_ERR_MSG_MOD(extack, "Bridging between multiple lan966x switches disallowed");
					return -EINVAL;
				}
			} else {
				/* This is the first lan966x port inside
				 * this bridge
				 */
				lan966x = port->lan966x;
			}
		} else {
			has_foreign = true;
		}

		if (lan966x && has_foreign) {
			NL_SET_ERR_MSG_MOD(extack, "Bridging lan966x ports with foreign interfaces disallowed");
			return -EINVAL;
		}
	}

	return 0;
}

and call this from two distinct call paths: from the NETDEV_CHANGEUPPER
of foreign interfaces, and from the NETDEV_CHANGEUPPER of lan966x interfaces.

Is it just me, or does this look more obvious and straightforward?

> +	if (info->linking) {
> +		if (!entry)
> +			return lan966x_ext_add_entry(info->upper_dev, lan966x);
> +
> +		if (entry->lan966x == lan966x) {
> +			entry->ports++;
> +			return true;
> +		}
> +	} else {
> +		lan966x_ext_remove_entry(info->upper_dev);
> +		return true;
> +	}
> +
> +	return false;
> +}
> +
> +static int lan966x_netdevice_port_event(struct net_device *dev,
> +					struct notifier_block *nb,
> +					unsigned long event, void *ptr)
> +{
> +	int err = 0;
> +
> +	if (!lan966x_netdevice_check(dev))
> +		return lan966x_ext_check_entry(dev, event, ptr);
> +
> +	switch (event) {
> +	case NETDEV_PRECHANGEUPPER:
> +		if (!lan966x_port_ext_check_entry(dev, ptr))
> +			return -EOPNOTSUPP;
> +
> +		err = lan966x_port_prechangeupper(dev, ptr);
> +		break;
> +	case NETDEV_CHANGEUPPER:
> +		err = lan966x_port_changeupper(dev, ptr);
> +		break;
> +	case NETDEV_PRE_UP:
> +		err = lan966x_port_add_addr(dev, true);
> +		break;
> +	case NETDEV_DOWN:
> +		err = lan966x_port_add_addr(dev, false);

Any reason why you track your own NETDEV_PRE_UP/NETDEV_DOWN and don't do
this directly in ->ndo_open/->ndo_close? Also, I don't think that the
"lan966x_port_add_addr" brings much value over "lan966x_mac_cpu_learn"
and "lan966x_mac_cpu_forget" called directly (especially if moved to
lan966x_port_open and lan966x_port_stop). And I don't see the relevance
of this change with respect to the commit title "add support to offload
the forwarding". CPU learned entries are for termination.

> +		break;
> +	}
> +
> +	return err;
> +}
> +
> +static int lan966x_netdevice_event(struct notifier_block *nb,
> +				   unsigned long event, void *ptr)
> +{
> +	struct net_device *dev = netdev_notifier_info_to_dev(ptr);
> +	int ret;
> +
> +	ret = lan966x_netdevice_port_event(dev, nb, event, ptr);
> +
> +	return notifier_from_errno(ret);
> +}
> +
> +static int lan966x_switchdev_event(struct notifier_block *nb,
> +				   unsigned long event, void *ptr)
> +{
> +	struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
> +	int err;
> +
> +	switch (event) {
> +	case SWITCHDEV_PORT_ATTR_SET:
> +		err = switchdev_handle_port_attr_set(dev, ptr,
> +						     lan966x_netdevice_check,
> +						     lan966x_port_attr_set);
> +		return notifier_from_errno(err);
> +	}
> +
> +	return NOTIFY_DONE;
> +}
> +
> +static int lan966x_switchdev_blocking_event(struct notifier_block *nb,
> +					    unsigned long event,
> +					    void *ptr)
> +{
> +	struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
> +	int err;
> +
> +	switch (event) {
> +	case SWITCHDEV_PORT_ATTR_SET:
> +		err = switchdev_handle_port_attr_set(dev, ptr,
> +						     lan966x_netdevice_check,
> +						     lan966x_port_attr_set);
> +		return notifier_from_errno(err);
> +	}
> +
> +	return NOTIFY_DONE;
> +}
> +
> +static struct notifier_block lan966x_netdevice_nb __read_mostly = {
> +	.notifier_call = lan966x_netdevice_event,
> +};
> +
> +static struct notifier_block lan966x_switchdev_nb __read_mostly = {
> +	.notifier_call = lan966x_switchdev_event,
> +};
> +
> +static struct notifier_block lan966x_switchdev_blocking_nb __read_mostly = {
> +	.notifier_call = lan966x_switchdev_blocking_event,
> +};
> +
> +void lan966x_register_notifier_blocks(struct lan966x *lan966x)
> +{
> +	register_netdevice_notifier(&lan966x_netdevice_nb);
> +	register_switchdev_notifier(&lan966x_switchdev_nb);
> +	register_switchdev_blocking_notifier(&lan966x_switchdev_blocking_nb);
> +}
> +
> +void lan966x_unregister_notifier_blocks(struct lan966x *lan966x)
> +{
> +	unregister_switchdev_blocking_notifier(&lan966x_switchdev_blocking_nb);
> +	unregister_switchdev_notifier(&lan966x_switchdev_nb);
> +	unregister_netdevice_notifier(&lan966x_netdevice_nb);
> +}
> -- 
> 2.33.0
>
Vladimir Oltean Dec. 16, 2021, 12:44 a.m. UTC | #2
On Wed, Dec 15, 2021 at 01:13:07PM +0100, Horatiu Vultur wrote:
> Extend the driver to support vlan filtering  by implementing the
> switchdev calls SWITCHDEV_OBJ_ID_PORT_VLAN and
> SWITCHDEV_ATTR_ID_BRIDGE_VLAN_FILTERING.

And the VLAN RX filtering net device ops.

> 
> Signed-off-by: Horatiu Vultur <horatiu.vultur@microchip.com>
> ---
>  .../net/ethernet/microchip/lan966x/Makefile   |   3 +-
>  .../ethernet/microchip/lan966x/lan966x_main.c |  39 +-
>  .../ethernet/microchip/lan966x/lan966x_main.h |  40 +-
>  .../microchip/lan966x/lan966x_switchdev.c     | 113 ++++-
>  .../ethernet/microchip/lan966x/lan966x_vlan.c | 444 ++++++++++++++++++
>  5 files changed, 632 insertions(+), 7 deletions(-)
>  create mode 100644 drivers/net/ethernet/microchip/lan966x/lan966x_vlan.c
> 
> diff --git a/drivers/net/ethernet/microchip/lan966x/Makefile b/drivers/net/ethernet/microchip/lan966x/Makefile
> index 974229c51f55..d82e896c2e53 100644
> --- a/drivers/net/ethernet/microchip/lan966x/Makefile
> +++ b/drivers/net/ethernet/microchip/lan966x/Makefile
> @@ -6,4 +6,5 @@
>  obj-$(CONFIG_LAN966X_SWITCH) += lan966x-switch.o
>  
>  lan966x-switch-objs  := lan966x_main.o lan966x_phylink.o lan966x_port.o \
> -			lan966x_mac.o lan966x_ethtool.o lan966x_switchdev.o
> +			lan966x_mac.o lan966x_ethtool.o lan966x_switchdev.o \
> +			lan966x_vlan.o
> diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_main.c b/drivers/net/ethernet/microchip/lan966x/lan966x_main.c
> index ee453967da71..881c1678f3e9 100644
> --- a/drivers/net/ethernet/microchip/lan966x/lan966x_main.c
> +++ b/drivers/net/ethernet/microchip/lan966x/lan966x_main.c
> @@ -103,17 +103,18 @@ static int lan966x_create_targets(struct platform_device *pdev,
>  static int lan966x_port_set_mac_address(struct net_device *dev, void *p)
>  {
>  	struct lan966x_port *port = netdev_priv(dev);
> +	u16 pvid = lan966x_vlan_port_get_pvid(port);
>  	struct lan966x *lan966x = port->lan966x;
>  	const struct sockaddr *addr = p;
>  	int ret;
>  
>  	/* Learn the new net device MAC address in the mac table. */
> -	ret = lan966x_mac_cpu_learn(lan966x, addr->sa_data, port->pvid);
> +	ret = lan966x_mac_cpu_learn(lan966x, addr->sa_data, pvid);

Logically speaking, there is a divide of responsibility. The bridge
emits switchdev FDB events for local MAC addresses, with a VID of 0
(corresponding to VLAN-unaware bridging) as well as for each installed
VLAN. Bridge VLAN 0 is equivalent to your UNAWARE_PVID macro. And the
driver is solely responsible for the MAC address in the HOST_PVID VLAN.
When the ndo_set_mac_address is called, you should just update the entry
learned in the HOST_PVID. The bridge will get an NETDEV_CHANGEADDR event
and update its local MAC addresses too, in the VLANs it handles.
Otherwise, if you just learn in the pvid that the port is currently in,
then RX filtering will be broken if you change your MAC address while
you're under a bridge, then you leave that bridge and become standalone.
So you need to re-learn the dev_addr in lan966x_port_bridge_leave, which
makes the implementation a bit more complicated than it needs to be
(unless I'm missing something about CPU-learned MAC addresses in VLANs
that aren't currently active, you seem to be avoiding that even though
it makes the driver keep a lot more state).

>  	if (ret)
>  		return ret;
>  
>  	/* Then forget the previous one. */
> -	ret = lan966x_mac_cpu_forget(lan966x, dev->dev_addr, port->pvid);
> +	ret = lan966x_mac_cpu_forget(lan966x, dev->dev_addr, pvid);
>  	if (ret)
>  		return ret;
>  
> @@ -283,6 +284,12 @@ static void lan966x_ifh_set_ipv(void *ifh, u64 bypass)
>  		IFH_POS_IPV, IFH_LEN * 4, PACK, 0);
>  }
>  
> +static void lan966x_ifh_set_vid(void *ifh, u64 vid)
> +{
> +	packing(ifh, &vid, IFH_POS_TCI + IFH_WID_TCI - 1,
> +		IFH_POS_TCI, IFH_LEN * 4, PACK, 0);
> +}
> +
>  static int lan966x_port_xmit(struct sk_buff *skb, struct net_device *dev)
>  {
>  	struct lan966x_port *port = netdev_priv(dev);
> @@ -294,6 +301,7 @@ static int lan966x_port_xmit(struct sk_buff *skb, struct net_device *dev)
>  	lan966x_ifh_set_port(ifh, BIT_ULL(port->chip_port));
>  	lan966x_ifh_set_qos_class(ifh, skb->priority >= 7 ? 0x7 : skb->priority);
>  	lan966x_ifh_set_ipv(ifh, skb->priority >= 7 ? 0x7 : skb->priority);
> +	lan966x_ifh_set_vid(ifh, skb_vlan_tag_get(skb));
>  
>  	return lan966x_port_ifh_xmit(skb, ifh, dev);
>  }
> @@ -343,6 +351,18 @@ static int lan966x_port_get_parent_id(struct net_device *dev,
>  	return 0;
>  }
>  
> +static int lan966x_port_set_features(struct net_device *dev,
> +				     netdev_features_t features)
> +{
> +	struct lan966x_port *port = netdev_priv(dev);
> +	netdev_features_t changed = dev->features ^ features;
> +
> +	if (changed & NETIF_F_HW_VLAN_CTAG_FILTER)
> +		lan966x_vlan_mode(port, features);
> +
> +	return 0;
> +}
> +
>  static const struct net_device_ops lan966x_port_netdev_ops = {
>  	.ndo_open			= lan966x_port_open,
>  	.ndo_stop			= lan966x_port_stop,
> @@ -353,6 +373,9 @@ static const struct net_device_ops lan966x_port_netdev_ops = {
>  	.ndo_get_stats64		= lan966x_stats_get,
>  	.ndo_set_mac_address		= lan966x_port_set_mac_address,
>  	.ndo_get_port_parent_id		= lan966x_port_get_parent_id,
> +	.ndo_set_features		= lan966x_port_set_features,
> +	.ndo_vlan_rx_add_vid		= lan966x_vlan_rx_add_vid,
> +	.ndo_vlan_rx_kill_vid		= lan966x_vlan_rx_kill_vid,

Do you have any particular use case for NETIF_F_HW_VLAN_CTAG_FILTER on
non-bridged ports? I find the fact that you implement these very strange
and likely bogus: you set port->vlan_aware = false when a port leaves a
bridge, yet you install VLANs to its RX filter as if those VLANs were to
actually match on any VLAN-tagged packet... which they won't because
lan966x_vlan_port_apply() clears ANA_VLAN_CFG_VLAN_AWARE_ENA_SET(1) when
port->vlan_aware isn't set. So you end up being "filtering" but not "aware"
- all packets get classified to the same VLAN, which isn't dropped.

>  };
>  
>  bool lan966x_netdevice_check(const struct net_device *dev)
> @@ -575,13 +598,16 @@ static int lan966x_probe_port(struct lan966x *lan966x, u32 p,
>  	port->dev = dev;
>  	port->lan966x = lan966x;
>  	port->chip_port = p;
> -	port->pvid = PORT_PVID;
>  	lan966x->ports[p] = port;
>  
>  	dev->max_mtu = ETH_MAX_MTU;
>  
>  	dev->netdev_ops = &lan966x_port_netdev_ops;
>  	dev->ethtool_ops = &lan966x_ethtool_ops;
> +	dev->hw_features |= NETIF_F_HW_VLAN_CTAG_FILTER;
> +	dev->features |= NETIF_F_HW_VLAN_CTAG_FILTER |
> +			 NETIF_F_HW_VLAN_CTAG_TX |
> +			 NETIF_F_HW_VLAN_STAG_TX;
>  	dev->needed_headroom = IFH_LEN * sizeof(u32);
>  
>  	eth_hw_addr_gen(dev, lan966x->base_mac, p + 1);
> @@ -625,6 +651,10 @@ static int lan966x_probe_port(struct lan966x *lan966x, u32 p,
>  		return err;
>  	}
>  
> +	lan966x_vlan_port_set_vlan_aware(port, 0);
> +	lan966x_vlan_port_set_vid(port, HOST_PVID, false, false);
> +	lan966x_vlan_port_apply(port);
> +
>  	return 0;
>  }
>  
> @@ -635,6 +665,9 @@ static void lan966x_init(struct lan966x *lan966x)
>  	/* MAC table initialization */
>  	lan966x_mac_init(lan966x);
>  
> +	/* Vlan initialization */
> +	lan966x_vlan_init(lan966x);

Curious how the lan966x_ext_entry stuff doesn't have any comment and
lan966x_vlan_init has such a trivial one?!

> +
>  	/* Flush queues */
>  	lan_wr(lan_rd(lan966x, QS_XTR_FLUSH) |
>  	       GENMASK(1, 0),
> diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_main.h b/drivers/net/ethernet/microchip/lan966x/lan966x_main.h
> index 3d228c9c0521..6d0d922617ae 100644
> --- a/drivers/net/ethernet/microchip/lan966x/lan966x_main.h
> +++ b/drivers/net/ethernet/microchip/lan966x/lan966x_main.h
> @@ -4,6 +4,7 @@
>  #define __LAN966X_MAIN_H__
>  
>  #include <linux/etherdevice.h>
> +#include <linux/if_vlan.h>
>  #include <linux/jiffies.h>
>  #include <linux/phy.h>
>  #include <linux/phylink.h>
> @@ -22,7 +23,8 @@
>  #define PGID_SRC			80
>  #define PGID_ENTRIES			89
>  
> -#define PORT_PVID			0
> +#define UNAWARE_PVID			0
> +#define HOST_PVID			4095
>  
>  /* Reserved amount for (SRC, PRIO) at index 8*SRC + PRIO */
>  #define QSYS_Q_RSRV			95
> @@ -82,6 +84,9 @@ struct lan966x {
>  	struct list_head mac_entries;
>  	spinlock_t mac_lock; /* lock for mac_entries list */
>  
> +	u16 vlan_mask[VLAN_N_VID];
> +	DECLARE_BITMAP(cpu_vlan_mask, VLAN_N_VID);
> +
>  	/* stats */
>  	const struct lan966x_stat_layout *stats_layout;
>  	u32 num_stats;
> @@ -113,6 +118,8 @@ struct lan966x_port {
>  
>  	u8 chip_port;
>  	u16 pvid;
> +	u16 vid;
> +	u8 vlan_aware;

bool

>  
>  	struct phylink_config phylink_config;
>  	struct phylink_pcs phylink_pcs;
> @@ -168,6 +175,37 @@ irqreturn_t lan966x_mac_irq_handler(struct lan966x *lan966x);
>  
>  void lan966x_ext_purge_entries(void);
>  
> +void lan966x_vlan_init(struct lan966x *lan966x);
> +void lan966x_vlan_port_apply(struct lan966x_port *port);
> +
> +int lan966x_vlan_rx_add_vid(struct net_device *dev, __be16 proto, u16 vid);
> +int lan966x_vlan_rx_kill_vid(struct net_device *dev, __be16 proto, u16 vid);
> +
> +void lan966x_vlan_mode(struct lan966x_port *port, netdev_features_t features);
> +u16 lan966x_vlan_port_get_pvid(struct lan966x_port *port);
> +
> +bool lan966x_vlan_cpu_member_cpu_vlan_mask(struct lan966x *lan966x, u16 vid);
> +void lan966x_vlan_cpu_add_cpu_vlan_mask(struct lan966x *lan966x, u16 vid);
> +bool lan966x_vlan_port_any_vlan_mask(struct lan966x *lan966x, u16 vid);
> +
> +void lan966x_vlan_cpu_set_vlan_aware(struct lan966x_port *port);
> +void lan966x_vlan_port_set_vlan_aware(struct lan966x_port *port,
> +				      bool vlan_aware);
> +int lan966x_vlan_port_set_vid(struct lan966x_port *port, u16 vid,
> +			      bool pvid, bool untagged);
> +int lan966x_vlan_port_add_vlan(struct lan966x_port *port,
> +			       u16 vid,
> +			       bool pvid,
> +			       bool untagged);
> +int lan966x_vlan_port_del_vlan(struct lan966x_port *port,
> +			       u16 vid);
> +int lan966x_vlan_cpu_add_vlan(struct lan966x *lan966x,
> +			      struct net_device *dev,
> +			      u16 vid);
> +int lan966x_vlan_cpu_del_vlan(struct lan966x *lan966x,
> +			      struct net_device *dev,
> +			      u16 vid);
> +
>  static inline void __iomem *lan_addr(void __iomem *base[],
>  				     int id, int tinst, int tcnt,
>  				     int gbase, int ginst,
> diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_switchdev.c b/drivers/net/ethernet/microchip/lan966x/lan966x_switchdev.c
> index 722ce7cb61b3..61f9e906cf80 100644
> --- a/drivers/net/ethernet/microchip/lan966x/lan966x_switchdev.c
> +++ b/drivers/net/ethernet/microchip/lan966x/lan966x_switchdev.c
> @@ -82,6 +82,11 @@ static int lan966x_port_attr_set(struct net_device *dev, const void *ctx,
>  	case SWITCHDEV_ATTR_ID_BRIDGE_AGEING_TIME:
>  		lan966x_port_ageing_set(port, attr->u.ageing_time);
>  		break;
> +	case SWITCHDEV_ATTR_ID_BRIDGE_VLAN_FILTERING:
> +		lan966x_vlan_port_set_vlan_aware(port, attr->u.vlan_filtering);
> +		lan966x_vlan_port_apply(port);
> +		lan966x_vlan_cpu_set_vlan_aware(port);
> +		break;
>  	default:
>  		err = -EOPNOTSUPP;
>  		break;
> @@ -127,7 +132,12 @@ static void lan966x_port_bridge_leave(struct lan966x_port *port,
>  	if (!lan966x->bridge_mask)
>  		lan966x->bridge = NULL;
>  
> -	lan966x_mac_cpu_learn(lan966x, port->dev->dev_addr, PORT_PVID);
> +	/* Set the port back to host mode */
> +	lan966x_vlan_port_set_vlan_aware(port, false);
> +	lan966x_vlan_port_set_vid(port, HOST_PVID, false, false);
> +	lan966x_vlan_port_apply(port);
> +
> +	lan966x_mac_cpu_learn(lan966x, port->dev->dev_addr, HOST_PVID);
>  }
>  
>  static int lan966x_port_changeupper(struct net_device *dev,
> @@ -169,7 +179,7 @@ static int lan966x_port_add_addr(struct net_device *dev, bool up)
>  	struct lan966x *lan966x = port->lan966x;
>  	u16 vid;
>  
> -	vid = port->pvid;
> +	vid = lan966x_vlan_port_get_pvid(port);
>  
>  	if (up)
>  		lan966x_mac_cpu_learn(lan966x, dev->dev_addr, vid);
> @@ -348,6 +358,95 @@ static int lan966x_switchdev_event(struct notifier_block *nb,
>  	return NOTIFY_DONE;
>  }
>  
> +static int lan966x_handle_port_vlan_add(struct lan966x_port *port,
> +					const struct switchdev_obj *obj)
> +{
> +	const struct switchdev_obj_port_vlan *v = SWITCHDEV_OBJ_PORT_VLAN(obj);
> +	struct lan966x *lan966x = port->lan966x;
> +
> +	/* When adding a port to a vlan, we get a callback for the port but
> +	 * also for the bridge. When get the callback for the bridge just bail
> +	 * out. Then when the bridge is added to the vlan, then we get a
> +	 * callback here but in this case the flags has set:
> +	 * BRIDGE_VLAN_INFO_BRENTRY. In this case it means that the CPU
> +	 * port is added to the vlan, so the broadcast frames and unicast frames
> +	 * with dmac of the bridge should be foward to CPU.
> +	 */
> +	if (netif_is_bridge_master(obj->orig_dev) &&
> +	    !(v->flags & BRIDGE_VLAN_INFO_BRENTRY))
> +		return 0;
> +
> +	if (!netif_is_bridge_master(obj->orig_dev))
> +		return lan966x_vlan_port_add_vlan(port, v->vid,
> +						  v->flags & BRIDGE_VLAN_INFO_PVID,
> +						  v->flags & BRIDGE_VLAN_INFO_UNTAGGED);
> +
> +	if (netif_is_bridge_master(obj->orig_dev))

"else" will suffice.

> +		return lan966x_vlan_cpu_add_vlan(lan966x, obj->orig_dev, v->vid);
> +
> +	return 0;
> +}
> +
> +static int lan966x_handle_port_obj_add(struct net_device *dev, const void *ctx,
> +				       const struct switchdev_obj *obj,
> +				       struct netlink_ext_ack *extack)
> +{
> +	struct lan966x_port *port = netdev_priv(dev);
> +	int err;
> +
> +	if (ctx && ctx != port)
> +		return 0;
> +
> +	switch (obj->id) {
> +	case SWITCHDEV_OBJ_ID_PORT_VLAN:
> +		err = lan966x_handle_port_vlan_add(port, obj);
> +		break;
> +	default:
> +		err = -EOPNOTSUPP;
> +		break;
> +	}
> +
> +	return err;
> +}
> +
> +static int lan966x_handle_port_vlan_del(struct lan966x_port *port,
> +					const struct switchdev_obj *obj)
> +{
> +	const struct switchdev_obj_port_vlan *v = SWITCHDEV_OBJ_PORT_VLAN(obj);
> +	struct lan966x *lan966x = port->lan966x;
> +
> +	/* In case the physical port gets called */
> +	if (!netif_is_bridge_master(obj->orig_dev))
> +		return lan966x_vlan_port_del_vlan(port, v->vid);
> +
> +	/* In case the bridge gets called */
> +	if (netif_is_bridge_master(obj->orig_dev))

likewise.

> +		return lan966x_vlan_cpu_del_vlan(lan966x, obj->orig_dev, v->vid);
> +
> +	return 0;
> +}
> +
> +static int lan966x_handle_port_obj_del(struct net_device *dev, const void *ctx,
> +				       const struct switchdev_obj *obj)
> +{
> +	struct lan966x_port *port = netdev_priv(dev);
> +	int err;
> +
> +	if (ctx && ctx != port)
> +		return 0;
> +
> +	switch (obj->id) {
> +	case SWITCHDEV_OBJ_ID_PORT_VLAN:
> +		err = lan966x_handle_port_vlan_del(port, obj);
> +		break;
> +	default:
> +		err = -EOPNOTSUPP;
> +		break;
> +	}
> +
> +	return err;
> +}
> +
>  static int lan966x_switchdev_blocking_event(struct notifier_block *nb,
>  					    unsigned long event,
>  					    void *ptr)
> @@ -356,6 +455,16 @@ static int lan966x_switchdev_blocking_event(struct notifier_block *nb,
>  	int err;
>  
>  	switch (event) {
> +	case SWITCHDEV_PORT_OBJ_ADD:
> +		err = switchdev_handle_port_obj_add(dev, ptr,
> +						    lan966x_netdevice_check,
> +						    lan966x_handle_port_obj_add);
> +		return notifier_from_errno(err);
> +	case SWITCHDEV_PORT_OBJ_DEL:
> +		err = switchdev_handle_port_obj_del(dev, ptr,
> +						    lan966x_netdevice_check,
> +						    lan966x_handle_port_obj_del);
> +		return notifier_from_errno(err);
>  	case SWITCHDEV_PORT_ATTR_SET:
>  		err = switchdev_handle_port_attr_set(dev, ptr,
>  						     lan966x_netdevice_check,
> diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_vlan.c b/drivers/net/ethernet/microchip/lan966x/lan966x_vlan.c
> new file mode 100644
> index 000000000000..e8ff95bb65fa
> --- /dev/null
> +++ b/drivers/net/ethernet/microchip/lan966x/lan966x_vlan.c
> @@ -0,0 +1,444 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +
> +#include "lan966x_main.h"
> +
> +#define VLANACCESS_CMD_IDLE		0
> +#define VLANACCESS_CMD_READ		1
> +#define VLANACCESS_CMD_WRITE		2
> +#define VLANACCESS_CMD_INIT		3
> +
> +static int lan966x_vlan_get_status(struct lan966x *lan966x)
> +{
> +	return lan_rd(lan966x, ANA_VLANACCESS);
> +}
> +
> +static int lan966x_vlan_wait_for_completion(struct lan966x *lan966x)
> +{
> +	u32 val;
> +
> +	return readx_poll_timeout(lan966x_vlan_get_status,
> +		lan966x, val,
> +		(val & ANA_VLANACCESS_VLAN_TBL_CMD) ==
> +		VLANACCESS_CMD_IDLE,
> +		TABLE_UPDATE_SLEEP_US, TABLE_UPDATE_TIMEOUT_US);
> +}
> +
> +static int lan966x_vlan_set_mask(struct lan966x *lan966x, u16 vid)
> +{
> +	u16 mask = lan966x->vlan_mask[vid];
> +	bool cpu_dis;
> +
> +	cpu_dis = !(mask & BIT(CPU_PORT));
> +
> +	/* Set flags and the VID to configure */
> +	lan_rmw(ANA_VLANTIDX_VLAN_PGID_CPU_DIS_SET(cpu_dis) |
> +		ANA_VLANTIDX_V_INDEX_SET(vid),
> +		ANA_VLANTIDX_VLAN_PGID_CPU_DIS |
> +		ANA_VLANTIDX_V_INDEX,
> +		lan966x, ANA_VLANTIDX);
> +
> +	/* Set the vlan port members mask */
> +	lan_rmw(ANA_VLAN_PORT_MASK_VLAN_PORT_MASK_SET(mask),
> +		ANA_VLAN_PORT_MASK_VLAN_PORT_MASK,
> +		lan966x, ANA_VLAN_PORT_MASK);
> +
> +	/* Issue a write command */
> +	lan_rmw(ANA_VLANACCESS_VLAN_TBL_CMD_SET(VLANACCESS_CMD_WRITE),
> +		ANA_VLANACCESS_VLAN_TBL_CMD,
> +		lan966x, ANA_VLANACCESS);
> +
> +	return lan966x_vlan_wait_for_completion(lan966x);

If you're not going to propagate the return code anywhere, at least
return void and print an error here. Otherwise it's totally silent.

> +}
> +
> +void lan966x_vlan_init(struct lan966x *lan966x)
> +{
> +	u16 port, vid;
> +
> +	/* Clear VLAN table, by default all ports are members of all VLANS */
> +	lan_rmw(ANA_VLANACCESS_VLAN_TBL_CMD_SET(VLANACCESS_CMD_INIT),
> +		ANA_VLANACCESS_VLAN_TBL_CMD,
> +		lan966x, ANA_VLANACCESS);
> +	lan966x_vlan_wait_for_completion(lan966x);

Again no error checking.

> +
> +	for (vid = 1; vid < VLAN_N_VID; vid++) {
> +		lan966x->vlan_mask[vid] = 0;
> +		lan966x_vlan_set_mask(lan966x, vid);
> +	}
> +
> +	/* Set all the ports + cpu to be part of HOST_PVID and UNAWARE_PVID */
> +	lan966x->vlan_mask[HOST_PVID] =
> +		GENMASK(lan966x->num_phys_ports - 1, 0) | BIT(CPU_PORT);
> +	lan966x_vlan_set_mask(lan966x, HOST_PVID);
> +
> +	lan966x->vlan_mask[UNAWARE_PVID] =
> +		GENMASK(lan966x->num_phys_ports - 1, 0) | BIT(CPU_PORT);
> +	lan966x_vlan_set_mask(lan966x, UNAWARE_PVID);
> +
> +	lan966x_vlan_cpu_add_cpu_vlan_mask(lan966x, UNAWARE_PVID);
> +
> +	/* Configure the CPU port to be vlan aware */
> +	lan_wr(ANA_VLAN_CFG_VLAN_VID_SET(0) |
> +	       ANA_VLAN_CFG_VLAN_AWARE_ENA_SET(1) |
> +	       ANA_VLAN_CFG_VLAN_POP_CNT_SET(1),
> +	       lan966x, ANA_VLAN_CFG(CPU_PORT));
> +
> +	/* Set vlan ingress filter mask to all ports */
> +	lan_wr(GENMASK(lan966x->num_phys_ports, 0),
> +	       lan966x, ANA_VLANMASK);
> +
> +	for (port = 0; port < lan966x->num_phys_ports; port++) {
> +		lan_wr(0, lan966x, REW_PORT_VLAN_CFG(port));
> +		lan_wr(0, lan966x, REW_TAG_CFG(port));
> +	}
> +}
> +
> +static int lan966x_vlan_port_add_vlan_mask(struct lan966x_port *port, u16 vid)
> +{
> +	struct lan966x *lan966x = port->lan966x;
> +	u8 p = port->chip_port;
> +
> +	lan966x->vlan_mask[vid] |= BIT(p);
> +	return lan966x_vlan_set_mask(lan966x, vid);
> +}
> +
> +static int lan966x_vlan_port_del_vlan_mask(struct lan966x_port *port, u16 vid)
> +{
> +	struct lan966x *lan966x = port->lan966x;
> +	u8 p = port->chip_port;
> +
> +	lan966x->vlan_mask[vid] &= ~BIT(p);
> +	return lan966x_vlan_set_mask(lan966x, vid);
> +}
> +
> +static bool lan966x_vlan_port_member_vlan_mask(struct lan966x_port *port, u16 vid)
> +{
> +	struct lan966x *lan966x = port->lan966x;
> +	u8 p = port->chip_port;
> +
> +	return lan966x->vlan_mask[vid] & BIT(p);
> +}
> +
> +bool lan966x_vlan_port_any_vlan_mask(struct lan966x *lan966x, u16 vid)
> +{
> +	return !!(lan966x->vlan_mask[vid] & ~BIT(CPU_PORT));
> +}
> +
> +static int lan966x_vlan_cpu_add_vlan_mask(struct lan966x *lan966x, u16 vid)
> +{
> +	lan966x->vlan_mask[vid] |= BIT(CPU_PORT);
> +	return lan966x_vlan_set_mask(lan966x, vid);
> +}
> +
> +static int lan966x_vlan_cpu_del_vlan_mask(struct lan966x *lan966x, u16 vid)
> +{
> +	lan966x->vlan_mask[vid] &= ~BIT(CPU_PORT);
> +	return lan966x_vlan_set_mask(lan966x, vid);
> +}
> +
> +void lan966x_vlan_cpu_add_cpu_vlan_mask(struct lan966x *lan966x, u16 vid)
> +{
> +	set_bit(vid, lan966x->cpu_vlan_mask);

Since these are all serialized by the rtnl_mutex, I think it's safe to
replace with __set_bit which is non-atomic and thus cheaper.

> +}
> +
> +static void lan966x_vlan_cpu_del_cpu_vlan_mask(struct lan966x *lan966x, u16 vid)
> +{
> +	clear_bit(vid, lan966x->cpu_vlan_mask);
> +}
> +
> +bool lan966x_vlan_cpu_member_cpu_vlan_mask(struct lan966x *lan966x, u16 vid)
> +{
> +	return test_bit(vid, lan966x->cpu_vlan_mask);
> +}
> +
> +u16 lan966x_vlan_port_get_pvid(struct lan966x_port *port)
> +{
> +	struct lan966x *lan966x = port->lan966x;
> +
> +	if (!(lan966x->bridge_mask & BIT(port->chip_port)))
> +		return HOST_PVID;
> +
> +	return port->vlan_aware ? port->pvid : UNAWARE_PVID;
> +}
> +
> +int lan966x_vlan_port_set_vid(struct lan966x_port *port, u16 vid,
> +			      bool pvid, bool untagged)

If you were to summarize what this function does, what would that be?

> +{
> +	struct lan966x *lan966x = port->lan966x;
> +
> +	/* Egress vlan classification */
> +	if (untagged && port->vid != vid) {
> +		if (port->vid) {
> +			dev_err(lan966x->dev,
> +				"Port already has a native VLAN: %d\n",
> +				port->vid);
> +			return -EBUSY;
> +		}
> +		port->vid = vid;
> +	}
> +
> +	/* Default ingress vlan classification */
> +	if (pvid)
> +		port->pvid = vid;
> +
> +	return 0;
> +}
> +
> +static int lan966x_vlan_port_remove_vid(struct lan966x_port *port, u16 vid)
> +{
> +	if (port->pvid == vid)
> +		port->pvid = 0;
> +
> +	if (port->vid == vid)
> +		port->vid = 0;
> +
> +	return 0;
> +}
> +
> +void lan966x_vlan_port_set_vlan_aware(struct lan966x_port *port,
> +				      bool vlan_aware)
> +{
> +	port->vlan_aware = vlan_aware;
> +}
> +
> +void lan966x_vlan_cpu_set_vlan_aware(struct lan966x_port *port)
> +{
> +	struct lan966x *lan966x = port->lan966x;
> +
> +	if (!port->vlan_aware) {
> +		/* In case of vlan unaware, all the ports will be set in
> +		 * UNAWARE_PVID and have their PVID set to this PVID
> +		 * The CPU doesn't need to be added because it is always part of
> +		 * that vlan, it is required just to add entries in the MAC
> +		 * table for the front port and the CPU
> +		 */
> +		lan966x_mac_cpu_learn(lan966x, port->dev->dev_addr, UNAWARE_PVID);
> +		lan966x_mac_cpu_learn(lan966x, lan966x->bridge->dev_addr,
> +				      UNAWARE_PVID);
> +
> +		lan966x_vlan_port_add_vlan_mask(port, UNAWARE_PVID);
> +		lan966x_vlan_port_apply(port);
> +	} else {
> +		/* In case of vlan aware, just clear what happened when changed
> +		 * to vlan unaware
> +		 */
> +		lan966x_mac_cpu_forget(lan966x, port->dev->dev_addr, UNAWARE_PVID);
> +		lan966x_mac_cpu_forget(lan966x, lan966x->bridge->dev_addr,
> +				       UNAWARE_PVID);
> +
> +		lan966x_vlan_port_del_vlan_mask(port, UNAWARE_PVID);
> +		lan966x_vlan_port_apply(port);
> +	}
> +}
> +
> +void lan966x_vlan_port_apply(struct lan966x_port *port)
> +{
> +	struct lan966x *lan966x = port->lan966x;
> +	u16 pvid;
> +	u32 val;
> +
> +	pvid = lan966x_vlan_port_get_pvid(port);
> +
> +	/* Ingress clasification (ANA_PORT_VLAN_CFG) */
> +	/* Default vlan to casify for untagged frames (may be zero) */

classify

> +	val = ANA_VLAN_CFG_VLAN_VID_SET(pvid);
> +	if (port->vlan_aware)
> +		val |= ANA_VLAN_CFG_VLAN_AWARE_ENA_SET(1) |
> +		       ANA_VLAN_CFG_VLAN_POP_CNT_SET(1);
> +
> +	lan_rmw(val,
> +		ANA_VLAN_CFG_VLAN_VID | ANA_VLAN_CFG_VLAN_AWARE_ENA |
> +		ANA_VLAN_CFG_VLAN_POP_CNT,
> +		lan966x, ANA_VLAN_CFG(port->chip_port));
> +
> +	/* Drop frames with multicast source address */
> +	val = ANA_DROP_CFG_DROP_MC_SMAC_ENA_SET(1);
> +	if (port->vlan_aware && !pvid)
> +		/* If port is vlan-aware and tagged, drop untagged and priority
> +		 * tagged frames.
> +		 */
> +		val |= ANA_DROP_CFG_DROP_UNTAGGED_ENA_SET(1) |
> +		       ANA_DROP_CFG_DROP_PRIO_S_TAGGED_ENA_SET(1) |
> +		       ANA_DROP_CFG_DROP_PRIO_C_TAGGED_ENA_SET(1);
> +
> +	lan_wr(val, lan966x, ANA_DROP_CFG(port->chip_port));
> +
> +	/* Egress configuration (REW_TAG_CFG): VLAN tag type to 8021Q */
> +	val = REW_TAG_CFG_TAG_TPID_CFG_SET(0);
> +	if (port->vlan_aware) {
> +		if (port->vid)
> +			/* Tag all frames except when VID == DEFAULT_VLAN */
> +			val |= REW_TAG_CFG_TAG_CFG_SET(1);
> +		else
> +			val |= REW_TAG_CFG_TAG_CFG_SET(3);
> +	}
> +
> +	/* Update only some bits in the register */
> +	lan_rmw(val,
> +		REW_TAG_CFG_TAG_TPID_CFG | REW_TAG_CFG_TAG_CFG,
> +		lan966x, REW_TAG_CFG(port->chip_port));
> +
> +	/* Set default VLAN and tag type to 8021Q */
> +	lan_rmw(REW_PORT_VLAN_CFG_PORT_TPID_SET(ETH_P_8021Q) |
> +		REW_PORT_VLAN_CFG_PORT_VID_SET(port->vid),
> +		REW_PORT_VLAN_CFG_PORT_TPID |
> +		REW_PORT_VLAN_CFG_PORT_VID,
> +		lan966x, REW_PORT_VLAN_CFG(port->chip_port));
> +}
> +
> +int lan966x_vlan_port_add_vlan(struct lan966x_port *port,
> +			       u16 vid,
> +			       bool pvid,
> +			       bool untagged)
> +{
> +	struct lan966x *lan966x = port->lan966x;
> +
> +	/* If the CPU(br) is already part of the vlan then add the MAC
> +	 * address of the device in MAC table to copy the frames to the
> +	 * CPU(br). If the CPU(br) is not part of the vlan then it would
> +	 * just drop the frames.
> +	 */
> +	if (lan966x_vlan_cpu_member_cpu_vlan_mask(lan966x, vid)) {
> +		lan966x_mac_cpu_learn(lan966x, port->dev->dev_addr, vid);
> +		lan966x_mac_cpu_learn(lan966x, lan966x->bridge->dev_addr, vid);

Doesn't the bridge notify you of all the addresses you need to learn on
the CPU port? What is the benefit of the added complexity of only
learning the addresses when the CPU joins the VLAN? Doesn't the CPU_DIS
bit work if an entry is present in the MAC table?

> +		lan966x_vlan_cpu_add_vlan_mask(lan966x, vid);
> +	}
> +
> +	lan966x_vlan_port_set_vid(port, vid, pvid, untagged);
> +	lan966x_vlan_port_add_vlan_mask(port, vid);
> +	lan966x_vlan_port_apply(port);
> +
> +	return 0;
> +}
> +
> +int lan966x_vlan_port_del_vlan(struct lan966x_port *port,
> +			       u16 vid)
> +{
> +	struct lan966x *lan966x = port->lan966x;
> +
> +	/* In case the CPU(br) is part of the vlan then remove the MAC entry
> +	 * because frame doesn't need to reach to CPU
> +	 */
> +	if (lan966x_vlan_cpu_member_cpu_vlan_mask(lan966x, vid))
> +		lan966x_mac_cpu_forget(lan966x, port->dev->dev_addr, vid);
> +
> +	lan966x_vlan_port_remove_vid(port, vid);
> +	lan966x_vlan_port_del_vlan_mask(port, vid);
> +	lan966x_vlan_port_apply(port);
> +
> +	/* In case there are no other ports in vlan then remove the CPU from
> +	 * that vlan but still keep it in the mask because it may be needed
> +	 * again then another port gets added in tha vlan

s/tha/that/

> +	 */
> +	if (!lan966x_vlan_port_any_vlan_mask(lan966x, vid)) {
> +		lan966x_mac_cpu_forget(lan966x, lan966x->bridge->dev_addr, vid);
> +		lan966x_vlan_cpu_del_vlan_mask(lan966x, vid);
> +	}
> +
> +	return 0;
> +}
> +
> +int lan966x_vlan_cpu_add_vlan(struct lan966x *lan966x,
> +			      struct net_device *dev,
> +			      u16 vid)
> +{
> +	int p;
> +
> +	/* Iterate over the ports and see which ones are part of the
> +	 * vlan and for those ports add entry in the MAC table to
> +	 * copy the frames to the CPU
> +	 */
> +	for (p = 0; p < lan966x->num_phys_ports; p++) {
> +		struct lan966x_port *port = lan966x->ports[p];
> +
> +		if (!port ||
> +		    !lan966x_vlan_port_member_vlan_mask(port, vid))
> +			continue;
> +
> +		lan966x_mac_cpu_learn(lan966x, port->dev->dev_addr, vid);
> +	}
> +
> +	/* Add an entry in the MAC table for the CPU
> +	 * Add the CPU part of the vlan only if there is another port in that
> +	 * vlan otherwise all the broadcast frames in that vlan will go to CPU
> +	 * even if none of the ports are in the vlan and then the CPU will just
> +	 * need to discard these frames. It is required to store this
> +	 * information so when a front port is added then it would add also the
> +	 * CPU port.
> +	 */
> +	if (lan966x_vlan_port_any_vlan_mask(lan966x, vid)) {
> +		lan966x_mac_cpu_learn(lan966x, dev->dev_addr, vid);
> +		lan966x_vlan_cpu_add_vlan_mask(lan966x, vid);
> +	}
> +
> +	lan966x_vlan_cpu_add_cpu_vlan_mask(lan966x, vid);
> +
> +	return 0;
> +}
> +
> +int lan966x_vlan_cpu_del_vlan(struct lan966x *lan966x,
> +			      struct net_device *dev,
> +			      u16 vid)
> +{
> +	int p;
> +
> +	/* Iterate over the ports and see which ones are part of the
> +	 * vlan and for those ports remove entry in the MAC table to
> +	 * copy the frames to the CPU
> +	 */
> +	for (p = 0; p < lan966x->num_phys_ports; p++) {
> +		struct lan966x_port *port = lan966x->ports[p];
> +
> +		if (!port ||
> +		    !lan966x_vlan_port_member_vlan_mask(port, vid))
> +			continue;
> +
> +		lan966x_mac_cpu_forget(lan966x, port->dev->dev_addr, vid);
> +	}
> +
> +	/* Remove an entry in the MAC table for the CPU */
> +	lan966x_mac_cpu_forget(lan966x, dev->dev_addr, vid);
> +
> +	/* Remove the CPU part of the vlan */
> +	lan966x_vlan_cpu_del_cpu_vlan_mask(lan966x, vid);
> +	lan966x_vlan_cpu_del_vlan_mask(lan966x, vid);
> +
> +	return 0;
> +}
> +
> +int lan966x_vlan_rx_add_vid(struct net_device *dev, __be16 proto, u16 vid)
> +{
> +	struct lan966x_port *port = netdev_priv(dev);
> +
> +	lan966x_vlan_port_set_vid(port, vid, false, false);
> +	lan966x_vlan_port_add_vlan_mask(port, vid);
> +	lan966x_vlan_port_apply(port);
> +
> +	return 0;
> +}
> +
> +int lan966x_vlan_rx_kill_vid(struct net_device *dev, __be16 proto,
> +			     u16 vid)
> +{
> +	struct lan966x_port *port = netdev_priv(dev);
> +
> +	lan966x_vlan_port_remove_vid(port, vid);
> +	lan966x_vlan_port_del_vlan_mask(port, vid);
> +	lan966x_vlan_port_apply(port);
> +
> +	return 0;
> +}
> +
> +void lan966x_vlan_mode(struct lan966x_port *port,
> +		       netdev_features_t features)
> +{
> +	struct lan966x *lan966x = port->lan966x;
> +	u32 val;
> +
> +	/* Filtering */
> +	val = lan_rd(lan966x, ANA_VLANMASK);
> +	if (features & NETIF_F_HW_VLAN_CTAG_FILTER)
> +		val |= BIT(port->chip_port);
> +	else
> +		val &= ~BIT(port->chip_port);
> +	lan_wr(val, lan966x, ANA_VLANMASK);
> +}
> -- 
> 2.33.0
>
Horatiu Vultur Dec. 16, 2021, 2:34 p.m. UTC | #3
The 12/15/2021 23:50, Vladimir Oltean wrote:
> 
> On Wed, Dec 15, 2021 at 01:13:06PM +0100, Horatiu Vultur wrote:
> > This patch adds basic support to offload in the HW the forwarding of the
> > frames. The driver registers to the switchdev callbacks and implements
> > the callbacks for attributes SWITCHDEV_ATTR_ID_PORT_STP_STATE and
> > SWITCHDEV_ATTR_ID_BRIDGE_AGEING_TIME.
> > It is not allowed to add a lan966x port to a bridge that contains a
> > different interface than lan966x.
> >
> > Signed-off-by: Horatiu Vultur <horatiu.vultur@microchip.com>
> > ---
> >  .../net/ethernet/microchip/lan966x/Kconfig    |   1 +
> >  .../net/ethernet/microchip/lan966x/Makefile   |   2 +-
> >  .../ethernet/microchip/lan966x/lan966x_main.c |  16 +-
> >  .../ethernet/microchip/lan966x/lan966x_main.h |  11 +
> >  .../microchip/lan966x/lan966x_switchdev.c     | 393 ++++++++++++++++++
> >  5 files changed, 419 insertions(+), 4 deletions(-)
> >  create mode 100644 drivers/net/ethernet/microchip/lan966x/lan966x_switchdev.c
> >
> > diff --git a/drivers/net/ethernet/microchip/lan966x/Kconfig b/drivers/net/ethernet/microchip/lan966x/Kconfig
> > index 2860a8c9923d..ac273f84b69e 100644
> > --- a/drivers/net/ethernet/microchip/lan966x/Kconfig
> > +++ b/drivers/net/ethernet/microchip/lan966x/Kconfig
> > @@ -2,6 +2,7 @@ config LAN966X_SWITCH
> >       tristate "Lan966x switch driver"
> >       depends on HAS_IOMEM
> >       depends on OF
> > +     depends on NET_SWITCHDEV
> >       select PHYLINK
> >       select PACKING
> >       help
> > diff --git a/drivers/net/ethernet/microchip/lan966x/Makefile b/drivers/net/ethernet/microchip/lan966x/Makefile
> > index 2989ba528236..974229c51f55 100644
> > --- a/drivers/net/ethernet/microchip/lan966x/Makefile
> > +++ b/drivers/net/ethernet/microchip/lan966x/Makefile
> > @@ -6,4 +6,4 @@
> >  obj-$(CONFIG_LAN966X_SWITCH) += lan966x-switch.o
> >
> >  lan966x-switch-objs  := lan966x_main.o lan966x_phylink.o lan966x_port.o \
> > -                     lan966x_mac.o lan966x_ethtool.o
> > +                     lan966x_mac.o lan966x_ethtool.o lan966x_switchdev.o
> > diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_main.c b/drivers/net/ethernet/microchip/lan966x/lan966x_main.c
> > index dc40ac2eb246..ee453967da71 100644
> > --- a/drivers/net/ethernet/microchip/lan966x/lan966x_main.c
> > +++ b/drivers/net/ethernet/microchip/lan966x/lan966x_main.c
> > @@ -355,6 +355,11 @@ static const struct net_device_ops lan966x_port_netdev_ops = {
> >       .ndo_get_port_parent_id         = lan966x_port_get_parent_id,
> >  };
> >
> > +bool lan966x_netdevice_check(const struct net_device *dev)
> > +{
> > +     return dev->netdev_ops == &lan966x_port_netdev_ops;
> > +}
> > +
> >  static int lan966x_port_xtr_status(struct lan966x *lan966x, u8 grp)
> >  {
> >       return lan_rd(lan966x, QS_XTR_RD(grp));
> > @@ -491,6 +496,9 @@ static irqreturn_t lan966x_xtr_irq_handler(int irq, void *args)
> >
> >               skb->protocol = eth_type_trans(skb, dev);
> >
> > +             if (lan966x->bridge_mask & BIT(src_port))
> > +                     skb->offload_fwd_mark = 1;
> > +
> >               netif_rx_ni(skb);
> >               dev->stats.rx_bytes += len;
> >               dev->stats.rx_packets++;
> > @@ -578,9 +586,6 @@ static int lan966x_probe_port(struct lan966x *lan966x, u32 p,
> >
> >       eth_hw_addr_gen(dev, lan966x->base_mac, p + 1);
> >
> > -     lan966x_mac_learn(lan966x, PGID_CPU, dev->dev_addr, port->pvid,
> > -                       ENTRYTYPE_LOCKED);
> > -
> >       port->phylink_config.dev = &port->dev->dev;
> >       port->phylink_config.type = PHYLINK_NETDEV;
> >       port->phylink_pcs.poll = true;
> > @@ -897,6 +902,8 @@ static int lan966x_probe(struct platform_device *pdev)
> >               lan966x_port_init(lan966x->ports[p]);
> >       }
> >
> > +     lan966x_register_notifier_blocks(lan966x);
> 
> To be clear, "singleton" would mean that irrespective of the number of
> driver instances, this function would be called once. So calling it from
> lan966x_probe() isn't exactly a good choice, since every instance of the
> driver "probes".

Ah.. yes. I will update it in the next version.

> 
> int dsa_slave_register_notifier(void)
> {
>         struct notifier_block *nb;
>         int err;
> 
>         err = register_netdevice_notifier(&dsa_slave_nb);
>         if (err)
>                 return err;
> 
>         err = register_switchdev_notifier(&dsa_slave_switchdev_notifier);
>         if (err)
>                 goto err_switchdev_nb;
> 
>         nb = &dsa_slave_switchdev_blocking_notifier;
>         err = register_switchdev_blocking_notifier(nb);
>         if (err)
>                 goto err_switchdev_blocking_nb;
> }
> 
> static int __init dsa_init_module(void)
> {
>         rc = dsa_slave_register_notifier();
> }
> module_init(dsa_init_module);
> 
> > +
> >       return 0;
> >
> >  cleanup_ports:
> > @@ -915,6 +922,8 @@ static int lan966x_remove(struct platform_device *pdev)
> >  {
> >       struct lan966x *lan966x = platform_get_drvdata(pdev);
> >
> > +     lan966x_unregister_notifier_blocks(lan966x);
> > +
> >       lan966x_cleanup_ports(lan966x);
> >
> >       cancel_delayed_work_sync(&lan966x->stats_work);
> > @@ -922,6 +931,7 @@ static int lan966x_remove(struct platform_device *pdev)
> >       mutex_destroy(&lan966x->stats_lock);
> >
> >       lan966x_mac_purge_entries(lan966x);
> > +     lan966x_ext_purge_entries();
> 
> Broken with multiple lan966x driver instances - you'd erase all other
> drivers' tabs keps on bridges in the system as soon as one single switch
> is unbound from its driver.

This will not be needed anymore in next version.

> 
> >
> >       return 0;
> >  }
> > diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_main.h b/drivers/net/ethernet/microchip/lan966x/lan966x_main.h
> > index fcd5d09a070c..3d228c9c0521 100644
> > --- a/drivers/net/ethernet/microchip/lan966x/lan966x_main.h
> > +++ b/drivers/net/ethernet/microchip/lan966x/lan966x_main.h
> > @@ -75,6 +75,10 @@ struct lan966x {
> >
> >       u8 base_mac[ETH_ALEN];
> >
> > +     struct net_device *bridge;
> > +     u16 bridge_mask;
> > +     u16 bridge_fwd_mask;
> > +
> >       struct list_head mac_entries;
> >       spinlock_t mac_lock; /* lock for mac_entries list */
> >
> > @@ -122,6 +126,11 @@ extern const struct phylink_mac_ops lan966x_phylink_mac_ops;
> >  extern const struct phylink_pcs_ops lan966x_phylink_pcs_ops;
> >  extern const struct ethtool_ops lan966x_ethtool_ops;
> >
> > +bool lan966x_netdevice_check(const struct net_device *dev);
> > +
> > +void lan966x_register_notifier_blocks(struct lan966x *lan966x);
> > +void lan966x_unregister_notifier_blocks(struct lan966x *lan966x);
> > +
> >  void lan966x_stats_get(struct net_device *dev,
> >                      struct rtnl_link_stats64 *stats);
> >  int lan966x_stats_init(struct lan966x *lan966x);
> > @@ -157,6 +166,8 @@ int lan966x_mac_add_entry(struct lan966x *lan966x,
> >  void lan966x_mac_purge_entries(struct lan966x *lan966x);
> >  irqreturn_t lan966x_mac_irq_handler(struct lan966x *lan966x);
> >
> > +void lan966x_ext_purge_entries(void);
> > +
> >  static inline void __iomem *lan_addr(void __iomem *base[],
> >                                    int id, int tinst, int tcnt,
> >                                    int gbase, int ginst,
> > diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_switchdev.c b/drivers/net/ethernet/microchip/lan966x/lan966x_switchdev.c
> > new file mode 100644
> > index 000000000000..722ce7cb61b3
> > --- /dev/null
> > +++ b/drivers/net/ethernet/microchip/lan966x/lan966x_switchdev.c
> > @@ -0,0 +1,393 @@
> > +// SPDX-License-Identifier: GPL-2.0+
> > +
> > +#include <linux/if_bridge.h>
> > +#include <net/switchdev.h>
> > +
> > +#include "lan966x_main.h"
> > +
> > +static struct notifier_block lan966x_netdevice_nb __read_mostly;
> > +static struct notifier_block lan966x_switchdev_nb __read_mostly;
> > +static struct notifier_block lan966x_switchdev_blocking_nb __read_mostly;
> > +
> > +static LIST_HEAD(ext_entries);
> > +
> > +struct lan966x_ext_entry {
> > +     struct list_head list;
> > +     struct net_device *dev;
> > +     u32 ports;
> > +     struct lan966x *lan966x;
> > +};
> > +
> > +static void lan966x_update_fwd_mask(struct lan966x *lan966x)
> > +{
> > +     int i;
> > +
> > +     for (i = 0; i < lan966x->num_phys_ports; i++) {
> > +             struct lan966x_port *port = lan966x->ports[i];
> > +             unsigned long mask = 0;
> > +
> > +             if (port && lan966x->bridge_fwd_mask & BIT(i))
> > +                     mask = lan966x->bridge_fwd_mask & ~BIT(i);
> > +
> > +             mask |= BIT(CPU_PORT);
> > +
> > +             lan_wr(ANA_PGID_PGID_SET(mask),
> > +                    lan966x, ANA_PGID(PGID_SRC + i));
> > +     }
> 
> I vaguely remember this was implemented better in previous versions of
> the patch set, and the restriction to not allow multiple bridges
> spanning the same switch wasn't there. Why do you keep disallowing
> multiple bridges for all the Microchip hardware? There are very real use
> cases that need them.

I think there are some cases where this is required. I am not 100% but just
to be in sync with the other Microchip devices, I have implemented the
same.

> 
> > +}
> > +
> > +static void lan966x_port_stp_state_set(struct lan966x_port *port, u8 state)
> > +{
> > +     struct lan966x *lan966x = port->lan966x;
> > +     bool learn_ena = false;
> > +
> > +     if (state == BR_STATE_FORWARDING || state == BR_STATE_LEARNING)
> > +             learn_ena = true;
> > +
> > +     if (state == BR_STATE_FORWARDING)
> > +             lan966x->bridge_fwd_mask |= BIT(port->chip_port);
> > +     else
> > +             lan966x->bridge_fwd_mask &= ~BIT(port->chip_port);
> > +
> > +     lan_rmw(ANA_PORT_CFG_LEARN_ENA_SET(learn_ena),
> > +             ANA_PORT_CFG_LEARN_ENA,
> > +             lan966x, ANA_PORT_CFG(port->chip_port));
> > +
> > +     lan966x_update_fwd_mask(lan966x);
> > +}
> > +
> > +static void lan966x_port_ageing_set(struct lan966x_port *port,
> > +                                 unsigned long ageing_clock_t)
> > +{
> > +     unsigned long ageing_jiffies = clock_t_to_jiffies(ageing_clock_t);
> > +     u32 ageing_time = jiffies_to_msecs(ageing_jiffies) / 1000;
> > +
> > +     lan966x_mac_set_ageing(port->lan966x, ageing_time);
> > +}
> > +
> > +static int lan966x_port_attr_set(struct net_device *dev, const void *ctx,
> > +                              const struct switchdev_attr *attr,
> > +                              struct netlink_ext_ack *extack)
> > +{
> > +     struct lan966x_port *port = netdev_priv(dev);
> > +     int err = 0;
> > +
> > +     if (ctx && ctx != port)
> > +             return 0;
> > +
> > +     switch (attr->id) {
> > +     case SWITCHDEV_ATTR_ID_PORT_STP_STATE:
> > +             lan966x_port_stp_state_set(port, attr->u.stp_state);
> > +             break;
> > +     case SWITCHDEV_ATTR_ID_BRIDGE_AGEING_TIME:
> > +             lan966x_port_ageing_set(port, attr->u.ageing_time);
> > +             break;
> > +     default:
> > +             err = -EOPNOTSUPP;
> > +             break;
> > +     }
> > +
> > +     return err;
> > +}
> > +
> > +static int lan966x_port_bridge_join(struct lan966x_port *port,
> > +                                 struct net_device *bridge,
> > +                                 struct netlink_ext_ack *extack)
> > +{
> > +     struct lan966x *lan966x = port->lan966x;
> > +     struct net_device *dev = port->dev;
> > +     int err;
> > +
> > +     if (!lan966x->bridge_mask) {
> > +             lan966x->bridge = bridge;
> > +     } else {
> > +             if (lan966x->bridge != bridge)
> 
> NL_SET_ERR_MSG_MOD(extack, "<excuse>");
> 
> > +                     return -ENODEV;
> > +     }
> > +
> > +     err = switchdev_bridge_port_offload(dev, dev, port,
> > +                                         &lan966x_switchdev_nb,
> > +                                         &lan966x_switchdev_blocking_nb,
> > +                                         false, extack);
> > +     if (err)
> > +             return err;
> > +
> > +     lan966x->bridge_mask |= BIT(port->chip_port);
> > +
> > +     return 0;
> > +}
> > +
> > +static void lan966x_port_bridge_leave(struct lan966x_port *port,
> > +                                   struct net_device *bridge)
> > +{
> > +     struct lan966x *lan966x = port->lan966x;
> > +
> > +     lan966x->bridge_mask &= ~BIT(port->chip_port);
> > +
> > +     if (!lan966x->bridge_mask)
> > +             lan966x->bridge = NULL;
> > +
> > +     lan966x_mac_cpu_learn(lan966x, port->dev->dev_addr, PORT_PVID);
> > +}
> > +
> > +static int lan966x_port_changeupper(struct net_device *dev,
> > +                                 struct netdev_notifier_changeupper_info *info)
> > +{
> > +     struct lan966x_port *port = netdev_priv(dev);
> > +     struct netlink_ext_ack *extack;
> > +     int err = 0;
> > +
> > +     extack = netdev_notifier_info_to_extack(&info->info);
> > +
> > +     if (netif_is_bridge_master(info->upper_dev)) {
> > +             if (info->linking)
> > +                     err = lan966x_port_bridge_join(port, info->upper_dev,
> > +                                                    extack);
> > +             else
> > +                     lan966x_port_bridge_leave(port, info->upper_dev);
> > +     }
> > +
> > +     return err;
> > +}
> > +
> > +static int lan966x_port_prechangeupper(struct net_device *dev,
> > +                                    struct netdev_notifier_changeupper_info *info)
> > +{
> > +     struct lan966x_port *port = netdev_priv(dev);
> > +
> > +     if (netif_is_bridge_master(info->upper_dev) && !info->linking)
> > +             switchdev_bridge_port_unoffload(port->dev, port,
> > +                                             &lan966x_switchdev_nb,
> > +                                             &lan966x_switchdev_blocking_nb);
> > +
> > +     return NOTIFY_DONE;
> > +}
> > +
> > +static int lan966x_port_add_addr(struct net_device *dev, bool up)
> > +{
> > +     struct lan966x_port *port = netdev_priv(dev);
> > +     struct lan966x *lan966x = port->lan966x;
> > +     u16 vid;
> > +
> > +     vid = port->pvid;
> > +
> > +     if (up)
> > +             lan966x_mac_cpu_learn(lan966x, dev->dev_addr, vid);
> > +     else
> > +             lan966x_mac_cpu_forget(lan966x, dev->dev_addr, vid);
> > +
> > +     return 0;
> > +}
> > +
> > +static struct lan966x_ext_entry *lan966x_ext_find_entry(struct net_device *dev)
> > +{
> > +     struct lan966x_ext_entry *ext_entry;
> > +
> > +     list_for_each_entry(ext_entry, &ext_entries, list) {
> > +             if (ext_entry->dev == dev)
> > +                     return ext_entry;
> > +     }
> > +
> > +     return NULL;
> > +}
> > +
> > +static bool lan966x_ext_add_entry(struct net_device *dev, void *lan966x)
> > +{
> > +     struct lan966x_ext_entry *ext_entry;
> > +
> > +     ext_entry = lan966x_ext_find_entry(dev);
> > +     if (ext_entry) {
> > +             if (ext_entry->lan966x)
> > +                     return false;
> > +
> > +             ext_entry->ports++;
> > +             return true;
> > +     }
> > +
> > +     ext_entry = kzalloc(sizeof(*ext_entry), GFP_KERNEL);
> > +     if (!ext_entry)
> > +             return false;
> > +
> > +     ext_entry->dev = dev;
> > +     ext_entry->ports = 1;
> > +     ext_entry->lan966x = lan966x;
> > +     list_add_tail(&ext_entry->list, &ext_entries);
> > +     return true;
> > +}
> > +
> > +static void lan966x_ext_remove_entry(struct net_device *dev)
> > +{
> > +     struct lan966x_ext_entry *ext_entry;
> > +
> > +     ext_entry = lan966x_ext_find_entry(dev);
> > +     if (!ext_entry)
> > +             return;
> > +
> > +     ext_entry->ports--;
> > +     if (!ext_entry->ports) {
> > +             list_del(&ext_entry->list);
> > +             kfree(ext_entry);
> > +     }
> > +}
> > +
> > +void lan966x_ext_purge_entries(void)
> > +{
> > +     struct lan966x_ext_entry *ext_entry, *tmp;
> > +
> > +     list_for_each_entry_safe(ext_entry, tmp, &ext_entries, list) {
> > +             list_del(&ext_entry->list);
> > +             kfree(ext_entry);
> > +     }
> > +}
> > +
> > +static int lan966x_ext_check_entry(struct net_device *dev,
> > +                                unsigned long event,
> > +                                void *ptr)
> > +{
> > +     struct netdev_notifier_changeupper_info *info;
> > +
> > +     if (event != NETDEV_PRECHANGEUPPER)
> > +             return 0;
> > +
> > +     info = ptr;
> > +     if (!netif_is_bridge_master(info->upper_dev))
> > +             return 0;
> > +
> > +     if (info->linking) {
> > +             if (!lan966x_ext_add_entry(info->upper_dev, NULL))
> > +                     return -EOPNOTSUPP;
> > +     } else {
> > +             lan966x_ext_remove_entry(info->upper_dev);
> > +     }
> > +
> > +     return NOTIFY_DONE;
> > +}
> > +
> > +static bool lan966x_port_ext_check_entry(struct net_device *dev,
> > +                                      struct netdev_notifier_changeupper_info *info)
> > +{
> > +     struct lan966x_port *port = netdev_priv(dev);
> > +     struct lan966x *lan966x = port->lan966x;
> > +     struct lan966x_ext_entry *entry;
> > +
> > +     if (!netif_is_bridge_master(info->upper_dev))
> > +             return true;
> > +
> > +     entry = lan966x_ext_find_entry(info->upper_dev);
> 
> "entry" is unused in the "else" block below, so logically speaking it
> could be moved inside the "if" block.
> 
> Anyway, this piece of code is objectively speaking very obscure: convoluted
> (lan966x_port_ext_check_entry calls lan966x_ext_find_entry _twice_, once
> here and once in lan966x_ext_add_entry ?!), no comments and poorly named
> (a lan966x_ext_entry represents a _bridge_ ?! what does "ext_entry"
> stand for?). Plus, with your design where the "ext_entries" list is
> global, and there are two instances of the driver, each driver would do
> this work twice and allocate memory twice. Although, I didn't really
> understand why you need to allocate memory to keep a tab on every bridge
> in the system in the first place.
> 
> If you move your check from NETDEV_PRECHANGEUPPER to NETDEV_CHANGEUPPER,
> you allow the upper/lower adjacency list relationship to have formed
> (allowing the use of netdev_for_each_lower_dev, and the newly joining
> interface will be a lower of the bridge). But you can still reject the
> bridge join.
> 
> So you can do something like this, and it should produce an equivalent
> effect (not compiled, not tested, written straight in the email body):
> 
> static int lan966x_foreign_bridging_check(struct net_device *bridge,
>                                           struct netlink_ext_ack *extack)
> {
>         struct lan966x *lan966x = NULL;
>         bool has_foreign = false;
>         struct net_device *dev;
>         struct list_head *iter;
> 
>         netdev_for_each_lower_dev(bridge, dev, iter) {
>                 if (lan966x_netdevice_check(dev)) {
>                         struct lan966x_port *port = netdev_priv(dev);
> 
>                         if (lan996x) {
>                                 /* Bridge already has at least one port
>                                  * of a lan966x switch inside it, check
>                                  * that it's the same instance of the
>                                  * driver.
>                                  */
>                                 if (port->lan966x != lan996x) {
>                                         NL_SET_ERR_MSG_MOD(extack, "Bridging between multiple lan966x switches disallowed");
>                                         return -EINVAL;
>                                 }
>                         } else {
>                                 /* This is the first lan966x port inside
>                                  * this bridge
>                                  */
>                                 lan966x = port->lan966x;
>                         }
>                 } else {
>                         has_foreign = true;
>                 }
> 
>                 if (lan966x && has_foreign) {
>                         NL_SET_ERR_MSG_MOD(extack, "Bridging lan966x ports with foreign interfaces disallowed");
>                         return -EINVAL;
>                 }
>         }
> 
>         return 0;
> }
> 
> and call this from two distinct call paths: from the NETDEV_CHANGEUPPER
> of foreign interfaces, and from the NETDEV_CHANGEUPPER of lan966x interfaces.
> 
> Is it just me, or does this look more obvious and straightforward?

Thanks for the code, it looks much more better and it is not to required
to keep the list of other bridges.

> 
> > +     if (info->linking) {
> > +             if (!entry)
> > +                     return lan966x_ext_add_entry(info->upper_dev, lan966x);
> > +
> > +             if (entry->lan966x == lan966x) {
> > +                     entry->ports++;
> > +                     return true;
> > +             }
> > +     } else {
> > +             lan966x_ext_remove_entry(info->upper_dev);
> > +             return true;
> > +     }
> > +
> > +     return false;
> > +}
> > +
> > +static int lan966x_netdevice_port_event(struct net_device *dev,
> > +                                     struct notifier_block *nb,
> > +                                     unsigned long event, void *ptr)
> > +{
> > +     int err = 0;
> > +
> > +     if (!lan966x_netdevice_check(dev))
> > +             return lan966x_ext_check_entry(dev, event, ptr);
> > +
> > +     switch (event) {
> > +     case NETDEV_PRECHANGEUPPER:
> > +             if (!lan966x_port_ext_check_entry(dev, ptr))
> > +                     return -EOPNOTSUPP;
> > +
> > +             err = lan966x_port_prechangeupper(dev, ptr);
> > +             break;
> > +     case NETDEV_CHANGEUPPER:
> > +             err = lan966x_port_changeupper(dev, ptr);
> > +             break;
> > +     case NETDEV_PRE_UP:
> > +             err = lan966x_port_add_addr(dev, true);
> > +             break;
> > +     case NETDEV_DOWN:
> > +             err = lan966x_port_add_addr(dev, false);
> 
> Any reason why you track your own NETDEV_PRE_UP/NETDEV_DOWN and don't do
> this directly in ->ndo_open/->ndo_close? Also, I don't think that the
> "lan966x_port_add_addr" brings much value over "lan966x_mac_cpu_learn"
> and "lan966x_mac_cpu_forget" called directly (especially if moved to
> lan966x_port_open and lan966x_port_stop). And I don't see the relevance
> of this change with respect to the commit title "add support to offload
> the forwarding". CPU learned entries are for termination.

OK, I will drop this for now. I will add this or in a different patch of
this series or will be another patch by itself.

> 
> > +             break;
> > +     }
> > +
> > +     return err;
> > +}
> > +
> > +static int lan966x_netdevice_event(struct notifier_block *nb,
> > +                                unsigned long event, void *ptr)
> > +{
> > +     struct net_device *dev = netdev_notifier_info_to_dev(ptr);
> > +     int ret;
> > +
> > +     ret = lan966x_netdevice_port_event(dev, nb, event, ptr);
> > +
> > +     return notifier_from_errno(ret);
> > +}
> > +
> > +static int lan966x_switchdev_event(struct notifier_block *nb,
> > +                                unsigned long event, void *ptr)
> > +{
> > +     struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
> > +     int err;
> > +
> > +     switch (event) {
> > +     case SWITCHDEV_PORT_ATTR_SET:
> > +             err = switchdev_handle_port_attr_set(dev, ptr,
> > +                                                  lan966x_netdevice_check,
> > +                                                  lan966x_port_attr_set);
> > +             return notifier_from_errno(err);
> > +     }
> > +
> > +     return NOTIFY_DONE;
> > +}
> > +
> > +static int lan966x_switchdev_blocking_event(struct notifier_block *nb,
> > +                                         unsigned long event,
> > +                                         void *ptr)
> > +{
> > +     struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
> > +     int err;
> > +
> > +     switch (event) {
> > +     case SWITCHDEV_PORT_ATTR_SET:
> > +             err = switchdev_handle_port_attr_set(dev, ptr,
> > +                                                  lan966x_netdevice_check,
> > +                                                  lan966x_port_attr_set);
> > +             return notifier_from_errno(err);
> > +     }
> > +
> > +     return NOTIFY_DONE;
> > +}
> > +
> > +static struct notifier_block lan966x_netdevice_nb __read_mostly = {
> > +     .notifier_call = lan966x_netdevice_event,
> > +};
> > +
> > +static struct notifier_block lan966x_switchdev_nb __read_mostly = {
> > +     .notifier_call = lan966x_switchdev_event,
> > +};
> > +
> > +static struct notifier_block lan966x_switchdev_blocking_nb __read_mostly = {
> > +     .notifier_call = lan966x_switchdev_blocking_event,
> > +};
> > +
> > +void lan966x_register_notifier_blocks(struct lan966x *lan966x)
> > +{
> > +     register_netdevice_notifier(&lan966x_netdevice_nb);
> > +     register_switchdev_notifier(&lan966x_switchdev_nb);
> > +     register_switchdev_blocking_notifier(&lan966x_switchdev_blocking_nb);
> > +}
> > +
> > +void lan966x_unregister_notifier_blocks(struct lan966x *lan966x)
> > +{
> > +     unregister_switchdev_blocking_notifier(&lan966x_switchdev_blocking_nb);
> > +     unregister_switchdev_notifier(&lan966x_switchdev_nb);
> > +     unregister_netdevice_notifier(&lan966x_netdevice_nb);
> > +}
> > --
> > 2.33.0
> >
Horatiu Vultur Dec. 17, 2021, 11:38 a.m. UTC | #4
The 12/16/2021 00:44, Vladimir Oltean wrote:
> 
> On Wed, Dec 15, 2021 at 01:13:07PM +0100, Horatiu Vultur wrote:
> > Extend the driver to support vlan filtering  by implementing the
> > switchdev calls SWITCHDEV_OBJ_ID_PORT_VLAN and
> > SWITCHDEV_ATTR_ID_BRIDGE_VLAN_FILTERING.
> 
> And the VLAN RX filtering net device ops.
> 
> >
> > Signed-off-by: Horatiu Vultur <horatiu.vultur@microchip.com>
> > ---
> >  .../net/ethernet/microchip/lan966x/Makefile   |   3 +-
> >  .../ethernet/microchip/lan966x/lan966x_main.c |  39 +-
> >  .../ethernet/microchip/lan966x/lan966x_main.h |  40 +-
> >  .../microchip/lan966x/lan966x_switchdev.c     | 113 ++++-
> >  .../ethernet/microchip/lan966x/lan966x_vlan.c | 444 ++++++++++++++++++
> >  5 files changed, 632 insertions(+), 7 deletions(-)
> >  create mode 100644 drivers/net/ethernet/microchip/lan966x/lan966x_vlan.c
> >
> > diff --git a/drivers/net/ethernet/microchip/lan966x/Makefile b/drivers/net/ethernet/microchip/lan966x/Makefile
> > index 974229c51f55..d82e896c2e53 100644
> > --- a/drivers/net/ethernet/microchip/lan966x/Makefile
> > +++ b/drivers/net/ethernet/microchip/lan966x/Makefile
> > @@ -6,4 +6,5 @@
> >  obj-$(CONFIG_LAN966X_SWITCH) += lan966x-switch.o
> >
> >  lan966x-switch-objs  := lan966x_main.o lan966x_phylink.o lan966x_port.o \
> > -                     lan966x_mac.o lan966x_ethtool.o lan966x_switchdev.o
> > +                     lan966x_mac.o lan966x_ethtool.o lan966x_switchdev.o \
> > +                     lan966x_vlan.o
> > diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_main.c b/drivers/net/ethernet/microchip/lan966x/lan966x_main.c
> > index ee453967da71..881c1678f3e9 100644
> > --- a/drivers/net/ethernet/microchip/lan966x/lan966x_main.c
> > +++ b/drivers/net/ethernet/microchip/lan966x/lan966x_main.c
> > @@ -103,17 +103,18 @@ static int lan966x_create_targets(struct platform_device *pdev,
> >  static int lan966x_port_set_mac_address(struct net_device *dev, void *p)
> >  {
> >       struct lan966x_port *port = netdev_priv(dev);
> > +     u16 pvid = lan966x_vlan_port_get_pvid(port);
> >       struct lan966x *lan966x = port->lan966x;
> >       const struct sockaddr *addr = p;
> >       int ret;
> >
> >       /* Learn the new net device MAC address in the mac table. */
> > -     ret = lan966x_mac_cpu_learn(lan966x, addr->sa_data, port->pvid);
> > +     ret = lan966x_mac_cpu_learn(lan966x, addr->sa_data, pvid);
> 
> Logically speaking, there is a divide of responsibility. The bridge
> emits switchdev FDB events for local MAC addresses, with a VID of 0
> (corresponding to VLAN-unaware bridging) as well as for each installed
> VLAN. Bridge VLAN 0 is equivalent to your UNAWARE_PVID macro. And the
> driver is solely responsible for the MAC address in the HOST_PVID VLAN.
> When the ndo_set_mac_address is called, you should just update the entry
> learned in the HOST_PVID. The bridge will get an NETDEV_CHANGEADDR event
> and update its local MAC addresses too, in the VLANs it handles.
> Otherwise, if you just learn in the pvid that the port is currently in,
> then RX filtering will be broken if you change your MAC address while
> you're under a bridge, then you leave that bridge and become standalone.
> So you need to re-learn the dev_addr in lan966x_port_bridge_leave, which
> makes the implementation a bit more complicated than it needs to be
> (unless I'm missing something about CPU-learned MAC addresses in VLANs
> that aren't currently active, you seem to be avoiding that even though
> it makes the driver keep a lot more state).

Yes, you are right. Thanks for the explanation.

> 
> >       if (ret)
> >               return ret;
> >
> >       /* Then forget the previous one. */
> > -     ret = lan966x_mac_cpu_forget(lan966x, dev->dev_addr, port->pvid);
> > +     ret = lan966x_mac_cpu_forget(lan966x, dev->dev_addr, pvid);
> >       if (ret)
> >               return ret;
> >
> > @@ -283,6 +284,12 @@ static void lan966x_ifh_set_ipv(void *ifh, u64 bypass)
> >               IFH_POS_IPV, IFH_LEN * 4, PACK, 0);
> >  }
> >
> > +static void lan966x_ifh_set_vid(void *ifh, u64 vid)
> > +{
> > +     packing(ifh, &vid, IFH_POS_TCI + IFH_WID_TCI - 1,
> > +             IFH_POS_TCI, IFH_LEN * 4, PACK, 0);
> > +}
> > +
> >  static int lan966x_port_xmit(struct sk_buff *skb, struct net_device *dev)
> >  {
> >       struct lan966x_port *port = netdev_priv(dev);
> > @@ -294,6 +301,7 @@ static int lan966x_port_xmit(struct sk_buff *skb, struct net_device *dev)
> >       lan966x_ifh_set_port(ifh, BIT_ULL(port->chip_port));
> >       lan966x_ifh_set_qos_class(ifh, skb->priority >= 7 ? 0x7 : skb->priority);
> >       lan966x_ifh_set_ipv(ifh, skb->priority >= 7 ? 0x7 : skb->priority);
> > +     lan966x_ifh_set_vid(ifh, skb_vlan_tag_get(skb));
> >
> >       return lan966x_port_ifh_xmit(skb, ifh, dev);
> >  }
> > @@ -343,6 +351,18 @@ static int lan966x_port_get_parent_id(struct net_device *dev,
> >       return 0;
> >  }
> >
> > +static int lan966x_port_set_features(struct net_device *dev,
> > +                                  netdev_features_t features)
> > +{
> > +     struct lan966x_port *port = netdev_priv(dev);
> > +     netdev_features_t changed = dev->features ^ features;
> > +
> > +     if (changed & NETIF_F_HW_VLAN_CTAG_FILTER)
> > +             lan966x_vlan_mode(port, features);
> > +
> > +     return 0;
> > +}
> > +
> >  static const struct net_device_ops lan966x_port_netdev_ops = {
> >       .ndo_open                       = lan966x_port_open,
> >       .ndo_stop                       = lan966x_port_stop,
> > @@ -353,6 +373,9 @@ static const struct net_device_ops lan966x_port_netdev_ops = {
> >       .ndo_get_stats64                = lan966x_stats_get,
> >       .ndo_set_mac_address            = lan966x_port_set_mac_address,
> >       .ndo_get_port_parent_id         = lan966x_port_get_parent_id,
> > +     .ndo_set_features               = lan966x_port_set_features,
> > +     .ndo_vlan_rx_add_vid            = lan966x_vlan_rx_add_vid,
> > +     .ndo_vlan_rx_kill_vid           = lan966x_vlan_rx_kill_vid,
> 
> Do you have any particular use case for NETIF_F_HW_VLAN_CTAG_FILTER on
> non-bridged ports?

Not from what I am aware of, so for now I can remove this.

> I find the fact that you implement these very strange
> and likely bogus: you set port->vlan_aware = false when a port leaves a
> bridge, yet you install VLANs to its RX filter as if those VLANs were to
> actually match on any VLAN-tagged packet... which they won't because
> lan966x_vlan_port_apply() clears ANA_VLAN_CFG_VLAN_AWARE_ENA_SET(1) when
> port->vlan_aware isn't set. So you end up being "filtering" but not "aware"
> - all packets get classified to the same VLAN, which isn't dropped.
> 
> >  };
> >
> >  bool lan966x_netdevice_check(const struct net_device *dev)
> > @@ -575,13 +598,16 @@ static int lan966x_probe_port(struct lan966x *lan966x, u32 p,
> >       port->dev = dev;
> >       port->lan966x = lan966x;
> >       port->chip_port = p;
> > -     port->pvid = PORT_PVID;
> >       lan966x->ports[p] = port;
> >
> >       dev->max_mtu = ETH_MAX_MTU;
> >
> >       dev->netdev_ops = &lan966x_port_netdev_ops;
> >       dev->ethtool_ops = &lan966x_ethtool_ops;
> > +     dev->hw_features |= NETIF_F_HW_VLAN_CTAG_FILTER;
> > +     dev->features |= NETIF_F_HW_VLAN_CTAG_FILTER |
> > +                      NETIF_F_HW_VLAN_CTAG_TX |
> > +                      NETIF_F_HW_VLAN_STAG_TX;
> >       dev->needed_headroom = IFH_LEN * sizeof(u32);
> >
> >       eth_hw_addr_gen(dev, lan966x->base_mac, p + 1);
> > @@ -625,6 +651,10 @@ static int lan966x_probe_port(struct lan966x *lan966x, u32 p,
> >               return err;
> >       }
> >
> > +     lan966x_vlan_port_set_vlan_aware(port, 0);
> > +     lan966x_vlan_port_set_vid(port, HOST_PVID, false, false);
> > +     lan966x_vlan_port_apply(port);
> > +
> >       return 0;
> >  }
> >
> > @@ -635,6 +665,9 @@ static void lan966x_init(struct lan966x *lan966x)
> >       /* MAC table initialization */
> >       lan966x_mac_init(lan966x);
> >
> > +     /* Vlan initialization */
> > +     lan966x_vlan_init(lan966x);
> 
> Curious how the lan966x_ext_entry stuff doesn't have any comment and
> lan966x_vlan_init has such a trivial one?!
> 
> > +
> >       /* Flush queues */
> >       lan_wr(lan_rd(lan966x, QS_XTR_FLUSH) |
> >              GENMASK(1, 0),
> > diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_main.h b/drivers/net/ethernet/microchip/lan966x/lan966x_main.h
> > index 3d228c9c0521..6d0d922617ae 100644
> > --- a/drivers/net/ethernet/microchip/lan966x/lan966x_main.h
> > +++ b/drivers/net/ethernet/microchip/lan966x/lan966x_main.h
> > @@ -4,6 +4,7 @@
> >  #define __LAN966X_MAIN_H__
> >
> >  #include <linux/etherdevice.h>
> > +#include <linux/if_vlan.h>
> >  #include <linux/jiffies.h>
> >  #include <linux/phy.h>
> >  #include <linux/phylink.h>
> > @@ -22,7 +23,8 @@
> >  #define PGID_SRC                     80
> >  #define PGID_ENTRIES                 89
> >
> > -#define PORT_PVID                    0
> > +#define UNAWARE_PVID                 0
> > +#define HOST_PVID                    4095
> >
> >  /* Reserved amount for (SRC, PRIO) at index 8*SRC + PRIO */
> >  #define QSYS_Q_RSRV                  95
> > @@ -82,6 +84,9 @@ struct lan966x {
> >       struct list_head mac_entries;
> >       spinlock_t mac_lock; /* lock for mac_entries list */
> >
> > +     u16 vlan_mask[VLAN_N_VID];
> > +     DECLARE_BITMAP(cpu_vlan_mask, VLAN_N_VID);
> > +
> >       /* stats */
> >       const struct lan966x_stat_layout *stats_layout;
> >       u32 num_stats;
> > @@ -113,6 +118,8 @@ struct lan966x_port {
> >
> >       u8 chip_port;
> >       u16 pvid;
> > +     u16 vid;
> > +     u8 vlan_aware;
> 
> bool
> 
> >
> >       struct phylink_config phylink_config;
> >       struct phylink_pcs phylink_pcs;
> > @@ -168,6 +175,37 @@ irqreturn_t lan966x_mac_irq_handler(struct lan966x *lan966x);
> >
> >  void lan966x_ext_purge_entries(void);
> >
> > +void lan966x_vlan_init(struct lan966x *lan966x);
> > +void lan966x_vlan_port_apply(struct lan966x_port *port);
> > +
> > +int lan966x_vlan_rx_add_vid(struct net_device *dev, __be16 proto, u16 vid);
> > +int lan966x_vlan_rx_kill_vid(struct net_device *dev, __be16 proto, u16 vid);
> > +
> > +void lan966x_vlan_mode(struct lan966x_port *port, netdev_features_t features);
> > +u16 lan966x_vlan_port_get_pvid(struct lan966x_port *port);
> > +
> > +bool lan966x_vlan_cpu_member_cpu_vlan_mask(struct lan966x *lan966x, u16 vid);
> > +void lan966x_vlan_cpu_add_cpu_vlan_mask(struct lan966x *lan966x, u16 vid);
> > +bool lan966x_vlan_port_any_vlan_mask(struct lan966x *lan966x, u16 vid);
> > +
> > +void lan966x_vlan_cpu_set_vlan_aware(struct lan966x_port *port);
> > +void lan966x_vlan_port_set_vlan_aware(struct lan966x_port *port,
> > +                                   bool vlan_aware);
> > +int lan966x_vlan_port_set_vid(struct lan966x_port *port, u16 vid,
> > +                           bool pvid, bool untagged);
> > +int lan966x_vlan_port_add_vlan(struct lan966x_port *port,
> > +                            u16 vid,
> > +                            bool pvid,
> > +                            bool untagged);
> > +int lan966x_vlan_port_del_vlan(struct lan966x_port *port,
> > +                            u16 vid);
> > +int lan966x_vlan_cpu_add_vlan(struct lan966x *lan966x,
> > +                           struct net_device *dev,
> > +                           u16 vid);
> > +int lan966x_vlan_cpu_del_vlan(struct lan966x *lan966x,
> > +                           struct net_device *dev,
> > +                           u16 vid);
> > +
> >  static inline void __iomem *lan_addr(void __iomem *base[],
> >                                    int id, int tinst, int tcnt,
> >                                    int gbase, int ginst,
> > diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_switchdev.c b/drivers/net/ethernet/microchip/lan966x/lan966x_switchdev.c
> > index 722ce7cb61b3..61f9e906cf80 100644
> > --- a/drivers/net/ethernet/microchip/lan966x/lan966x_switchdev.c
> > +++ b/drivers/net/ethernet/microchip/lan966x/lan966x_switchdev.c
> > @@ -82,6 +82,11 @@ static int lan966x_port_attr_set(struct net_device *dev, const void *ctx,
> >       case SWITCHDEV_ATTR_ID_BRIDGE_AGEING_TIME:
> >               lan966x_port_ageing_set(port, attr->u.ageing_time);
> >               break;
> > +     case SWITCHDEV_ATTR_ID_BRIDGE_VLAN_FILTERING:
> > +             lan966x_vlan_port_set_vlan_aware(port, attr->u.vlan_filtering);
> > +             lan966x_vlan_port_apply(port);
> > +             lan966x_vlan_cpu_set_vlan_aware(port);
> > +             break;
> >       default:
> >               err = -EOPNOTSUPP;
> >               break;
> > @@ -127,7 +132,12 @@ static void lan966x_port_bridge_leave(struct lan966x_port *port,
> >       if (!lan966x->bridge_mask)
> >               lan966x->bridge = NULL;
> >
> > -     lan966x_mac_cpu_learn(lan966x, port->dev->dev_addr, PORT_PVID);
> > +     /* Set the port back to host mode */
> > +     lan966x_vlan_port_set_vlan_aware(port, false);
> > +     lan966x_vlan_port_set_vid(port, HOST_PVID, false, false);
> > +     lan966x_vlan_port_apply(port);
> > +
> > +     lan966x_mac_cpu_learn(lan966x, port->dev->dev_addr, HOST_PVID);
> >  }
> >
> >  static int lan966x_port_changeupper(struct net_device *dev,
> > @@ -169,7 +179,7 @@ static int lan966x_port_add_addr(struct net_device *dev, bool up)
> >       struct lan966x *lan966x = port->lan966x;
> >       u16 vid;
> >
> > -     vid = port->pvid;
> > +     vid = lan966x_vlan_port_get_pvid(port);
> >
> >       if (up)
> >               lan966x_mac_cpu_learn(lan966x, dev->dev_addr, vid);
> > @@ -348,6 +358,95 @@ static int lan966x_switchdev_event(struct notifier_block *nb,
> >       return NOTIFY_DONE;
> >  }
> >
> > +static int lan966x_handle_port_vlan_add(struct lan966x_port *port,
> > +                                     const struct switchdev_obj *obj)
> > +{
> > +     const struct switchdev_obj_port_vlan *v = SWITCHDEV_OBJ_PORT_VLAN(obj);
> > +     struct lan966x *lan966x = port->lan966x;
> > +
> > +     /* When adding a port to a vlan, we get a callback for the port but
> > +      * also for the bridge. When get the callback for the bridge just bail
> > +      * out. Then when the bridge is added to the vlan, then we get a
> > +      * callback here but in this case the flags has set:
> > +      * BRIDGE_VLAN_INFO_BRENTRY. In this case it means that the CPU
> > +      * port is added to the vlan, so the broadcast frames and unicast frames
> > +      * with dmac of the bridge should be foward to CPU.
> > +      */
> > +     if (netif_is_bridge_master(obj->orig_dev) &&
> > +         !(v->flags & BRIDGE_VLAN_INFO_BRENTRY))
> > +             return 0;
> > +
> > +     if (!netif_is_bridge_master(obj->orig_dev))
> > +             return lan966x_vlan_port_add_vlan(port, v->vid,
> > +                                               v->flags & BRIDGE_VLAN_INFO_PVID,
> > +                                               v->flags & BRIDGE_VLAN_INFO_UNTAGGED);
> > +
> > +     if (netif_is_bridge_master(obj->orig_dev))
> 
> "else" will suffice.
> 
> > +             return lan966x_vlan_cpu_add_vlan(lan966x, obj->orig_dev, v->vid);
> > +
> > +     return 0;
> > +}
> > +
> > +static int lan966x_handle_port_obj_add(struct net_device *dev, const void *ctx,
> > +                                    const struct switchdev_obj *obj,
> > +                                    struct netlink_ext_ack *extack)
> > +{
> > +     struct lan966x_port *port = netdev_priv(dev);
> > +     int err;
> > +
> > +     if (ctx && ctx != port)
> > +             return 0;
> > +
> > +     switch (obj->id) {
> > +     case SWITCHDEV_OBJ_ID_PORT_VLAN:
> > +             err = lan966x_handle_port_vlan_add(port, obj);
> > +             break;
> > +     default:
> > +             err = -EOPNOTSUPP;
> > +             break;
> > +     }
> > +
> > +     return err;
> > +}
> > +
> > +static int lan966x_handle_port_vlan_del(struct lan966x_port *port,
> > +                                     const struct switchdev_obj *obj)
> > +{
> > +     const struct switchdev_obj_port_vlan *v = SWITCHDEV_OBJ_PORT_VLAN(obj);
> > +     struct lan966x *lan966x = port->lan966x;
> > +
> > +     /* In case the physical port gets called */
> > +     if (!netif_is_bridge_master(obj->orig_dev))
> > +             return lan966x_vlan_port_del_vlan(port, v->vid);
> > +
> > +     /* In case the bridge gets called */
> > +     if (netif_is_bridge_master(obj->orig_dev))
> 
> likewise.
> 
> > +             return lan966x_vlan_cpu_del_vlan(lan966x, obj->orig_dev, v->vid);
> > +
> > +     return 0;
> > +}
> > +
> > +static int lan966x_handle_port_obj_del(struct net_device *dev, const void *ctx,
> > +                                    const struct switchdev_obj *obj)
> > +{
> > +     struct lan966x_port *port = netdev_priv(dev);
> > +     int err;
> > +
> > +     if (ctx && ctx != port)
> > +             return 0;
> > +
> > +     switch (obj->id) {
> > +     case SWITCHDEV_OBJ_ID_PORT_VLAN:
> > +             err = lan966x_handle_port_vlan_del(port, obj);
> > +             break;
> > +     default:
> > +             err = -EOPNOTSUPP;
> > +             break;
> > +     }
> > +
> > +     return err;
> > +}
> > +
> >  static int lan966x_switchdev_blocking_event(struct notifier_block *nb,
> >                                           unsigned long event,
> >                                           void *ptr)
> > @@ -356,6 +455,16 @@ static int lan966x_switchdev_blocking_event(struct notifier_block *nb,
> >       int err;
> >
> >       switch (event) {
> > +     case SWITCHDEV_PORT_OBJ_ADD:
> > +             err = switchdev_handle_port_obj_add(dev, ptr,
> > +                                                 lan966x_netdevice_check,
> > +                                                 lan966x_handle_port_obj_add);
> > +             return notifier_from_errno(err);
> > +     case SWITCHDEV_PORT_OBJ_DEL:
> > +             err = switchdev_handle_port_obj_del(dev, ptr,
> > +                                                 lan966x_netdevice_check,
> > +                                                 lan966x_handle_port_obj_del);
> > +             return notifier_from_errno(err);
> >       case SWITCHDEV_PORT_ATTR_SET:
> >               err = switchdev_handle_port_attr_set(dev, ptr,
> >                                                    lan966x_netdevice_check,
> > diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_vlan.c b/drivers/net/ethernet/microchip/lan966x/lan966x_vlan.c
> > new file mode 100644
> > index 000000000000..e8ff95bb65fa
> > --- /dev/null
> > +++ b/drivers/net/ethernet/microchip/lan966x/lan966x_vlan.c
> > @@ -0,0 +1,444 @@
> > +// SPDX-License-Identifier: GPL-2.0+
> > +
> > +#include "lan966x_main.h"
> > +
> > +#define VLANACCESS_CMD_IDLE          0
> > +#define VLANACCESS_CMD_READ          1
> > +#define VLANACCESS_CMD_WRITE         2
> > +#define VLANACCESS_CMD_INIT          3
> > +
> > +static int lan966x_vlan_get_status(struct lan966x *lan966x)
> > +{
> > +     return lan_rd(lan966x, ANA_VLANACCESS);
> > +}
> > +
> > +static int lan966x_vlan_wait_for_completion(struct lan966x *lan966x)
> > +{
> > +     u32 val;
> > +
> > +     return readx_poll_timeout(lan966x_vlan_get_status,
> > +             lan966x, val,
> > +             (val & ANA_VLANACCESS_VLAN_TBL_CMD) ==
> > +             VLANACCESS_CMD_IDLE,
> > +             TABLE_UPDATE_SLEEP_US, TABLE_UPDATE_TIMEOUT_US);
> > +}
> > +
> > +static int lan966x_vlan_set_mask(struct lan966x *lan966x, u16 vid)
> > +{
> > +     u16 mask = lan966x->vlan_mask[vid];
> > +     bool cpu_dis;
> > +
> > +     cpu_dis = !(mask & BIT(CPU_PORT));
> > +
> > +     /* Set flags and the VID to configure */
> > +     lan_rmw(ANA_VLANTIDX_VLAN_PGID_CPU_DIS_SET(cpu_dis) |
> > +             ANA_VLANTIDX_V_INDEX_SET(vid),
> > +             ANA_VLANTIDX_VLAN_PGID_CPU_DIS |
> > +             ANA_VLANTIDX_V_INDEX,
> > +             lan966x, ANA_VLANTIDX);
> > +
> > +     /* Set the vlan port members mask */
> > +     lan_rmw(ANA_VLAN_PORT_MASK_VLAN_PORT_MASK_SET(mask),
> > +             ANA_VLAN_PORT_MASK_VLAN_PORT_MASK,
> > +             lan966x, ANA_VLAN_PORT_MASK);
> > +
> > +     /* Issue a write command */
> > +     lan_rmw(ANA_VLANACCESS_VLAN_TBL_CMD_SET(VLANACCESS_CMD_WRITE),
> > +             ANA_VLANACCESS_VLAN_TBL_CMD,
> > +             lan966x, ANA_VLANACCESS);
> > +
> > +     return lan966x_vlan_wait_for_completion(lan966x);
> 
> If you're not going to propagate the return code anywhere, at least
> return void and print an error here. Otherwise it's totally silent.

Yes, I will return void and print an error.

> 
> > +}
> > +
> > +void lan966x_vlan_init(struct lan966x *lan966x)
> > +{
> > +     u16 port, vid;
> > +
> > +     /* Clear VLAN table, by default all ports are members of all VLANS */
> > +     lan_rmw(ANA_VLANACCESS_VLAN_TBL_CMD_SET(VLANACCESS_CMD_INIT),
> > +             ANA_VLANACCESS_VLAN_TBL_CMD,
> > +             lan966x, ANA_VLANACCESS);
> > +     lan966x_vlan_wait_for_completion(lan966x);
> 
> Again no error checking.
> 
> > +
> > +     for (vid = 1; vid < VLAN_N_VID; vid++) {
> > +             lan966x->vlan_mask[vid] = 0;
> > +             lan966x_vlan_set_mask(lan966x, vid);
> > +     }
> > +
> > +     /* Set all the ports + cpu to be part of HOST_PVID and UNAWARE_PVID */
> > +     lan966x->vlan_mask[HOST_PVID] =
> > +             GENMASK(lan966x->num_phys_ports - 1, 0) | BIT(CPU_PORT);
> > +     lan966x_vlan_set_mask(lan966x, HOST_PVID);
> > +
> > +     lan966x->vlan_mask[UNAWARE_PVID] =
> > +             GENMASK(lan966x->num_phys_ports - 1, 0) | BIT(CPU_PORT);
> > +     lan966x_vlan_set_mask(lan966x, UNAWARE_PVID);
> > +
> > +     lan966x_vlan_cpu_add_cpu_vlan_mask(lan966x, UNAWARE_PVID);
> > +
> > +     /* Configure the CPU port to be vlan aware */
> > +     lan_wr(ANA_VLAN_CFG_VLAN_VID_SET(0) |
> > +            ANA_VLAN_CFG_VLAN_AWARE_ENA_SET(1) |
> > +            ANA_VLAN_CFG_VLAN_POP_CNT_SET(1),
> > +            lan966x, ANA_VLAN_CFG(CPU_PORT));
> > +
> > +     /* Set vlan ingress filter mask to all ports */
> > +     lan_wr(GENMASK(lan966x->num_phys_ports, 0),
> > +            lan966x, ANA_VLANMASK);
> > +
> > +     for (port = 0; port < lan966x->num_phys_ports; port++) {
> > +             lan_wr(0, lan966x, REW_PORT_VLAN_CFG(port));
> > +             lan_wr(0, lan966x, REW_TAG_CFG(port));
> > +     }
> > +}
> > +
> > +static int lan966x_vlan_port_add_vlan_mask(struct lan966x_port *port, u16 vid)
> > +{
> > +     struct lan966x *lan966x = port->lan966x;
> > +     u8 p = port->chip_port;
> > +
> > +     lan966x->vlan_mask[vid] |= BIT(p);
> > +     return lan966x_vlan_set_mask(lan966x, vid);
> > +}
> > +
> > +static int lan966x_vlan_port_del_vlan_mask(struct lan966x_port *port, u16 vid)
> > +{
> > +     struct lan966x *lan966x = port->lan966x;
> > +     u8 p = port->chip_port;
> > +
> > +     lan966x->vlan_mask[vid] &= ~BIT(p);
> > +     return lan966x_vlan_set_mask(lan966x, vid);
> > +}
> > +
> > +static bool lan966x_vlan_port_member_vlan_mask(struct lan966x_port *port, u16 vid)
> > +{
> > +     struct lan966x *lan966x = port->lan966x;
> > +     u8 p = port->chip_port;
> > +
> > +     return lan966x->vlan_mask[vid] & BIT(p);
> > +}
> > +
> > +bool lan966x_vlan_port_any_vlan_mask(struct lan966x *lan966x, u16 vid)
> > +{
> > +     return !!(lan966x->vlan_mask[vid] & ~BIT(CPU_PORT));
> > +}
> > +
> > +static int lan966x_vlan_cpu_add_vlan_mask(struct lan966x *lan966x, u16 vid)
> > +{
> > +     lan966x->vlan_mask[vid] |= BIT(CPU_PORT);
> > +     return lan966x_vlan_set_mask(lan966x, vid);
> > +}
> > +
> > +static int lan966x_vlan_cpu_del_vlan_mask(struct lan966x *lan966x, u16 vid)
> > +{
> > +     lan966x->vlan_mask[vid] &= ~BIT(CPU_PORT);
> > +     return lan966x_vlan_set_mask(lan966x, vid);
> > +}
> > +
> > +void lan966x_vlan_cpu_add_cpu_vlan_mask(struct lan966x *lan966x, u16 vid)
> > +{
> > +     set_bit(vid, lan966x->cpu_vlan_mask);
> 
> Since these are all serialized by the rtnl_mutex, I think it's safe to
> replace with __set_bit which is non-atomic and thus cheaper.
> 
> > +}
> > +
> > +static void lan966x_vlan_cpu_del_cpu_vlan_mask(struct lan966x *lan966x, u16 vid)
> > +{
> > +     clear_bit(vid, lan966x->cpu_vlan_mask);
> > +}
> > +
> > +bool lan966x_vlan_cpu_member_cpu_vlan_mask(struct lan966x *lan966x, u16 vid)
> > +{
> > +     return test_bit(vid, lan966x->cpu_vlan_mask);
> > +}
> > +
> > +u16 lan966x_vlan_port_get_pvid(struct lan966x_port *port)
> > +{
> > +     struct lan966x *lan966x = port->lan966x;
> > +
> > +     if (!(lan966x->bridge_mask & BIT(port->chip_port)))
> > +             return HOST_PVID;
> > +
> > +     return port->vlan_aware ? port->pvid : UNAWARE_PVID;
> > +}
> > +
> > +int lan966x_vlan_port_set_vid(struct lan966x_port *port, u16 vid,
> > +                           bool pvid, bool untagged)
> 
> If you were to summarize what this function does, what would that be?

This will set the port pvid and vid based on the parameters pvid and
untagged.

> 
> > +{
> > +     struct lan966x *lan966x = port->lan966x;
> > +
> > +     /* Egress vlan classification */
> > +     if (untagged && port->vid != vid) {
> > +             if (port->vid) {
> > +                     dev_err(lan966x->dev,
> > +                             "Port already has a native VLAN: %d\n",
> > +                             port->vid);
> > +                     return -EBUSY;
> > +             }
> > +             port->vid = vid;
> > +     }
> > +
> > +     /* Default ingress vlan classification */
> > +     if (pvid)
> > +             port->pvid = vid;
> > +
> > +     return 0;
> > +}
> > +
> > +static int lan966x_vlan_port_remove_vid(struct lan966x_port *port, u16 vid)
> > +{
> > +     if (port->pvid == vid)
> > +             port->pvid = 0;
> > +
> > +     if (port->vid == vid)
> > +             port->vid = 0;
> > +
> > +     return 0;
> > +}
> > +
> > +void lan966x_vlan_port_set_vlan_aware(struct lan966x_port *port,
> > +                                   bool vlan_aware)
> > +{
> > +     port->vlan_aware = vlan_aware;
> > +}
> > +
> > +void lan966x_vlan_cpu_set_vlan_aware(struct lan966x_port *port)
> > +{
> > +     struct lan966x *lan966x = port->lan966x;
> > +
> > +     if (!port->vlan_aware) {
> > +             /* In case of vlan unaware, all the ports will be set in
> > +              * UNAWARE_PVID and have their PVID set to this PVID
> > +              * The CPU doesn't need to be added because it is always part of
> > +              * that vlan, it is required just to add entries in the MAC
> > +              * table for the front port and the CPU
> > +              */
> > +             lan966x_mac_cpu_learn(lan966x, port->dev->dev_addr, UNAWARE_PVID);
> > +             lan966x_mac_cpu_learn(lan966x, lan966x->bridge->dev_addr,
> > +                                   UNAWARE_PVID);
> > +
> > +             lan966x_vlan_port_add_vlan_mask(port, UNAWARE_PVID);
> > +             lan966x_vlan_port_apply(port);
> > +     } else {
> > +             /* In case of vlan aware, just clear what happened when changed
> > +              * to vlan unaware
> > +              */
> > +             lan966x_mac_cpu_forget(lan966x, port->dev->dev_addr, UNAWARE_PVID);
> > +             lan966x_mac_cpu_forget(lan966x, lan966x->bridge->dev_addr,
> > +                                    UNAWARE_PVID);
> > +
> > +             lan966x_vlan_port_del_vlan_mask(port, UNAWARE_PVID);
> > +             lan966x_vlan_port_apply(port);
> > +     }
> > +}
> > +
> > +void lan966x_vlan_port_apply(struct lan966x_port *port)
> > +{
> > +     struct lan966x *lan966x = port->lan966x;
> > +     u16 pvid;
> > +     u32 val;
> > +
> > +     pvid = lan966x_vlan_port_get_pvid(port);
> > +
> > +     /* Ingress clasification (ANA_PORT_VLAN_CFG) */
> > +     /* Default vlan to casify for untagged frames (may be zero) */
> 
> classify
> 
> > +     val = ANA_VLAN_CFG_VLAN_VID_SET(pvid);
> > +     if (port->vlan_aware)
> > +             val |= ANA_VLAN_CFG_VLAN_AWARE_ENA_SET(1) |
> > +                    ANA_VLAN_CFG_VLAN_POP_CNT_SET(1);
> > +
> > +     lan_rmw(val,
> > +             ANA_VLAN_CFG_VLAN_VID | ANA_VLAN_CFG_VLAN_AWARE_ENA |
> > +             ANA_VLAN_CFG_VLAN_POP_CNT,
> > +             lan966x, ANA_VLAN_CFG(port->chip_port));
> > +
> > +     /* Drop frames with multicast source address */
> > +     val = ANA_DROP_CFG_DROP_MC_SMAC_ENA_SET(1);
> > +     if (port->vlan_aware && !pvid)
> > +             /* If port is vlan-aware and tagged, drop untagged and priority
> > +              * tagged frames.
> > +              */
> > +             val |= ANA_DROP_CFG_DROP_UNTAGGED_ENA_SET(1) |
> > +                    ANA_DROP_CFG_DROP_PRIO_S_TAGGED_ENA_SET(1) |
> > +                    ANA_DROP_CFG_DROP_PRIO_C_TAGGED_ENA_SET(1);
> > +
> > +     lan_wr(val, lan966x, ANA_DROP_CFG(port->chip_port));
> > +
> > +     /* Egress configuration (REW_TAG_CFG): VLAN tag type to 8021Q */
> > +     val = REW_TAG_CFG_TAG_TPID_CFG_SET(0);
> > +     if (port->vlan_aware) {
> > +             if (port->vid)
> > +                     /* Tag all frames except when VID == DEFAULT_VLAN */
> > +                     val |= REW_TAG_CFG_TAG_CFG_SET(1);
> > +             else
> > +                     val |= REW_TAG_CFG_TAG_CFG_SET(3);
> > +     }
> > +
> > +     /* Update only some bits in the register */
> > +     lan_rmw(val,
> > +             REW_TAG_CFG_TAG_TPID_CFG | REW_TAG_CFG_TAG_CFG,
> > +             lan966x, REW_TAG_CFG(port->chip_port));
> > +
> > +     /* Set default VLAN and tag type to 8021Q */
> > +     lan_rmw(REW_PORT_VLAN_CFG_PORT_TPID_SET(ETH_P_8021Q) |
> > +             REW_PORT_VLAN_CFG_PORT_VID_SET(port->vid),
> > +             REW_PORT_VLAN_CFG_PORT_TPID |
> > +             REW_PORT_VLAN_CFG_PORT_VID,
> > +             lan966x, REW_PORT_VLAN_CFG(port->chip_port));
> > +}
> > +
> > +int lan966x_vlan_port_add_vlan(struct lan966x_port *port,
> > +                            u16 vid,
> > +                            bool pvid,
> > +                            bool untagged)
> > +{
> > +     struct lan966x *lan966x = port->lan966x;
> > +
> > +     /* If the CPU(br) is already part of the vlan then add the MAC
> > +      * address of the device in MAC table to copy the frames to the
> > +      * CPU(br). If the CPU(br) is not part of the vlan then it would
> > +      * just drop the frames.
> > +      */
> > +     if (lan966x_vlan_cpu_member_cpu_vlan_mask(lan966x, vid)) {
> > +             lan966x_mac_cpu_learn(lan966x, port->dev->dev_addr, vid);
> > +             lan966x_mac_cpu_learn(lan966x, lan966x->bridge->dev_addr, vid);
> 
> Doesn't the bridge notify you of all the addresses you need to learn on
> the CPU port?

Yes it does so I don't need these lan966x_mac_cpu_learn/forget here and
in the other places in this file.

> What is the benefit of the added complexity of only
> learning the addresses when the CPU joins the VLAN? 

If we add an entry MAC table regardless if the CPU is in that vlan, then if
there are any trunk ports then, we need to add an entry in MAC table for each
vlan. That is the reason why to add the entries in MAC table only if the
CPU is in the vlan.

> Doesn't the CPU_DIS bit work if an entry is present in the MAC table?

Yes it works.

> 
> > +             lan966x_vlan_cpu_add_vlan_mask(lan966x, vid);
> > +     }
> > +
> > +     lan966x_vlan_port_set_vid(port, vid, pvid, untagged);
> > +     lan966x_vlan_port_add_vlan_mask(port, vid);
> > +     lan966x_vlan_port_apply(port);
> > +
> > +     return 0;
> > +}
> > +
> > +int lan966x_vlan_port_del_vlan(struct lan966x_port *port,
> > +                            u16 vid)
> > +{
> > +     struct lan966x *lan966x = port->lan966x;
> > +
> > +     /* In case the CPU(br) is part of the vlan then remove the MAC entry
> > +      * because frame doesn't need to reach to CPU
> > +      */
> > +     if (lan966x_vlan_cpu_member_cpu_vlan_mask(lan966x, vid))
> > +             lan966x_mac_cpu_forget(lan966x, port->dev->dev_addr, vid);
> > +
> > +     lan966x_vlan_port_remove_vid(port, vid);
> > +     lan966x_vlan_port_del_vlan_mask(port, vid);
> > +     lan966x_vlan_port_apply(port);
> > +
> > +     /* In case there are no other ports in vlan then remove the CPU from
> > +      * that vlan but still keep it in the mask because it may be needed
> > +      * again then another port gets added in tha vlan
> 
> s/tha/that/
> 
> > +      */
> > +     if (!lan966x_vlan_port_any_vlan_mask(lan966x, vid)) {
> > +             lan966x_mac_cpu_forget(lan966x, lan966x->bridge->dev_addr, vid);
> > +             lan966x_vlan_cpu_del_vlan_mask(lan966x, vid);
> > +     }
> > +
> > +     return 0;
> > +}
> > +
> > +int lan966x_vlan_cpu_add_vlan(struct lan966x *lan966x,
> > +                           struct net_device *dev,
> > +                           u16 vid)
> > +{
> > +     int p;
> > +
> > +     /* Iterate over the ports and see which ones are part of the
> > +      * vlan and for those ports add entry in the MAC table to
> > +      * copy the frames to the CPU
> > +      */
> > +     for (p = 0; p < lan966x->num_phys_ports; p++) {
> > +             struct lan966x_port *port = lan966x->ports[p];
> > +
> > +             if (!port ||
> > +                 !lan966x_vlan_port_member_vlan_mask(port, vid))
> > +                     continue;
> > +
> > +             lan966x_mac_cpu_learn(lan966x, port->dev->dev_addr, vid);
> > +     }
> > +
> > +     /* Add an entry in the MAC table for the CPU
> > +      * Add the CPU part of the vlan only if there is another port in that
> > +      * vlan otherwise all the broadcast frames in that vlan will go to CPU
> > +      * even if none of the ports are in the vlan and then the CPU will just
> > +      * need to discard these frames. It is required to store this
> > +      * information so when a front port is added then it would add also the
> > +      * CPU port.
> > +      */
> > +     if (lan966x_vlan_port_any_vlan_mask(lan966x, vid)) {
> > +             lan966x_mac_cpu_learn(lan966x, dev->dev_addr, vid);
> > +             lan966x_vlan_cpu_add_vlan_mask(lan966x, vid);
> > +     }
> > +
> > +     lan966x_vlan_cpu_add_cpu_vlan_mask(lan966x, vid);
> > +
> > +     return 0;
> > +}
> > +
> > +int lan966x_vlan_cpu_del_vlan(struct lan966x *lan966x,
> > +                           struct net_device *dev,
> > +                           u16 vid)
> > +{
> > +     int p;
> > +
> > +     /* Iterate over the ports and see which ones are part of the
> > +      * vlan and for those ports remove entry in the MAC table to
> > +      * copy the frames to the CPU
> > +      */
> > +     for (p = 0; p < lan966x->num_phys_ports; p++) {
> > +             struct lan966x_port *port = lan966x->ports[p];
> > +
> > +             if (!port ||
> > +                 !lan966x_vlan_port_member_vlan_mask(port, vid))
> > +                     continue;
> > +
> > +             lan966x_mac_cpu_forget(lan966x, port->dev->dev_addr, vid);
> > +     }
> > +
> > +     /* Remove an entry in the MAC table for the CPU */
> > +     lan966x_mac_cpu_forget(lan966x, dev->dev_addr, vid);
> > +
> > +     /* Remove the CPU part of the vlan */
> > +     lan966x_vlan_cpu_del_cpu_vlan_mask(lan966x, vid);
> > +     lan966x_vlan_cpu_del_vlan_mask(lan966x, vid);
> > +
> > +     return 0;
> > +}
> > +
> > +int lan966x_vlan_rx_add_vid(struct net_device *dev, __be16 proto, u16 vid)
> > +{
> > +     struct lan966x_port *port = netdev_priv(dev);
> > +
> > +     lan966x_vlan_port_set_vid(port, vid, false, false);
> > +     lan966x_vlan_port_add_vlan_mask(port, vid);
> > +     lan966x_vlan_port_apply(port);
> > +
> > +     return 0;
> > +}
> > +
> > +int lan966x_vlan_rx_kill_vid(struct net_device *dev, __be16 proto,
> > +                          u16 vid)
> > +{
> > +     struct lan966x_port *port = netdev_priv(dev);
> > +
> > +     lan966x_vlan_port_remove_vid(port, vid);
> > +     lan966x_vlan_port_del_vlan_mask(port, vid);
> > +     lan966x_vlan_port_apply(port);
> > +
> > +     return 0;
> > +}
> > +
> > +void lan966x_vlan_mode(struct lan966x_port *port,
> > +                    netdev_features_t features)
> > +{
> > +     struct lan966x *lan966x = port->lan966x;
> > +     u32 val;
> > +
> > +     /* Filtering */
> > +     val = lan_rd(lan966x, ANA_VLANMASK);
> > +     if (features & NETIF_F_HW_VLAN_CTAG_FILTER)
> > +             val |= BIT(port->chip_port);
> > +     else
> > +             val &= ~BIT(port->chip_port);
> > +     lan_wr(val, lan966x, ANA_VLANMASK);
> > +}
> > --
> > 2.33.0
> >