1
0
Fork 0
mirror of synced 2025-03-06 20:59:54 +01:00
linux/drivers/net/ethernet/microchip/sparx5/sparx5_mactable.c
Vladimir Oltean c35b57ceff net: switchdev: zero-initialize struct switchdev_notifier_fdb_info emitted by drivers towards the bridge
The blamed commit added a new field to struct switchdev_notifier_fdb_info,
but did not make sure that all call paths set it to something valid.
For example, a switchdev driver may emit a SWITCHDEV_FDB_ADD_TO_BRIDGE
notifier, and since the 'is_local' flag is not set, it contains junk
from the stack, so the bridge might interpret those notifications as
being for local FDB entries when that was not intended.

To avoid that now and in the future, zero-initialize all
switchdev_notifier_fdb_info structures created by drivers such that all
newly added fields to not need to touch drivers again.

Fixes: 2c4eca3ef7 ("net: bridge: switchdev: include local flag in FDB notifications")
Reported-by: Ido Schimmel <idosch@idosch.org>
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Reviewed-by: Ido Schimmel <idosch@nvidia.com>
Tested-by: Ido Schimmel <idosch@nvidia.com>
Reviewed-by: Leon Romanovsky <leonro@nvidia.com>
Reviewed-by: Karsten Graul <kgraul@linux.ibm.com>
Link: https://lore.kernel.org/r/20210810115024.1629983-1-vladimir.oltean@nxp.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2021-08-10 13:22:57 -07:00

500 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;
}
static int sparx5_mact_lookup(struct sparx5 *sparx5,
const unsigned char mac[ETH_ALEN],
u16 vid)
{
int ret;
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)
goto out;
ret = LRN_MAC_ACCESS_CFG_2_MAC_ENTRY_VLD_GET
(spx5_rd(sparx5, LRN_MAC_ACCESS_CFG_2));
out:
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 sparx5_port *port,
const unsigned char *addr, u16 vid)
{
struct sparx5_mact_entry *mact_entry;
int ret;
ret = sparx5_mact_lookup(sparx5, addr, vid);
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, port->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, port->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, port->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,
port->ndev, 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)) {
list_del(&mact_entry->list);
devm_kfree(sparx5->dev, mact_entry);
sparx5_mact_forget(sparx5, addr, mact_entry->vid);
}
}
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);
}