504 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			504 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0+
 | |
| /* Microchip Sparx5 Switch driver
 | |
|  *
 | |
|  * Copyright (c) 2021 Microchip Technology Inc. and its subsidiaries.
 | |
|  */
 | |
| 
 | |
| #include <net/switchdev.h>
 | |
| #include <linux/if_bridge.h>
 | |
| #include <linux/iopoll.h>
 | |
| 
 | |
| #include "sparx5_main_regs.h"
 | |
| #include "sparx5_main.h"
 | |
| 
 | |
| /* Commands for Mac Table Command register */
 | |
| #define MAC_CMD_LEARN         0 /* Insert (Learn) 1 entry */
 | |
| #define MAC_CMD_UNLEARN       1 /* Unlearn (Forget) 1 entry */
 | |
| #define MAC_CMD_LOOKUP        2 /* Look up 1 entry */
 | |
| #define MAC_CMD_READ          3 /* Read entry at Mac Table Index */
 | |
| #define MAC_CMD_WRITE         4 /* Write entry at Mac Table Index */
 | |
| #define MAC_CMD_SCAN          5 /* Scan (Age or find next) */
 | |
| #define MAC_CMD_FIND_SMALLEST 6 /* Get next entry */
 | |
| #define MAC_CMD_CLEAR_ALL     7 /* Delete all entries in table */
 | |
| 
 | |
| /* Commands for MAC_ENTRY_ADDR_TYPE */
 | |
| #define  MAC_ENTRY_ADDR_TYPE_UPSID_PN         0
 | |
| #define  MAC_ENTRY_ADDR_TYPE_UPSID_CPU_OR_INT 1
 | |
| #define  MAC_ENTRY_ADDR_TYPE_GLAG             2
 | |
| #define  MAC_ENTRY_ADDR_TYPE_MC_IDX           3
 | |
| 
 | |
| #define TABLE_UPDATE_SLEEP_US 10
 | |
| #define TABLE_UPDATE_TIMEOUT_US 100000
 | |
| 
 | |
| struct sparx5_mact_entry {
 | |
| 	struct list_head list;
 | |
| 	unsigned char mac[ETH_ALEN];
 | |
| 	u32 flags;
 | |
| #define MAC_ENT_ALIVE	BIT(0)
 | |
| #define MAC_ENT_MOVED	BIT(1)
 | |
| #define MAC_ENT_LOCK	BIT(2)
 | |
| 	u16 vid;
 | |
| 	u16 port;
 | |
| };
 | |
| 
 | |
| static int sparx5_mact_get_status(struct sparx5 *sparx5)
 | |
| {
 | |
| 	return spx5_rd(sparx5, LRN_COMMON_ACCESS_CTRL);
 | |
| }
 | |
| 
 | |
| static int sparx5_mact_wait_for_completion(struct sparx5 *sparx5)
 | |
| {
 | |
| 	u32 val;
 | |
| 
 | |
| 	return readx_poll_timeout(sparx5_mact_get_status,
 | |
| 		sparx5, val,
 | |
| 		LRN_COMMON_ACCESS_CTRL_MAC_TABLE_ACCESS_SHOT_GET(val) == 0,
 | |
| 		TABLE_UPDATE_SLEEP_US, TABLE_UPDATE_TIMEOUT_US);
 | |
| }
 | |
| 
 | |
| static void sparx5_mact_select(struct sparx5 *sparx5,
 | |
| 			       const unsigned char mac[ETH_ALEN],
 | |
| 			       u16 vid)
 | |
| {
 | |
| 	u32 macl = 0, mach = 0;
 | |
| 
 | |
| 	/* Set the MAC address to handle and the vlan associated in a format
 | |
| 	 * understood by the hardware.
 | |
| 	 */
 | |
| 	mach |= vid    << 16;
 | |
| 	mach |= mac[0] << 8;
 | |
| 	mach |= mac[1] << 0;
 | |
| 	macl |= mac[2] << 24;
 | |
| 	macl |= mac[3] << 16;
 | |
| 	macl |= mac[4] << 8;
 | |
| 	macl |= mac[5] << 0;
 | |
| 
 | |
| 	spx5_wr(mach, sparx5, LRN_MAC_ACCESS_CFG_0);
 | |
| 	spx5_wr(macl, sparx5, LRN_MAC_ACCESS_CFG_1);
 | |
| }
 | |
| 
 | |
| int sparx5_mact_learn(struct sparx5 *sparx5, int pgid,
 | |
| 		      const unsigned char mac[ETH_ALEN], u16 vid)
 | |
