linux-stable/drivers/net/ethernet/microchip/lan966x/lan966x_fdb.c
Horatiu Vultur 9be99f2d1d net: lan966x: Extend FDB to support also lag
Offload FDB entries when the original device is a lag interface. Because
all the ports under the lag have the same chip id, which is the chip id
of first port, then add the entries only for the first port.

Signed-off-by: Horatiu Vultur <horatiu.vultur@microchip.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2022-08-22 14:00:54 +01:00

289 lines
7.1 KiB
C

// SPDX-License-Identifier: GPL-2.0+
#include <net/switchdev.h>
#include "lan966x_main.h"
struct lan966x_fdb_event_work {
struct work_struct work;
struct switchdev_notifier_fdb_info fdb_info;
struct net_device *dev;
struct net_device *orig_dev;
struct lan966x *lan966x;
unsigned long event;
};
struct lan966x_fdb_entry {
struct list_head list;
unsigned char mac[ETH_ALEN] __aligned(2);
u16 vid;
u32 references;
};
static struct lan966x_fdb_entry *
lan966x_fdb_find_entry(struct lan966x *lan966x,
struct switchdev_notifier_fdb_info *fdb_info)
{
struct lan966x_fdb_entry *fdb_entry;
list_for_each_entry(fdb_entry, &lan966x->fdb_entries, list) {
if (fdb_entry->vid == fdb_info->vid &&
ether_addr_equal(fdb_entry->mac, fdb_info->addr))
return fdb_entry;
}
return NULL;
}
static void lan966x_fdb_add_entry(struct lan966x *lan966x,
struct switchdev_notifier_fdb_info *fdb_info)
{
struct lan966x_fdb_entry *fdb_entry;
fdb_entry = lan966x_fdb_find_entry(lan966x, fdb_info);
if (fdb_entry) {
fdb_entry->references++;
return;
}
fdb_entry = kzalloc(sizeof(*fdb_entry), GFP_KERNEL);
if (!fdb_entry)
return;
ether_addr_copy(fdb_entry->mac, fdb_info->addr);
fdb_entry->vid = fdb_info->vid;
fdb_entry->references = 1;
list_add_tail(&fdb_entry->list, &lan966x->fdb_entries);
}
static bool lan966x_fdb_del_entry(struct lan966x *lan966x,
struct switchdev_notifier_fdb_info *fdb_info)
{
struct lan966x_fdb_entry *fdb_entry, *tmp;
list_for_each_entry_safe(fdb_entry, tmp, &lan966x->fdb_entries,
list) {
if (fdb_entry->vid == fdb_info->vid &&
ether_addr_equal(fdb_entry->mac, fdb_info->addr)) {
fdb_entry->references--;
if (!fdb_entry->references) {
list_del(&fdb_entry->list);
kfree(fdb_entry);
return true;
}
break;
}
}
return false;
}
void lan966x_fdb_write_entries(struct lan966x *lan966x, u16 vid)
{
struct lan966x_fdb_entry *fdb_entry;
list_for_each_entry(fdb_entry, &lan966x->fdb_entries, list) {
if (fdb_entry->vid != vid)
continue;
lan966x_mac_cpu_learn(lan966x, fdb_entry->mac, fdb_entry->vid);
}
}
void lan966x_fdb_erase_entries(struct lan966x *lan966x, u16 vid)
{
struct lan966x_fdb_entry *fdb_entry;
list_for_each_entry(fdb_entry, &lan966x->fdb_entries, list) {
if (fdb_entry->vid != vid)
continue;
lan966x_mac_cpu_forget(lan966x, fdb_entry->mac, fdb_entry->vid);
}
}
static void lan966x_fdb_purge_entries(struct lan966x *lan966x)
{
struct lan966x_fdb_entry *fdb_entry, *tmp;
list_for_each_entry_safe(fdb_entry, tmp, &lan966x->fdb_entries, list) {
list_del(&fdb_entry->list);
kfree(fdb_entry);
}
}
int lan966x_fdb_init(struct lan966x *lan966x)
{
INIT_LIST_HEAD(&lan966x->fdb_entries);
lan966x->fdb_work = alloc_ordered_workqueue("lan966x_order", 0);
if (!lan966x->fdb_work)
return -ENOMEM;
return 0;
}
void lan966x_fdb_deinit(struct lan966x *lan966x)
{
destroy_workqueue(lan966x->fdb_work);
lan966x_fdb_purge_entries(lan966x);
}
void lan966x_fdb_flush_workqueue(struct lan966x *lan966x)
{
flush_workqueue(lan966x->fdb_work);
}
static void lan966x_fdb_port_event_work(struct lan966x_fdb_event_work *fdb_work)
{
struct switchdev_notifier_fdb_info *fdb_info;
struct lan966x_port *port;
struct lan966x *lan966x;
lan966x = fdb_work->lan966x;
port = netdev_priv(fdb_work->orig_dev);
fdb_info = &fdb_work->fdb_info;
switch (fdb_work->event) {
case SWITCHDEV_FDB_ADD_TO_DEVICE:
if (!fdb_info->added_by_user)
break;
lan966x_mac_add_entry(lan966x, port, fdb_info->addr,
fdb_info->vid);
break;
case SWITCHDEV_FDB_DEL_TO_DEVICE:
if (!fdb_info->added_by_user)
break;
lan966x_mac_del_entry(lan966x, fdb_info->addr,
fdb_info->vid);
break;
}
}
static void lan966x_fdb_bridge_event_work(struct lan966x_fdb_event_work *fdb_work)
{
struct switchdev_notifier_fdb_info *fdb_info;
struct lan966x *lan966x;
int ret;
lan966x = fdb_work->lan966x;
fdb_info = &fdb_work->fdb_info;
/* In case the bridge is called */
switch (fdb_work->event) {
case SWITCHDEV_FDB_ADD_TO_DEVICE:
/* If there is no front port in this vlan, there is no
* point to copy the frame to CPU because it would be
* just dropped at later point. So add it only if
* there is a port but it is required to store the fdb
* entry for later point when a port actually gets in
* the vlan.
*/
lan966x_fdb_add_entry(lan966x, fdb_info);
if (!lan966x_vlan_cpu_member_cpu_vlan_mask(lan966x,
fdb_info->vid))
break;
lan966x_mac_cpu_learn(lan966x, fdb_info->addr,
fdb_info->vid);
break;
case SWITCHDEV_FDB_DEL_TO_DEVICE:
ret = lan966x_fdb_del_entry(lan966x, fdb_info);
if (!lan966x_vlan_cpu_member_cpu_vlan_mask(lan966x,
fdb_info->vid))
break;
if (ret)
lan966x_mac_cpu_forget(lan966x, fdb_info->addr,
fdb_info->vid);
break;
}
}
static void lan966x_fdb_lag_event_work(struct lan966x_fdb_event_work *fdb_work)
{
struct switchdev_notifier_fdb_info *fdb_info;
struct lan966x_port *port;
struct lan966x *lan966x;
if (!lan966x_lag_first_port(fdb_work->orig_dev, fdb_work->dev))
return;
lan966x = fdb_work->lan966x;
port = netdev_priv(fdb_work->dev);
fdb_info = &fdb_work->fdb_info;
switch (fdb_work->event) {
case SWITCHDEV_FDB_ADD_TO_DEVICE:
if (!fdb_info->added_by_user)
break;
lan966x_mac_add_entry(lan966x, port, fdb_info->addr,
fdb_info->vid);
break;
case SWITCHDEV_FDB_DEL_TO_DEVICE:
if (!fdb_info->added_by_user)
break;
lan966x_mac_del_entry(lan966x, fdb_info->addr, fdb_info->vid);
break;
}
}
static void lan966x_fdb_event_work(struct work_struct *work)
{
struct lan966x_fdb_event_work *fdb_work =
container_of(work, struct lan966x_fdb_event_work, work);
if (lan966x_netdevice_check(fdb_work->orig_dev))
lan966x_fdb_port_event_work(fdb_work);
else if (netif_is_bridge_master(fdb_work->orig_dev))
lan966x_fdb_bridge_event_work(fdb_work);
else if (netif_is_lag_master(fdb_work->orig_dev))
lan966x_fdb_lag_event_work(fdb_work);
kfree(fdb_work->fdb_info.addr);
kfree(fdb_work);
}
int lan966x_handle_fdb(struct net_device *dev,
struct net_device *orig_dev,
unsigned long event, const void *ctx,
const struct switchdev_notifier_fdb_info *fdb_info)
{
struct lan966x_port *port = netdev_priv(dev);
struct lan966x *lan966x = port->lan966x;
struct lan966x_fdb_event_work *fdb_work;
if (ctx && ctx != port)
return 0;
switch (event) {
case SWITCHDEV_FDB_ADD_TO_DEVICE:
case SWITCHDEV_FDB_DEL_TO_DEVICE:
if (lan966x_netdevice_check(orig_dev) &&
!fdb_info->added_by_user)
break;
fdb_work = kzalloc(sizeof(*fdb_work), GFP_ATOMIC);
if (!fdb_work)
return -ENOMEM;
fdb_work->dev = dev;
fdb_work->orig_dev = orig_dev;
fdb_work->lan966x = lan966x;
fdb_work->event = event;
INIT_WORK(&fdb_work->work, lan966x_fdb_event_work);
memcpy(&fdb_work->fdb_info, fdb_info, sizeof(fdb_work->fdb_info));
fdb_work->fdb_info.addr = kzalloc(ETH_ALEN, GFP_ATOMIC);
if (!fdb_work->fdb_info.addr)
goto err_addr_alloc;
ether_addr_copy((u8 *)fdb_work->fdb_info.addr, fdb_info->addr);
queue_work(lan966x->fdb_work, &fdb_work->work);
break;
}
return 0;
err_addr_alloc:
kfree(fdb_work);
return -ENOMEM;
}