net: lan966x: Split lan966x_fdb_event_work
[sfrench/cifs-2.6.git] / drivers / net / ethernet / microchip / lan966x / lan966x_fdb.c
1 // SPDX-License-Identifier: GPL-2.0+
2
3 #include <net/switchdev.h>
4
5 #include "lan966x_main.h"
6
7 struct 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
16 struct lan966x_fdb_entry {
17         struct list_head list;
18         unsigned char mac[ETH_ALEN] __aligned(2);
19         u16 vid;
20         u32 references;
21 };
22
23 static struct lan966x_fdb_entry *
24 lan966x_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
38 static 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
59 static 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
81 void 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
93 void 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
105 static 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
115 int 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
125 void lan966x_fdb_deinit(struct lan966x *lan966x)
126 {
127         destroy_workqueue(lan966x->fdb_work);
128         lan966x_fdb_purge_entries(lan966x);
129 }
130
131 static void lan966x_fdb_port_event_work(struct lan966x_fdb_event_work *fdb_work)
132 {
133         struct switchdev_notifier_fdb_info *fdb_info;
134         struct lan966x_port *port;
135         struct lan966x *lan966x;
136
137         lan966x = fdb_work->lan966x;
138         port = netdev_priv(fdb_work->orig_dev);
139         fdb_info = &fdb_work->fdb_info;
140
141         switch (fdb_work->event) {
142         case SWITCHDEV_FDB_ADD_TO_DEVICE:
143                 if (!fdb_info->added_by_user)
144                         break;
145                 lan966x_mac_add_entry(lan966x, port, fdb_info->addr,
146                                       fdb_info->vid);
147                 break;
148         case SWITCHDEV_FDB_DEL_TO_DEVICE:
149                 if (!fdb_info->added_by_user)
150                         break;
151                 lan966x_mac_del_entry(lan966x, fdb_info->addr,
152                                       fdb_info->vid);
153                 break;
154         }
155 }
156
157 static void lan966x_fdb_bridge_event_work(struct lan966x_fdb_event_work *fdb_work)
158 {
159         struct switchdev_notifier_fdb_info *fdb_info;
160         struct lan966x *lan966x;
161         int ret;
162
163         lan966x = fdb_work->lan966x;
164         fdb_info = &fdb_work->fdb_info;
165
166         /* In case the bridge is called */
167         switch (fdb_work->event) {
168         case SWITCHDEV_FDB_ADD_TO_DEVICE:
169                 /* If there is no front port in this vlan, there is no
170                  * point to copy the frame to CPU because it would be
171                  * just dropped at later point. So add it only if
172                  * there is a port but it is required to store the fdb
173                  * entry for later point when a port actually gets in
174                  * the vlan.
175                  */
176                 lan966x_fdb_add_entry(lan966x, fdb_info);
177                 if (!lan966x_vlan_cpu_member_cpu_vlan_mask(lan966x,
178                                                            fdb_info->vid))
179                         break;
180
181                 lan966x_mac_cpu_learn(lan966x, fdb_info->addr,
182                                       fdb_info->vid);
183                 break;
184         case SWITCHDEV_FDB_DEL_TO_DEVICE:
185                 ret = lan966x_fdb_del_entry(lan966x, fdb_info);
186                 if (!lan966x_vlan_cpu_member_cpu_vlan_mask(lan966x,
187                                                            fdb_info->vid))
188                         break;
189
190                 if (ret)
191                         lan966x_mac_cpu_forget(lan966x, fdb_info->addr,
192                                                fdb_info->vid);
193                 break;
194         }
195 }
196
197 static void lan966x_fdb_event_work(struct work_struct *work)
198 {
199         struct lan966x_fdb_event_work *fdb_work =
200                 container_of(work, struct lan966x_fdb_event_work, work);
201
202         if (lan966x_netdevice_check(fdb_work->orig_dev))
203                 lan966x_fdb_port_event_work(fdb_work);
204         else if (netif_is_bridge_master(fdb_work->orig_dev))
205                 lan966x_fdb_bridge_event_work(fdb_work);
206
207         kfree(fdb_work->fdb_info.addr);
208         dev_put(fdb_work->dev);
209         dev_put(fdb_work->orig_dev);
210         kfree(fdb_work);
211 }
212
213 int lan966x_handle_fdb(struct net_device *dev,
214                        struct net_device *orig_dev,
215                        unsigned long event, const void *ctx,
216                        const struct switchdev_notifier_fdb_info *fdb_info)
217 {
218         struct lan966x_port *port = netdev_priv(dev);
219         struct lan966x *lan966x = port->lan966x;
220         struct lan966x_fdb_event_work *fdb_work;
221
222         if (ctx && ctx != port)
223                 return 0;
224
225         switch (event) {
226         case SWITCHDEV_FDB_ADD_TO_DEVICE:
227         case SWITCHDEV_FDB_DEL_TO_DEVICE:
228                 if (lan966x_netdevice_check(orig_dev) &&
229                     !fdb_info->added_by_user)
230                         break;
231
232                 fdb_work = kzalloc(sizeof(*fdb_work), GFP_ATOMIC);
233                 if (!fdb_work)
234                         return -ENOMEM;
235
236                 fdb_work->dev = dev;
237                 fdb_work->orig_dev = orig_dev;
238                 fdb_work->lan966x = lan966x;
239                 fdb_work->event = event;
240                 INIT_WORK(&fdb_work->work, lan966x_fdb_event_work);
241                 memcpy(&fdb_work->fdb_info, fdb_info, sizeof(fdb_work->fdb_info));
242                 fdb_work->fdb_info.addr = kzalloc(ETH_ALEN, GFP_ATOMIC);
243                 if (!fdb_work->fdb_info.addr)
244                         goto err_addr_alloc;
245
246                 ether_addr_copy((u8 *)fdb_work->fdb_info.addr, fdb_info->addr);
247                 dev_hold(dev);
248                 dev_hold(orig_dev);
249
250                 queue_work(lan966x->fdb_work, &fdb_work->work);
251                 break;
252         }
253
254         return 0;
255 err_addr_alloc:
256         kfree(fdb_work);
257         return -ENOMEM;
258 }