290 lines · c
1// SPDX-License-Identifier: GPL-2.0+2 3#include <net/switchdev.h>4 5#include "lan966x_main.h"6 7struct lan966x_fdb_event_work {8 struct work_struct work;9 struct switchdev_notifier_fdb_info fdb_info;10 struct net_device *dev;11 struct net_device *orig_dev;12 struct lan966x *lan966x;13 unsigned long event;14};15 16struct lan966x_fdb_entry {17 struct list_head list;18 unsigned char mac[ETH_ALEN] __aligned(2);19 u16 vid;20 u32 references;21};22 23static struct lan966x_fdb_entry *24lan966x_fdb_find_entry(struct lan966x *lan966x,25 struct switchdev_notifier_fdb_info *fdb_info)26{27 struct lan966x_fdb_entry *fdb_entry;28 29 list_for_each_entry(fdb_entry, &lan966x->fdb_entries, list) {30 if (fdb_entry->vid == fdb_info->vid &&31 ether_addr_equal(fdb_entry->mac, fdb_info->addr))32 return fdb_entry;33 }34 35 return NULL;36}37 38static void lan966x_fdb_add_entry(struct lan966x *lan966x,39 struct switchdev_notifier_fdb_info *fdb_info)40{41 struct lan966x_fdb_entry *fdb_entry;42 43 fdb_entry = lan966x_fdb_find_entry(lan966x, fdb_info);44 if (fdb_entry) {45 fdb_entry->references++;46 return;47 }48 49 fdb_entry = kzalloc(sizeof(*fdb_entry), GFP_KERNEL);50 if (!fdb_entry)51 return;52 53 ether_addr_copy(fdb_entry->mac, fdb_info->addr);54 fdb_entry->vid = fdb_info->vid;55 fdb_entry->references = 1;56 list_add_tail(&fdb_entry->list, &lan966x->fdb_entries);57}58 59static bool lan966x_fdb_del_entry(struct lan966x *lan966x,60 struct switchdev_notifier_fdb_info *fdb_info)61{62 struct lan966x_fdb_entry *fdb_entry, *tmp;63 64 list_for_each_entry_safe(fdb_entry, tmp, &lan966x->fdb_entries,65 list) {66 if (fdb_entry->vid == fdb_info->vid &&67 ether_addr_equal(fdb_entry->mac, fdb_info->addr)) {68 fdb_entry->references--;69 if (!fdb_entry->references) {70 list_del(&fdb_entry->list);71 kfree(fdb_entry);72 return true;73 }74 break;75 }76 }77 78 return false;79}80 81void lan966x_fdb_write_entries(struct lan966x *lan966x, u16 vid)82{83 struct lan966x_fdb_entry *fdb_entry;84 85 list_for_each_entry(fdb_entry, &lan966x->fdb_entries, list) {86 if (fdb_entry->vid != vid)87 continue;88 89 lan966x_mac_cpu_learn(lan966x, fdb_entry->mac, fdb_entry->vid);90 }91}92 93void lan966x_fdb_erase_entries(struct lan966x *lan966x, u16 vid)94{95 struct lan966x_fdb_entry *fdb_entry;96 97 list_for_each_entry(fdb_entry, &lan966x->fdb_entries, list) {98 if (fdb_entry->vid != vid)99 continue;100 101 lan966x_mac_cpu_forget(lan966x, fdb_entry->mac, fdb_entry->vid);102 }103}104 105static void lan966x_fdb_purge_entries(struct lan966x *lan966x)106{107 struct lan966x_fdb_entry *fdb_entry, *tmp;108 109 list_for_each_entry_safe(fdb_entry, tmp, &lan966x->fdb_entries, list) {110 list_del(&fdb_entry->list);111 kfree(fdb_entry);112 }113}114 115int lan966x_fdb_init(struct lan966x *lan966x)116{117 INIT_LIST_HEAD(&lan966x->fdb_entries);118 lan966x->fdb_work = alloc_ordered_workqueue("lan966x_order", 0);119 if (!lan966x->fdb_work)120 return -ENOMEM;121 122 return 0;123}124 125void lan966x_fdb_deinit(struct lan966x *lan966x)126{127 destroy_workqueue(lan966x->fdb_work);128 lan966x_fdb_purge_entries(lan966x);129}130 131void lan966x_fdb_flush_workqueue(struct lan966x *lan966x)132{133 flush_workqueue(lan966x->fdb_work);134}135 136static void lan966x_fdb_port_event_work(struct lan966x_fdb_event_work *fdb_work)137{138 struct switchdev_notifier_fdb_info *fdb_info;139 struct lan966x_port *port;140 struct lan966x *lan966x;141 142 lan966x = fdb_work->lan966x;143 port = netdev_priv(fdb_work->orig_dev);144 fdb_info = &fdb_work->fdb_info;145 146 switch (fdb_work->event) {147 case SWITCHDEV_FDB_ADD_TO_DEVICE:148 if (!fdb_info->added_by_user)149 break;150 lan966x_mac_add_entry(lan966x, port, fdb_info->addr,151 fdb_info->vid);152 break;153 case SWITCHDEV_FDB_DEL_TO_DEVICE:154 if (!fdb_info->added_by_user)155 break;156 lan966x_mac_del_entry(lan966x, fdb_info->addr,157 fdb_info->vid);158 break;159 }160}161 162static void lan966x_fdb_bridge_event_work(struct lan966x_fdb_event_work *fdb_work)163{164 struct switchdev_notifier_fdb_info *fdb_info;165 struct lan966x *lan966x;166 int ret;167 168 lan966x = fdb_work->lan966x;169 fdb_info = &fdb_work->fdb_info;170 171 /* In case the bridge is called */172 switch (fdb_work->event) {173 case SWITCHDEV_FDB_ADD_TO_DEVICE:174 /* If there is no front port in this vlan, there is no175 * point to copy the frame to CPU because it would be176 * just dropped at later point. So add it only if177 * there is a port but it is required to store the fdb178 * entry for later point when a port actually gets in179 * the vlan.180 */181 lan966x_fdb_add_entry(lan966x, fdb_info);182 if (!lan966x_vlan_cpu_member_cpu_vlan_mask(lan966x,183 fdb_info->vid))184 break;185 186 lan966x_mac_cpu_learn(lan966x, fdb_info->addr,187 fdb_info->vid);188 break;189 case SWITCHDEV_FDB_DEL_TO_DEVICE:190 ret = lan966x_fdb_del_entry(lan966x, fdb_info);191 if (!lan966x_vlan_cpu_member_cpu_vlan_mask(lan966x,192 fdb_info->vid))193 break;194 195 if (ret)196 lan966x_mac_cpu_forget(lan966x, fdb_info->addr,197 fdb_info->vid);198 break;199 }200}201 202static void lan966x_fdb_lag_event_work(struct lan966x_fdb_event_work *fdb_work)203{204 struct switchdev_notifier_fdb_info *fdb_info;205 struct lan966x_port *port;206 struct lan966x *lan966x;207 208 if (!lan966x_lag_first_port(fdb_work->orig_dev, fdb_work->dev))209 return;210 211 lan966x = fdb_work->lan966x;212 port = netdev_priv(fdb_work->dev);213 fdb_info = &fdb_work->fdb_info;214 215 switch (fdb_work->event) {216 case SWITCHDEV_FDB_ADD_TO_DEVICE:217 if (!fdb_info->added_by_user)218 break;219 lan966x_mac_add_entry(lan966x, port, fdb_info->addr,220 fdb_info->vid);221 break;222 case SWITCHDEV_FDB_DEL_TO_DEVICE:223 if (!fdb_info->added_by_user)224 break;225 lan966x_mac_del_entry(lan966x, fdb_info->addr, fdb_info->vid);226 break;227 }228}229 230static void lan966x_fdb_event_work(struct work_struct *work)231{232 struct lan966x_fdb_event_work *fdb_work =233 container_of(work, struct lan966x_fdb_event_work, work);234 235 if (lan966x_netdevice_check(fdb_work->orig_dev))236 lan966x_fdb_port_event_work(fdb_work);237 else if (netif_is_bridge_master(fdb_work->orig_dev))238 lan966x_fdb_bridge_event_work(fdb_work);239 else if (netif_is_lag_master(fdb_work->orig_dev))240 lan966x_fdb_lag_event_work(fdb_work);241 242 kfree(fdb_work->fdb_info.addr);243 kfree(fdb_work);244}245 246int lan966x_handle_fdb(struct net_device *dev,247 struct net_device *orig_dev,248 unsigned long event, const void *ctx,249 const struct switchdev_notifier_fdb_info *fdb_info)250{251 struct lan966x_port *port = netdev_priv(dev);252 struct lan966x *lan966x = port->lan966x;253 struct lan966x_fdb_event_work *fdb_work;254 255 if (ctx && ctx != port)256 return 0;257 258 switch (event) {259 case SWITCHDEV_FDB_ADD_TO_DEVICE:260 case SWITCHDEV_FDB_DEL_TO_DEVICE:261 if (lan966x_netdevice_check(orig_dev) &&262 !fdb_info->added_by_user)263 break;264 265 fdb_work = kzalloc(sizeof(*fdb_work), GFP_ATOMIC);266 if (!fdb_work)267 return -ENOMEM;268 269 fdb_work->dev = dev;270 fdb_work->orig_dev = orig_dev;271 fdb_work->lan966x = lan966x;272 fdb_work->event = event;273 INIT_WORK(&fdb_work->work, lan966x_fdb_event_work);274 memcpy(&fdb_work->fdb_info, fdb_info, sizeof(fdb_work->fdb_info));275 fdb_work->fdb_info.addr = kzalloc(ETH_ALEN, GFP_ATOMIC);276 if (!fdb_work->fdb_info.addr)277 goto err_addr_alloc;278 279 ether_addr_copy((u8 *)fdb_work->fdb_info.addr, fdb_info->addr);280 281 queue_work(lan966x->fdb_work, &fdb_work->work);282 break;283 }284 285 return 0;286err_addr_alloc:287 kfree(fdb_work);288 return -ENOMEM;289}290