Merge branch 'net-lan966x-fix-issues-with-mac-table'
Horatiu Vultur says: ==================== net: lan966x: Fix issues with MAC table The patch series fixes 2 issues: - when an entry was forgotten the irq thread was holding a spin lock and then was talking also rtnl_lock. - the access to the HW MAC table is indirect, so the access to the HW MAC table was not synchronized, which means that there could be race conditions. ==================== Link: https://lore.kernel.org/r/20220714194040.231651-1-horatiu.vultur@microchip.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
commit
b6224a36de
@ -75,6 +75,9 @@ static int __lan966x_mac_learn(struct lan966x *lan966x, int pgid,
|
|||||||
unsigned int vid,
|
unsigned int vid,
|
||||||
enum macaccess_entry_type type)
|
enum macaccess_entry_type type)
|
||||||
{
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
spin_lock(&lan966x->mac_lock);
|
||||||
lan966x_mac_select(lan966x, mac, vid);
|
lan966x_mac_select(lan966x, mac, vid);
|
||||||
|
|
||||||
/* Issue a write command */
|
/* Issue a write command */
|
||||||
@ -86,7 +89,10 @@ static int __lan966x_mac_learn(struct lan966x *lan966x, int pgid,
|
|||||||
ANA_MACACCESS_MAC_TABLE_CMD_SET(MACACCESS_CMD_LEARN),
|
ANA_MACACCESS_MAC_TABLE_CMD_SET(MACACCESS_CMD_LEARN),
|
||||||
lan966x, ANA_MACACCESS);
|
lan966x, ANA_MACACCESS);
|
||||||
|
|
||||||
return lan966x_mac_wait_for_completion(lan966x);
|
ret = lan966x_mac_wait_for_completion(lan966x);
|
||||||
|
spin_unlock(&lan966x->mac_lock);
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The mask of the front ports is encoded inside the mac parameter via a call
|
/* The mask of the front ports is encoded inside the mac parameter via a call
|
||||||
@ -113,11 +119,13 @@ int lan966x_mac_learn(struct lan966x *lan966x, int port,
|
|||||||
return __lan966x_mac_learn(lan966x, port, false, mac, vid, type);
|
return __lan966x_mac_learn(lan966x, port, false, mac, vid, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
int lan966x_mac_forget(struct lan966x *lan966x,
|
static int lan966x_mac_forget_locked(struct lan966x *lan966x,
|
||||||
const unsigned char mac[ETH_ALEN],
|
const unsigned char mac[ETH_ALEN],
|
||||||
unsigned int vid,
|
unsigned int vid,
|
||||||
enum macaccess_entry_type type)
|
enum macaccess_entry_type type)
|
||||||
{
|
{
|
||||||
|
lockdep_assert_held(&lan966x->mac_lock);
|
||||||
|
|
||||||
lan966x_mac_select(lan966x, mac, vid);
|
lan966x_mac_select(lan966x, mac, vid);
|
||||||
|
|
||||||
/* Issue a forget command */
|
/* Issue a forget command */
|
||||||
@ -128,6 +136,20 @@ int lan966x_mac_forget(struct lan966x *lan966x,
|
|||||||
return lan966x_mac_wait_for_completion(lan966x);
|
return lan966x_mac_wait_for_completion(lan966x);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int lan966x_mac_forget(struct lan966x *lan966x,
|
||||||
|
const unsigned char mac[ETH_ALEN],
|
||||||
|
unsigned int vid,
|
||||||
|
enum macaccess_entry_type type)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
spin_lock(&lan966x->mac_lock);
|
||||||
|
ret = lan966x_mac_forget_locked(lan966x, mac, vid, type);
|
||||||
|
spin_unlock(&lan966x->mac_lock);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
int lan966x_mac_cpu_learn(struct lan966x *lan966x, const char *addr, u16 vid)
|
int lan966x_mac_cpu_learn(struct lan966x *lan966x, const char *addr, u16 vid)
|
||||||
{
|
{
|
||||||
return lan966x_mac_learn(lan966x, PGID_CPU, addr, vid, ENTRYTYPE_LOCKED);
|
return lan966x_mac_learn(lan966x, PGID_CPU, addr, vid, ENTRYTYPE_LOCKED);
|
||||||
@ -161,7 +183,7 @@ static struct lan966x_mac_entry *lan966x_mac_alloc_entry(const unsigned char *ma
|
|||||||
{
|
{
|
||||||
struct lan966x_mac_entry *mac_entry;
|
struct lan966x_mac_entry *mac_entry;
|
||||||
|
|
||||||
mac_entry = kzalloc(sizeof(*mac_entry), GFP_KERNEL);
|
mac_entry = kzalloc(sizeof(*mac_entry), GFP_ATOMIC);
|
||||||
if (!mac_entry)
|
if (!mac_entry)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
@ -179,7 +201,6 @@ static struct lan966x_mac_entry *lan966x_mac_find_entry(struct lan966x *lan966x,
|
|||||||
struct lan966x_mac_entry *res = NULL;
|
struct lan966x_mac_entry *res = NULL;
|
||||||
struct lan966x_mac_entry *mac_entry;
|
struct lan966x_mac_entry *mac_entry;
|
||||||
|
|
||||||
spin_lock(&lan966x->mac_lock);
|
|
||||||
list_for_each_entry(mac_entry, &lan966x->mac_entries, list) {
|
list_for_each_entry(mac_entry, &lan966x->mac_entries, list) {
|
||||||
if (mac_entry->vid == vid &&
|
if (mac_entry->vid == vid &&
|
||||||
ether_addr_equal(mac, mac_entry->mac) &&
|
ether_addr_equal(mac, mac_entry->mac) &&
|
||||||
@ -188,7 +209,6 @@ static struct lan966x_mac_entry *lan966x_mac_find_entry(struct lan966x *lan966x,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
spin_unlock(&lan966x->mac_lock);
|
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@ -231,8 +251,11 @@ int lan966x_mac_add_entry(struct lan966x *lan966x, struct lan966x_port *port,
|
|||||||
{
|
{
|
||||||
struct lan966x_mac_entry *mac_entry;
|
struct lan966x_mac_entry *mac_entry;
|
||||||
|
|
||||||
if (lan966x_mac_lookup(lan966x, addr, vid, ENTRYTYPE_NORMAL))
|
spin_lock(&lan966x->mac_lock);
|
||||||
|
if (lan966x_mac_lookup(lan966x, addr, vid, ENTRYTYPE_NORMAL)) {
|
||||||
|
spin_unlock(&lan966x->mac_lock);
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* In case the entry already exists, don't add it again to SW,
|
/* 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
|
* just update HW, but we need to look in the actual HW because
|
||||||
@ -241,21 +264,25 @@ int lan966x_mac_add_entry(struct lan966x *lan966x, struct lan966x_port *port,
|
|||||||
* add the entry but without the extern_learn flag.
|
* add the entry but without the extern_learn flag.
|
||||||
*/
|
*/
|
||||||
mac_entry = lan966x_mac_find_entry(lan966x, addr, vid, port->chip_port);
|
mac_entry = lan966x_mac_find_entry(lan966x, addr, vid, port->chip_port);
|
||||||
if (mac_entry)
|
if (mac_entry) {
|
||||||
return lan966x_mac_learn(lan966x, port->chip_port,
|
spin_unlock(&lan966x->mac_lock);
|
||||||
addr, vid, ENTRYTYPE_LOCKED);
|
goto mac_learn;
|
||||||
|
}
|
||||||
|
|
||||||
mac_entry = lan966x_mac_alloc_entry(addr, vid, port->chip_port);
|
mac_entry = lan966x_mac_alloc_entry(addr, vid, port->chip_port);
|
||||||
if (!mac_entry)
|
if (!mac_entry) {
|
||||||
|
spin_unlock(&lan966x->mac_lock);
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
spin_lock(&lan966x->mac_lock);
|
|
||||||
list_add_tail(&mac_entry->list, &lan966x->mac_entries);
|
list_add_tail(&mac_entry->list, &lan966x->mac_entries);
|
||||||
spin_unlock(&lan966x->mac_lock);
|
spin_unlock(&lan966x->mac_lock);
|
||||||
|
|
||||||
lan966x_mac_learn(lan966x, port->chip_port, addr, vid, ENTRYTYPE_LOCKED);
|
|
||||||
lan966x_fdb_call_notifiers(SWITCHDEV_FDB_OFFLOADED, addr, vid, port->dev);
|
lan966x_fdb_call_notifiers(SWITCHDEV_FDB_OFFLOADED, addr, vid, port->dev);
|
||||||
|
|
||||||
|
mac_learn:
|
||||||
|
lan966x_mac_learn(lan966x, port->chip_port, addr, vid, ENTRYTYPE_LOCKED);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,7 +296,8 @@ int lan966x_mac_del_entry(struct lan966x *lan966x, const unsigned char *addr,
|
|||||||
list) {
|
list) {
|
||||||
if (mac_entry->vid == vid &&
|
if (mac_entry->vid == vid &&
|
||||||
ether_addr_equal(addr, mac_entry->mac)) {
|
ether_addr_equal(addr, mac_entry->mac)) {
|
||||||
lan966x_mac_forget(lan966x, mac_entry->mac, mac_entry->vid,
|
lan966x_mac_forget_locked(lan966x, mac_entry->mac,
|
||||||
|
mac_entry->vid,
|
||||||
ENTRYTYPE_LOCKED);
|
ENTRYTYPE_LOCKED);
|
||||||
|
|
||||||
list_del(&mac_entry->list);
|
list_del(&mac_entry->list);
|
||||||
@ -288,8 +316,8 @@ void lan966x_mac_purge_entries(struct lan966x *lan966x)
|
|||||||
spin_lock(&lan966x->mac_lock);
|
spin_lock(&lan966x->mac_lock);
|
||||||
list_for_each_entry_safe(mac_entry, tmp, &lan966x->mac_entries,
|
list_for_each_entry_safe(mac_entry, tmp, &lan966x->mac_entries,
|
||||||
list) {
|
list) {
|
||||||
lan966x_mac_forget(lan966x, mac_entry->mac, mac_entry->vid,
|
lan966x_mac_forget_locked(lan966x, mac_entry->mac,
|
||||||
ENTRYTYPE_LOCKED);
|
mac_entry->vid, ENTRYTYPE_LOCKED);
|
||||||
|
|
||||||
list_del(&mac_entry->list);
|
list_del(&mac_entry->list);
|
||||||
kfree(mac_entry);
|
kfree(mac_entry);
|
||||||
@ -325,10 +353,13 @@ static void lan966x_mac_irq_process(struct lan966x *lan966x, u32 row,
|
|||||||
{
|
{
|
||||||
struct lan966x_mac_entry *mac_entry, *tmp;
|
struct lan966x_mac_entry *mac_entry, *tmp;
|
||||||
unsigned char mac[ETH_ALEN] __aligned(2);
|
unsigned char mac[ETH_ALEN] __aligned(2);
|
||||||
|
struct list_head mac_deleted_entries;
|
||||||
u32 dest_idx;
|
u32 dest_idx;
|
||||||
u32 column;
|
u32 column;
|
||||||
u16 vid;
|
u16 vid;
|
||||||
|
|
||||||
|
INIT_LIST_HEAD(&mac_deleted_entries);
|
||||||
|
|
||||||
spin_lock(&lan966x->mac_lock);
|
spin_lock(&lan966x->mac_lock);
|
||||||
list_for_each_entry_safe(mac_entry, tmp, &lan966x->mac_entries, list) {
|
list_for_each_entry_safe(mac_entry, tmp, &lan966x->mac_entries, list) {
|
||||||
bool found = false;
|
bool found = false;
|
||||||
@ -362,19 +393,25 @@ static void lan966x_mac_irq_process(struct lan966x *lan966x, u32 row,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!found) {
|
if (!found) {
|
||||||
|
list_del(&mac_entry->list);
|
||||||
|
/* Move the entry from SW list to a tmp list such that
|
||||||
|
* it would be deleted later
|
||||||
|
*/
|
||||||
|
list_add_tail(&mac_entry->list, &mac_deleted_entries);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spin_unlock(&lan966x->mac_lock);
|
||||||
|
|
||||||
|
list_for_each_entry_safe(mac_entry, tmp, &mac_deleted_entries, list) {
|
||||||
/* Notify the bridge that the entry doesn't exist
|
/* Notify the bridge that the entry doesn't exist
|
||||||
* anymore in the HW and remove the entry from the SW
|
* anymore in the HW
|
||||||
* list
|
|
||||||
*/
|
*/
|
||||||
lan966x_mac_notifiers(SWITCHDEV_FDB_DEL_TO_BRIDGE,
|
lan966x_mac_notifiers(SWITCHDEV_FDB_DEL_TO_BRIDGE,
|
||||||
mac_entry->mac, mac_entry->vid,
|
mac_entry->mac, mac_entry->vid,
|
||||||
lan966x->ports[mac_entry->port_index]->dev);
|
lan966x->ports[mac_entry->port_index]->dev);
|
||||||
|
|
||||||
list_del(&mac_entry->list);
|
list_del(&mac_entry->list);
|
||||||
kfree(mac_entry);
|
kfree(mac_entry);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
spin_unlock(&lan966x->mac_lock);
|
|
||||||
|
|
||||||
/* Now go to the list of columns and see if any entry was not in the SW
|
/* Now go to the list of columns and see if any entry was not in the SW
|
||||||
* list, then that means that the entry is new so it needs to notify the
|
* list, then that means that the entry is new so it needs to notify the
|
||||||
@ -396,13 +433,20 @@ static void lan966x_mac_irq_process(struct lan966x *lan966x, u32 row,
|
|||||||
if (WARN_ON(dest_idx >= lan966x->num_phys_ports))
|
if (WARN_ON(dest_idx >= lan966x->num_phys_ports))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
spin_lock(&lan966x->mac_lock);
|
||||||
|
mac_entry = lan966x_mac_find_entry(lan966x, mac, vid, dest_idx);
|
||||||
|
if (mac_entry) {
|
||||||
|
spin_unlock(&lan966x->mac_lock);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
mac_entry = lan966x_mac_alloc_entry(mac, vid, dest_idx);
|
mac_entry = lan966x_mac_alloc_entry(mac, vid, dest_idx);
|
||||||
if (!mac_entry)
|
if (!mac_entry) {
|
||||||
|
spin_unlock(&lan966x->mac_lock);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
mac_entry->row = row;
|
mac_entry->row = row;
|
||||||
|
|
||||||
spin_lock(&lan966x->mac_lock);
|
|
||||||
list_add_tail(&mac_entry->list, &lan966x->mac_entries);
|
list_add_tail(&mac_entry->list, &lan966x->mac_entries);
|
||||||
spin_unlock(&lan966x->mac_lock);
|
spin_unlock(&lan966x->mac_lock);
|
||||||
|
|
||||||
@ -424,6 +468,7 @@ irqreturn_t lan966x_mac_irq_handler(struct lan966x *lan966x)
|
|||||||
lan966x, ANA_MACTINDX);
|
lan966x, ANA_MACTINDX);
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
|
spin_lock(&lan966x->mac_lock);
|
||||||
lan_rmw(ANA_MACACCESS_MAC_TABLE_CMD_SET(MACACCESS_CMD_SYNC_GET_NEXT),
|
lan_rmw(ANA_MACACCESS_MAC_TABLE_CMD_SET(MACACCESS_CMD_SYNC_GET_NEXT),
|
||||||
ANA_MACACCESS_MAC_TABLE_CMD,
|
ANA_MACACCESS_MAC_TABLE_CMD,
|
||||||
lan966x, ANA_MACACCESS);
|
lan966x, ANA_MACACCESS);
|
||||||
@ -447,12 +492,15 @@ irqreturn_t lan966x_mac_irq_handler(struct lan966x *lan966x)
|
|||||||
stop = false;
|
stop = false;
|
||||||
|
|
||||||
if (column == LAN966X_MAC_COLUMNS - 1 &&
|
if (column == LAN966X_MAC_COLUMNS - 1 &&
|
||||||
index == 0 && stop)
|
index == 0 && stop) {
|
||||||
|
spin_unlock(&lan966x->mac_lock);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
entry[column].mach = lan_rd(lan966x, ANA_MACHDATA);
|
entry[column].mach = lan_rd(lan966x, ANA_MACHDATA);
|
||||||
entry[column].macl = lan_rd(lan966x, ANA_MACLDATA);
|
entry[column].macl = lan_rd(lan966x, ANA_MACLDATA);
|
||||||
entry[column].maca = lan_rd(lan966x, ANA_MACACCESS);
|
entry[column].maca = lan_rd(lan966x, ANA_MACACCESS);
|
||||||
|
spin_unlock(&lan966x->mac_lock);
|
||||||
|
|
||||||
/* Once all the columns are read process them */
|
/* Once all the columns are read process them */
|
||||||
if (column == LAN966X_MAC_COLUMNS - 1) {
|
if (column == LAN966X_MAC_COLUMNS - 1) {
|
||||||
|
Loading…
Reference in New Issue
Block a user