lan966x_fdb.c (6007B)
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 lan966x *lan966x; 12 unsigned long event; 13}; 14 15struct lan966x_fdb_entry { 16 struct list_head list; 17 unsigned char mac[ETH_ALEN] __aligned(2); 18 u16 vid; 19 u32 references; 20}; 21 22static struct lan966x_fdb_entry * 23lan966x_fdb_find_entry(struct lan966x *lan966x, 24 struct switchdev_notifier_fdb_info *fdb_info) 25{ 26 struct lan966x_fdb_entry *fdb_entry; 27 28 list_for_each_entry(fdb_entry, &lan966x->fdb_entries, list) { 29 if (fdb_entry->vid == fdb_info->vid && 30 ether_addr_equal(fdb_entry->mac, fdb_info->addr)) 31 return fdb_entry; 32 } 33 34 return NULL; 35} 36 37static void lan966x_fdb_add_entry(struct lan966x *lan966x, 38 struct switchdev_notifier_fdb_info *fdb_info) 39{ 40 struct lan966x_fdb_entry *fdb_entry; 41 42 fdb_entry = lan966x_fdb_find_entry(lan966x, fdb_info); 43 if (fdb_entry) { 44 fdb_entry->references++; 45 return; 46 } 47 48 fdb_entry = kzalloc(sizeof(*fdb_entry), GFP_KERNEL); 49 if (!fdb_entry) 50 return; 51 52 ether_addr_copy(fdb_entry->mac, fdb_info->addr); 53 fdb_entry->vid = fdb_info->vid; 54 fdb_entry->references = 1; 55 list_add_tail(&fdb_entry->list, &lan966x->fdb_entries); 56} 57 58static bool lan966x_fdb_del_entry(struct lan966x *lan966x, 59 struct switchdev_notifier_fdb_info *fdb_info) 60{ 61 struct lan966x_fdb_entry *fdb_entry, *tmp; 62 63 list_for_each_entry_safe(fdb_entry, tmp, &lan966x->fdb_entries, 64 list) { 65 if (fdb_entry->vid == fdb_info->vid && 66 ether_addr_equal(fdb_entry->mac, fdb_info->addr)) { 67 fdb_entry->references--; 68 if (!fdb_entry->references) { 69 list_del(&fdb_entry->list); 70 kfree(fdb_entry); 71 return true; 72 } 73 break; 74 } 75 } 76 77 return false; 78} 79 80void lan966x_fdb_write_entries(struct lan966x *lan966x, u16 vid) 81{ 82 struct lan966x_fdb_entry *fdb_entry; 83 84 list_for_each_entry(fdb_entry, &lan966x->fdb_entries, list) { 85 if (fdb_entry->vid != vid) 86 continue; 87 88 lan966x_mac_cpu_learn(lan966x, fdb_entry->mac, fdb_entry->vid); 89 } 90} 91 92void lan966x_fdb_erase_entries(struct lan966x *lan966x, u16 vid) 93{ 94 struct lan966x_fdb_entry *fdb_entry; 95 96 list_for_each_entry(fdb_entry, &lan966x->fdb_entries, list) { 97 if (fdb_entry->vid != vid) 98 continue; 99 100 lan966x_mac_cpu_forget(lan966x, fdb_entry->mac, fdb_entry->vid); 101 } 102} 103 104static void lan966x_fdb_purge_entries(struct lan966x *lan966x) 105{ 106 struct lan966x_fdb_entry *fdb_entry, *tmp; 107 108 list_for_each_entry_safe(fdb_entry, tmp, &lan966x->fdb_entries, list) { 109 list_del(&fdb_entry->list); 110 kfree(fdb_entry); 111 } 112} 113 114int lan966x_fdb_init(struct lan966x *lan966x) 115{ 116 INIT_LIST_HEAD(&lan966x->fdb_entries); 117 lan966x->fdb_work = alloc_ordered_workqueue("lan966x_order", 0); 118 if (!lan966x->fdb_work) 119 return -ENOMEM; 120 121 return 0; 122} 123 124void lan966x_fdb_deinit(struct lan966x *lan966x) 125{ 126 destroy_workqueue(lan966x->fdb_work); 127 lan966x_fdb_purge_entries(lan966x); 128} 129 130static void lan966x_fdb_event_work(struct work_struct *work) 131{ 132 struct lan966x_fdb_event_work *fdb_work = 133 container_of(work, struct lan966x_fdb_event_work, work); 134 struct switchdev_notifier_fdb_info *fdb_info; 135 struct net_device *dev = fdb_work->dev; 136 struct lan966x_port *port; 137 struct lan966x *lan966x; 138 int ret; 139 140 fdb_info = &fdb_work->fdb_info; 141 lan966x = fdb_work->lan966x; 142 143 if (lan966x_netdevice_check(dev)) { 144 port = netdev_priv(dev); 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 } else { 161 if (!netif_is_bridge_master(dev)) 162 goto out; 163 164 /* In case the bridge is called */ 165 switch (fdb_work->event) { 166 case SWITCHDEV_FDB_ADD_TO_DEVICE: 167 /* If there is no front port in this vlan, there is no 168 * point to copy the frame to CPU because it would be 169 * just dropped at later point. So add it only if 170 * there is a port but it is required to store the fdb 171 * entry for later point when a port actually gets in 172 * the vlan. 173 */ 174 lan966x_fdb_add_entry(lan966x, fdb_info); 175 if (!lan966x_vlan_cpu_member_cpu_vlan_mask(lan966x, 176 fdb_info->vid)) 177 break; 178 179 lan966x_mac_cpu_learn(lan966x, fdb_info->addr, 180 fdb_info->vid); 181 break; 182 case SWITCHDEV_FDB_DEL_TO_DEVICE: 183 ret = lan966x_fdb_del_entry(lan966x, fdb_info); 184 if (!lan966x_vlan_cpu_member_cpu_vlan_mask(lan966x, 185 fdb_info->vid)) 186 break; 187 188 if (ret) 189 lan966x_mac_cpu_forget(lan966x, fdb_info->addr, 190 fdb_info->vid); 191 break; 192 } 193 } 194 195out: 196 kfree(fdb_work->fdb_info.addr); 197 kfree(fdb_work); 198 dev_put(dev); 199} 200 201int lan966x_handle_fdb(struct net_device *dev, 202 struct net_device *orig_dev, 203 unsigned long event, const void *ctx, 204 const struct switchdev_notifier_fdb_info *fdb_info) 205{ 206 struct lan966x_port *port = netdev_priv(dev); 207 struct lan966x *lan966x = port->lan966x; 208 struct lan966x_fdb_event_work *fdb_work; 209 210 if (ctx && ctx != port) 211 return 0; 212 213 switch (event) { 214 case SWITCHDEV_FDB_ADD_TO_DEVICE: 215 case SWITCHDEV_FDB_DEL_TO_DEVICE: 216 if (lan966x_netdevice_check(orig_dev) && 217 !fdb_info->added_by_user) 218 break; 219 220 fdb_work = kzalloc(sizeof(*fdb_work), GFP_ATOMIC); 221 if (!fdb_work) 222 return -ENOMEM; 223 224 fdb_work->dev = orig_dev; 225 fdb_work->lan966x = lan966x; 226 fdb_work->event = event; 227 INIT_WORK(&fdb_work->work, lan966x_fdb_event_work); 228 memcpy(&fdb_work->fdb_info, fdb_info, sizeof(fdb_work->fdb_info)); 229 fdb_work->fdb_info.addr = kzalloc(ETH_ALEN, GFP_ATOMIC); 230 if (!fdb_work->fdb_info.addr) 231 goto err_addr_alloc; 232 233 ether_addr_copy((u8 *)fdb_work->fdb_info.addr, fdb_info->addr); 234 dev_hold(orig_dev); 235 236 queue_work(lan966x->fdb_work, &fdb_work->work); 237 break; 238 } 239 240 return 0; 241err_addr_alloc: 242 kfree(fdb_work); 243 return -ENOMEM; 244}