| {
 | |
| 	int addr, type, ret;
 | |
| 
 | |
| 	if (pgid < SPX5_PORTS) {
 | |
| 		type = MAC_ENTRY_ADDR_TYPE_UPSID_PN;
 | |
| 		addr = pgid % 32;
 | |
| 		addr += (pgid / 32) << 5; /* Add upsid */
 | |
| 	} else {
 | |
| 		type = MAC_ENTRY_ADDR_TYPE_MC_IDX;
 | |
| 		addr = pgid - SPX5_PORTS;
 | |
| 	}
 | |
| 
 | |
| 	mutex_lock(&sparx5->lock);
 | |
| 
 | |
| 	sparx5_mact_select(sparx5, mac, vid);
 | |
| 
 | |
| 	/* MAC entry properties */
 | |
| 	spx5_wr(LRN_MAC_ACCESS_CFG_2_MAC_ENTRY_ADDR_SET(addr) |
 | |
| 		LRN_MAC_ACCESS_CFG_2_MAC_ENTRY_ADDR_TYPE_SET(type) |
 | |
| 		LRN_MAC_ACCESS_CFG_2_MAC_ENTRY_VLD_SET(1) |
 | |
| 		LRN_MAC_ACCESS_CFG_2_MAC_ENTRY_LOCKED_SET(1),
 | |
| 		sparx5, LRN_MAC_ACCESS_CFG_2);
 | |
| 	spx5_wr(0, sparx5, LRN_MAC_ACCESS_CFG_3);
 | |
| 
 | |
| 	/*  Insert/learn new entry */
 | |
| 	spx5_wr(LRN_COMMON_ACCESS_CTRL_CPU_ACCESS_CMD_SET(MAC_CMD_LEARN) |
 | |
| 		LRN_COMMON_ACCESS_CTRL_MAC_TABLE_ACCESS_SHOT_SET(1),
 | |
| 		sparx5, LRN_COMMON_ACCESS_CTRL);
 | |
| 
 | |
| 	ret = sparx5_mact_wait_for_completion(sparx5);
 | |
| 
 | |
| 	mutex_unlock(&sparx5->lock);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| int sparx5_mc_unsync(struct net_device *dev, const unsigned char *addr)
 | |
| {
 | |
| 	struct sparx5_port *port = netdev_priv(dev);
 | |
| 	struct sparx5 *sparx5 = port->sparx5;
 | |
| 
 | |
| 	return sparx5_mact_forget(sparx5, addr, port->pvid);
 | |
| }
 | |
| 
 | |
| int sparx5_mc_sync(struct net_device *dev, const unsigned char *addr)
 | |
| {
 | |
| 	struct sparx5_port *port = netdev_priv(dev);
 | |
| 	struct sparx5 *sparx5 = port->sparx5;
 | |
| 
 | |
| 	return sparx5_mact_learn(sparx5, PGID_CPU, addr, port->pvid);
 | |
| }
 | |
| 
 | |
| static int sparx5_mact_get(struct sparx5 *sparx5,
 | |
| 			   unsigned char mac[ETH_ALEN],
 | |
| 			   u16 *vid, u32 *pcfg2)
 | |
| {
 | |
| 	u32 mach, macl, cfg2;
 | |
| 	int ret = -ENOENT;
 | |
| 
 | |
| 	cfg2 = spx5_rd(sparx5, LRN_MAC_ACCESS_CFG_2);
 | |
| 	if (LRN_MAC_ACCESS_CFG_2_MAC_ENTRY_VLD_GET(cfg2)) {
 | |
| 		mach = spx5_rd(sparx5, LRN_MAC_ACCESS_CFG_0);
 | |
| 		macl = spx5_rd(sparx5, LRN_MAC_ACCESS_CFG_1);
 | |
| 		mac[0] = ((mach >> 8)  & 0xff);
 | |
| 		mac[1] = ((mach >> 0)  & 0xff);
 | |
| 		mac[2] = ((macl >> 24) & 0xff);
 | |
| 		mac[3] = ((macl >> 16) & 0xff);
 | |
| 		mac[4] = ((macl >> 8)  & 0xff);
 | |
| 		mac[5] = ((macl >> 0)  & 0xff);
 | |
| 		*vid = mach >> 16;
 | |
| 		*pcfg2 = cfg2;
 | |
| 		ret = 0;
 | |
| 	}
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| bool sparx5_mact_getnext(struct sparx5 *sparx5,
 | |
| 			 unsigned char mac[ETH_ALEN], u16 *vid, u32 *pcfg2)
 | |
| {
 | |
| 	u32 cfg2;
 | |
| 	int ret;
 | |
| 
 | |
| 	mutex_lock(&sparx5->lock);
 | |
| 
 | |
| 	sparx5_mact_select(sparx5, mac, *vid);
 | |
| 
 | |
| 	spx5_wr(LRN_SCAN_NEXT_CFG_SCAN_NEXT_IGNORE_LOCKED_ENA_SET(1) |
 | |
| 		LRN_SCAN_NEXT_CFG_SCAN_NEXT_UNTIL_FOUND_ENA_SET(1),
 | |
| 		sparx5, LRN_SCAN_NEXT_CFG);
 | |
| 	spx5_wr(LRN_COMMON_ACCESS_CTRL_CPU_ACCESS_CMD_SET
 | |
| 		(MAC_CMD_FIND_SMALLEST) |
 | |
| 		LRN_COMMON_ACCESS_CTRL_MAC_TABLE_ACCESS_SHOT_SET(1),
 | |
| 		sparx5, LRN_COMMON_ACCESS_CTRL);
 | |
| 
 | |
| 	ret = sparx5_mact_wait_for_completion(sparx5);
 | |
| 	if (ret == 0) {
 | |
| 		ret = sparx5_mact_get(sparx5, mac, vid, &cfg2);
 | |
| 		if (ret == 0)
 | |
| 			*pcfg2 = cfg2;
 | |
| 	}
 | |
| 
 | |
| 	mutex_unlock(&sparx5->lock);
 | |
| 
 | |
| 	return ret == 0;
 | |
| }
 | |
| 
 | |
| int sparx5_mact_find(struct sparx5 *sparx5,
 | |
| 		     const unsigned char mac[ETH_ALEN], u16 vid, u32 *pcfg2)
 | |
| {
 | |
| 	int ret;
 | |
| 	u32 cfg2;
 | |
| 
 | |
| 	mutex_lock(&sparx5->lock);
 | |
| 
 | |
| 	sparx5_mact_select(sparx5, mac, vid);
 | |
| 
 | |
| 	/* Issue a lookup command */
 | |
| 	spx5_wr(LRN_COMMON_ACCESS_CTRL_CPU_ACCESS_CMD_SET(MAC_CMD_LOOKUP) |
 | |
| 		LRN_COMMON_ACCESS_CTRL_MAC_TABLE_ACCESS_SHOT_SET(1),
 | |
| 		sparx5, LRN_COMMON_ACCESS_CTRL);
 | |
| 
 | |
| 	ret = sparx5_mact_wait_for_completion(sparx5);
 | |
| 	if (ret == 0) {
 | |
| 		cfg2 = spx5_rd(sparx5, LRN_MAC_ACCESS_CFG_2);
 | |
| 		if (LRN_MAC_ACCESS_CFG_2_MAC_ENTRY_VLD_GET(cfg2))
 | |
| 			*pcfg2 = cfg2;
 | |
| 		else
 | |
| 			ret = -ENOENT;
 | |
| 	}
 | |
| 
 | |
| 	mutex_unlock(&sparx5->lock);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| int sparx5_mact_forget(struct sparx5 *sparx5,
 | |
| 		       const unsigned char mac[ETH_ALEN], u16 vid)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	mutex_lock(&sparx5->lock);
 | |
| 
 | |
| 	sparx5_mact_select(sparx5, mac, vid);
 | |
| 
 | |
| 	/* Issue an unlearn command */
 | |
| 	spx5_wr(LRN_COMMON_ACCESS_CTRL_CPU_ACCESS_CMD_SET(MAC_CMD_UNLEARN) |
 | |
| 		LRN_COMMON_ACCESS_CTRL_MAC_TABLE_ACCESS_SHOT_SET(1),
 | |
| 		sparx5, LRN_COMMON_ACCESS_CTRL);
 | |
| 
 | |
| 	ret = sparx5_mact_wait_for_completion(sparx5);
 | |
| 
 | |
| 	mutex_unlock(&sparx5->lock);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static struct sparx5_mact_entry *alloc_mact_entry(struct sparx5 *sparx5,
 | |
| 						  const unsigned char *mac,
 | |
| 						  u16 vid, u16 port_index)
 | |
| {
 | |
| 	struct sparx5_mact_entry *mact_entry;
 | |
| 
 | |
| 	mact_entry = devm_kzalloc(sparx5->dev,
 | |
| 				  sizeof(*mact_entry), GFP_ATOMIC);
 | |
| 	if (!mact_entry)
 | |
| 		return NULL;
 | |
| 
 | |
| 	memcpy(mact_entry->mac, mac, ETH_ALEN);
 | |
| 	mact_entry->vid = vid;
 | |
| 	mact_entry->port = port_index;
 | |
| 	return mact_entry;
 | |
| }
 | |
| 
 | |
| static struct sparx5_mact_entry *find_mact_entry(struct sparx5 *sparx5,
 | |
| 						 const unsigned char *mac,
 | |
| 						 u16 vid, u16 port_index)
 | |
| {
 | |
| 	struct sparx5_mact_entry *mact_entry;
 | |
| 	struct sparx5_mact_entry *res = NULL;
 | |
| 
 | |
| 	mutex_lock(&sparx5->mact_lock);
 | |
| 	list_for_each_entry(mact_entry, &sparx5->mact_entries, list) {
 | |
| 		if (mact_entry->vid == vid &&
 | |
| 		    ether_addr_equal(mac, mact_entry->mac) &&
 | |
| 		    mact_entry->port == port_index) {
 | |
| 			res = mact_entry;
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 	mutex_unlock(&sparx5->mact_lock);
 | |
| 
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| static void sparx5_fdb_call_notifiers(enum switchdev_notifier_type type,
 | |
| 				      const char *mac, u16 vid,
 | |
| 				      struct net_device *dev, bool offloaded)
 | |
| {
 | |
| 	struct switchdev_notifier_fdb_info info = {};
 | |
| 
 | |
| 	info.addr = mac;
 | |
| 	info.vid = vid;
 | |
| 	info.offloaded = offloaded;
 | |
| 	call_switchdev_notifiers(type, dev, &info.info, NULL);
 | |
| }
 | |
| 
 | |
| int sparx5_add_mact_entry(struct sparx5 *sparx5,
 | |
| 			  struct net_device *dev,
 | |
| 			  u16 portno,
 | |
| 			  const unsigned char *addr, u16 vid)
 | |
| {
 | |
| 	struct sparx5_mact_entry *mact_entry;
 | |
| 	int ret;
 | |
| 	u32 cfg2;
 | |
| 
 | |
| 	ret = sparx5_mact_find(sparx5, addr, vid, &cfg2);
 | |
| 	if (!ret)
 | |
| 		return 0;
 | |
| 
 | |
| 	/* In case the entry already exists, don't add it again to SW,
 | |
| 	 * just update HW, but we need to look in the actual HW because
 | |
| 	 * it is possible for an entry to be learn by HW and before the
 | |
| 	 * mact thread to start the frame will reach CPU and the CPU will
 | |
| 	 * add the entry but without the extern_learn flag.
 | |
| 	 */
 | |
| 	mact_entry = find_mact_entry(sparx5, addr, vid, portno);
 | |
| 	if (mact_entry)
 | |
| 		goto update_hw;
 | |
| 
 | |
| 	/* Add the entry in SW MAC table not to get the notification when
 | |
| 	 * SW is pulling again
 | |
| 	 */
 | |
| 	mact_entry = alloc_mact_entry(sparx5, addr, vid, portno);
 | |
| 	if (!mact_entry)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	mutex_lock(&sparx5->mact_lock);
 | |
| 	list_add_tail(&mact_entry->list, &sparx5->mact_entries);
 | |
| 	mutex_unlock(&sparx5->mact_lock);
 | |
| 
 | |
| update_hw:
 | |
| 	ret = sparx5_mact_learn(sparx5, portno, addr, vid);
 | |
| 
 | |
| 	/* New entry? */
 | |
| 	if (mact_entry->flags == 0) {
 | |
| 		mact_entry->flags |= MAC_ENT_LOCK; /* Don't age this */
 | |
| 		sparx5_fdb_call_notifiers(SWITCHDEV_FDB_ADD_TO_BRIDGE, addr, vid,
 | |
| 					  dev, true);
 | |
| 	}
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| int sparx5_del_mact_entry(struct sparx5 *sparx5,
 | |
| 			  const unsigned char *addr,
 | |
| 			  u16 vid)
 | |
| {
 | |
| 	struct sparx5_mact_entry *mact_entry, *tmp;
 | |
| 
 | |
| 	/* Delete the entry in SW MAC table not to get the notification when
 | |
| 	 * SW is pulling again
 | |
| 	 */
 | |
| 	mutex_lock(&sparx5->mact_lock);
 | |
| 	list_for_each_entry_safe(mact_entry, tmp, &sparx5->mact_entries,
 | |
| 				 list) {
 | |
| 		if ((vid == 0 || mact_entry->vid == vid) &&
 | |
| 		    ether_addr_equal(addr, mact_entry->mac)) {
 | |
| 			sparx5_mact_forget(sparx5, addr, mact_entry->vid);
 | |
| 
 | |
| 			list_del(&mact_entry->list);
 | |
| 			devm_kfree(sparx5->dev, mact_entry);
 | |
| 		}
 | |
| 	}
 | |
| 	mutex_unlock(&sparx5->mact_lock);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static void sparx5_mact_handle_entry(struct sparx5 *sparx5,
 | |
| 				     unsigned char mac[ETH_ALEN],
 | |
| 				     u16 vid, u32 cfg2)
 | |
| {
 | |
| 	struct sparx5_mact_entry *mact_entry;
 | |
| 	bool found = false;
 | |
| 	u16 port;
 | |
| 
 | |
| 	if (LRN_MAC_ACCESS_CFG_2_MAC_ENTRY_ADDR_TYPE_GET(cfg2) !=
 | |
| 	    MAC_ENTRY_ADDR_TYPE_UPSID_PN)
 | |
| 		return;
 | |
| 
 | |
| 	port = LRN_MAC_ACCESS_CFG_2_MAC_ENTRY_ADDR_GET(cfg2);
 | |
| 	if (port >= SPX5_PORTS)
 | |
| 		return;
 | |
| 
 | |
| 	if (!test_bit(port, sparx5->bridge_mask))
 | |
| 		return;
 | |
| 
 | |
| 	mutex_lock(&sparx5->mact_lock);
 | |
| 	list_for_each_entry(mact_entry, &sparx5->mact_entries, list) {
 | |
| 		if (mact_entry->vid == vid &&
 | |
| 		    ether_addr_equal(mac, mact_entry->mac)) {
 | |
| 			found = true;
 | |
| 			mact_entry->flags |= MAC_ENT_ALIVE;
 | |
| 			if (mact_entry->port != port) {
 | |
| 				dev_warn(sparx5->dev, "Entry move: %d -> %d\n",
 | |
| 					 mact_entry->port, port);
 | |
| 				mact_entry->port = port;
 | |
| 				mact_entry->flags |= MAC_ENT_MOVED;
 | |
| 			}
 | |
| 			/* Entry handled */
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 	mutex_unlock(&sparx5->mact_lock);
 | |
| 
 | |
| 	if (found && !(mact_entry->flags & MAC_ENT_MOVED))
 | |
| 		/* Present, not moved */
 | |
| 		return;
 | |
| 
 | |
| 	if (!found) {
 | |
| 		/* Entry not found - now add */
 | |
| 		mact_entry = alloc_mact_entry(sparx5, mac, vid, port);
 | |
| 		if (!mact_entry)
 | |
| 			return;
 | |
| 
 | |
| 		mact_entry->flags |= MAC_ENT_ALIVE;
 | |
| 		mutex_lock(&sparx5->mact_lock);
 | |
| 		list_add_tail(&mact_entry->list, &sparx5->mact_entries);
 | |
| 		mutex_unlock(&sparx5->mact_lock);
 | |
| 	}
 | |
| 
 | |
| 	/* New or moved entry - notify bridge */
 | |
| 	sparx5_fdb_call_notifiers(SWITCHDEV_FDB_ADD_TO_BRIDGE,
 | |
| 				  mac, vid, sparx5->ports[port]->ndev,
 | |
| 				  true);
 | |
| }
 | |
| 
 | |
| void sparx5_mact_pull_work(struct work_struct *work)
 | |
| {
 | |
| 	struct delayed_work *del_work = to_delayed_work(work);
 | |
| 	struct sparx5 *sparx5 = container_of(del_work, struct sparx5,
 | |
| 					     mact_work);
 | |
| 	struct sparx5_mact_entry *mact_entry, *tmp;
 | |
| 	unsigned char mac[ETH_ALEN];
 | |
| 	u32 cfg2;
 | |
| 	u16 vid;
 | |
| 	int ret;
 | |
| 
 | |
| 	/* Reset MAC entry flags */
 | |
| 	mutex_lock(&sparx5->mact_lock);
 | |
| 	list_for_each_entry(mact_entry, &sparx5->mact_entries, list)
 | |
| 		mact_entry->flags &= MAC_ENT_LOCK;
 | |
| 	mutex_unlock(&sparx5->mact_lock);
 | |
| 
 | |
| 	/* MAIN mac address processing loop */
 | |
| 	vid = 0;
 | |
| 	memset(mac, 0, sizeof(mac));
 | |
| 	do {
 | |
| 		mutex_lock(&sparx5->lock);
 | |
| 		sparx5_mact_select(sparx5, mac, vid);
 | |
| 		spx5_wr(LRN_SCAN_NEXT_CFG_SCAN_NEXT_UNTIL_FOUND_ENA_SET(1),
 | |
| 			sparx5, LRN_SCAN_NEXT_CFG);
 | |
| 		spx5_wr(LRN_COMMON_ACCESS_CTRL_CPU_ACCESS_CMD_SET
 | |
| 			(MAC_CMD_FIND_SMALLEST) |
 | |
| 			LRN_COMMON_ACCESS_CTRL_MAC_TABLE_ACCESS_SHOT_SET(1),
 | |
| 			sparx5, LRN_COMMON_ACCESS_CTRL);
 | |
| 		ret = sparx5_mact_wait_for_completion(sparx5);
 | |
| 		if (ret == 0)
 | |
| 			ret = sparx5_mact_get(sparx5, mac, &vid, &cfg2);
 | |
| 		mutex_unlock(&sparx5->lock);
 | |
| 		if (ret == 0)
 | |
| 			sparx5_mact_handle_entry(sparx5, mac, vid, cfg2);
 | |
| 	} while (ret == 0);
 | |
| 
 | |
| 	mutex_lock(&sparx5->mact_lock);
 | |
| 	list_for_each_entry_safe(mact_entry, tmp, &sparx5->mact_entries,
 | |
| 				 list) {
 | |
| 		/* If the entry is in HW or permanent, then skip */
 | |
| 		if (mact_entry->flags & (MAC_ENT_ALIVE | MAC_ENT_LOCK))
 | |
| 			continue;
 | |
| 
 | |
| 		sparx5_fdb_call_notifiers(SWITCHDEV_FDB_DEL_TO_BRIDGE,
 | |
| 					  mact_entry->mac, mact_entry->vid,
 | |
| 					  sparx5->ports[mact_entry->port]->ndev,
 | |
| 					  true);
 | |
| 
 | |
| 		list_del(&mact_entry->list);
 | |
| 		devm_kfree(sparx5->dev, mact_entry);
 | |
| 	}
 | |
| 	mutex_unlock(&sparx5->mact_lock);
 | |
| 
 | |
| 	queue_delayed_work(sparx5->mact_queue, &sparx5->mact_work,
 | |
| 			   SPX5_MACT_PULL_DELAY);
 | |
| }
 | |
| 
 | |
| void sparx5_set_ageing(struct sparx5 *sparx5, int msecs)
 | |
| {
 | |
| 	int value = max(1, msecs / 10); /* unit 10 ms */
 | |
| 
 | |
| 	spx5_rmw(LRN_AUTOAGE_CFG_UNIT_SIZE_SET(2) | /* 10 ms */
 | |
| 		 LRN_AUTOAGE_CFG_PERIOD_VAL_SET(value / 2), /* one bit ageing */
 | |
| 		 LRN_AUTOAGE_CFG_UNIT_SIZE |
 | |
| 		 LRN_AUTOAGE_CFG_PERIOD_VAL,
 | |
| 		 sparx5,
 | |
| 		 LRN_AUTOAGE_CFG(0));
 | |
| }
 | |
| 
 | |
| void sparx5_mact_init(struct sparx5 *sparx5)
 | |
| {
 | |
| 	mutex_init(&sparx5->lock);
 | |
| 
 | |
| 	/*  Flush MAC table */
 | |
| 	spx5_wr(LRN_COMMON_ACCESS_CTRL_CPU_ACCESS_CMD_SET(MAC_CMD_CLEAR_ALL) |
 | |
| 		LRN_COMMON_ACCESS_CTRL_MAC_TABLE_ACCESS_SHOT_SET(1),
 | |
| 		sparx5, LRN_COMMON_ACCESS_CTRL);
 | |
| 
 | |
| 	if (sparx5_mact_wait_for_completion(sparx5) != 0)
 | |
| 		dev_warn(sparx5->dev, "MAC flush error\n");
 | |
| 
 | |
| 	sparx5_set_ageing(sparx5, BR_DEFAULT_AGEING_TIME / HZ * 1000);
 | |
| }
 |