1 // SPDX-License-Identifier: GPL-2.0
2
3 /* Texas Instruments ICSSM Ethernet Driver
4 *
5 * Copyright (C) 2018-2022 Texas Instruments Incorporated - https://www.ti.com/
6 *
7 */
8
9 #include <linux/etherdevice.h>
10 #include <linux/kernel.h>
11 #include <linux/remoteproc.h>
12 #include <net/switchdev.h>
13
14 #include "icssm_prueth.h"
15 #include "icssm_prueth_switch.h"
16 #include "icssm_prueth_fdb_tbl.h"
17
18 /* switchev event work */
19 struct icssm_sw_event_work {
20 netdevice_tracker ndev_tracker;
21 struct work_struct work;
22 struct switchdev_notifier_fdb_info fdb_info;
23 struct prueth_emac *emac;
24 unsigned long event;
25 };
26
icssm_prueth_sw_set_stp_state(struct prueth * prueth,enum prueth_port port,u8 state)27 void icssm_prueth_sw_set_stp_state(struct prueth *prueth,
28 enum prueth_port port, u8 state)
29 {
30 struct fdb_tbl *t = prueth->fdb_tbl;
31
32 writeb(state, port - 1 ? (void __iomem *)&t->port2_stp_cfg->state :
33 (void __iomem *)&t->port1_stp_cfg->state);
34 }
35
icssm_prueth_sw_get_stp_state(struct prueth * prueth,enum prueth_port port)36 u8 icssm_prueth_sw_get_stp_state(struct prueth *prueth, enum prueth_port port)
37 {
38 struct fdb_tbl *t = prueth->fdb_tbl;
39 u8 state;
40
41 state = readb(port - 1 ? (void __iomem *)&t->port2_stp_cfg->state :
42 (void __iomem *)&t->port1_stp_cfg->state);
43 return state;
44 }
45
icssm_prueth_sw_attr_set(struct net_device * ndev,const void * ctx,const struct switchdev_attr * attr,struct netlink_ext_ack * extack)46 static int icssm_prueth_sw_attr_set(struct net_device *ndev, const void *ctx,
47 const struct switchdev_attr *attr,
48 struct netlink_ext_ack *extack)
49 {
50 struct prueth_emac *emac = netdev_priv(ndev);
51 struct prueth *prueth = emac->prueth;
52 int err = 0;
53 u8 o_state;
54
55 /* Interface is not up */
56 if (!prueth->fdb_tbl)
57 return 0;
58
59 switch (attr->id) {
60 case SWITCHDEV_ATTR_ID_PORT_STP_STATE:
61 o_state = icssm_prueth_sw_get_stp_state(prueth, emac->port_id);
62 icssm_prueth_sw_set_stp_state(prueth, emac->port_id,
63 attr->u.stp_state);
64
65 if (o_state != attr->u.stp_state)
66 icssm_prueth_sw_purge_fdb(emac);
67
68 dev_dbg(prueth->dev, "attr set: stp state:%u port:%u\n",
69 attr->u.stp_state, emac->port_id);
70 break;
71 default:
72 err = -EOPNOTSUPP;
73 break;
74 }
75
76 return err;
77 }
78
icssm_prueth_sw_fdb_offload(struct net_device * ndev,struct switchdev_notifier_fdb_info * rcv)79 static void icssm_prueth_sw_fdb_offload(struct net_device *ndev,
80 struct switchdev_notifier_fdb_info *rcv)
81 {
82 struct switchdev_notifier_fdb_info info;
83
84 info.addr = rcv->addr;
85 info.vid = rcv->vid;
86 call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED, ndev, &info.info,
87 NULL);
88 }
89
90 /**
91 * icssm_sw_event_work - insert/delete fdb entry
92 *
93 * @work: work structure
94 *
95 */
icssm_sw_event_work(struct work_struct * work)96 static void icssm_sw_event_work(struct work_struct *work)
97 {
98 struct icssm_sw_event_work *switchdev_work =
99 container_of(work, struct icssm_sw_event_work, work);
100 struct prueth_emac *emac = switchdev_work->emac;
101 struct switchdev_notifier_fdb_info *fdb;
102 struct prueth *prueth = emac->prueth;
103 int port = emac->port_id;
104
105 rtnl_lock();
106
107 /* Interface is not up */
108 if (!emac->prueth->fdb_tbl)
109 goto free;
110
111 switch (switchdev_work->event) {
112 case SWITCHDEV_FDB_ADD_TO_DEVICE:
113 fdb = &switchdev_work->fdb_info;
114 dev_dbg(prueth->dev,
115 "prueth fdb add: MACID = %pM vid = %u flags = %u -- port %d\n",
116 fdb->addr, fdb->vid, fdb->added_by_user, port);
117
118 if (!fdb->added_by_user)
119 break;
120
121 if (fdb->is_local)
122 break;
123
124 icssm_prueth_sw_fdb_add(emac, fdb);
125 icssm_prueth_sw_fdb_offload(emac->ndev, fdb);
126 break;
127 case SWITCHDEV_FDB_DEL_TO_DEVICE:
128 fdb = &switchdev_work->fdb_info;
129 dev_dbg(prueth->dev,
130 "prueth fdb del: MACID = %pM vid = %u flags = %u -- port %d\n",
131 fdb->addr, fdb->vid, fdb->added_by_user, port);
132
133 if (fdb->is_local)
134 break;
135
136 icssm_prueth_sw_fdb_del(emac, fdb);
137 break;
138 default:
139 break;
140 }
141
142 free:
143 rtnl_unlock();
144
145 netdev_put(emac->ndev, &switchdev_work->ndev_tracker);
146 kfree(switchdev_work->fdb_info.addr);
147 kfree(switchdev_work);
148 }
149
150 /* called under rcu_read_lock() */
icssm_prueth_sw_switchdev_event(struct notifier_block * unused,unsigned long event,void * ptr)151 static int icssm_prueth_sw_switchdev_event(struct notifier_block *unused,
152 unsigned long event, void *ptr)
153 {
154 struct net_device *ndev = switchdev_notifier_info_to_dev(ptr);
155 struct switchdev_notifier_fdb_info *fdb_info = ptr;
156 struct prueth_emac *emac = netdev_priv(ndev);
157 struct icssm_sw_event_work *switchdev_work;
158 int err;
159
160 if (!icssm_prueth_sw_port_dev_check(ndev))
161 return NOTIFY_DONE;
162
163 if (event == SWITCHDEV_PORT_ATTR_SET) {
164 err = switchdev_handle_port_attr_set
165 (ndev, ptr, icssm_prueth_sw_port_dev_check,
166 icssm_prueth_sw_attr_set);
167 return notifier_from_errno(err);
168 }
169
170 switchdev_work = kzalloc_obj(*switchdev_work, GFP_ATOMIC);
171 if (WARN_ON(!switchdev_work))
172 return NOTIFY_BAD;
173
174 INIT_WORK(&switchdev_work->work, icssm_sw_event_work);
175 switchdev_work->emac = emac;
176 switchdev_work->event = event;
177
178 switch (event) {
179 case SWITCHDEV_FDB_ADD_TO_DEVICE:
180 case SWITCHDEV_FDB_DEL_TO_DEVICE:
181 memcpy(&switchdev_work->fdb_info, ptr,
182 sizeof(switchdev_work->fdb_info));
183 switchdev_work->fdb_info.addr = kzalloc(ETH_ALEN, GFP_ATOMIC);
184 if (!switchdev_work->fdb_info.addr)
185 goto err_addr_alloc;
186 ether_addr_copy((u8 *)switchdev_work->fdb_info.addr,
187 fdb_info->addr);
188 netdev_hold(ndev, &switchdev_work->ndev_tracker, GFP_ATOMIC);
189 break;
190 default:
191 kfree(switchdev_work);
192 return NOTIFY_DONE;
193 }
194
195 queue_work(system_long_wq, &switchdev_work->work);
196
197 return NOTIFY_DONE;
198
199 err_addr_alloc:
200 kfree(switchdev_work);
201 return NOTIFY_BAD;
202 }
203
icssm_prueth_switchdev_obj_add(struct net_device * ndev,const void * ctx,const struct switchdev_obj * obj,struct netlink_ext_ack * extack)204 static int icssm_prueth_switchdev_obj_add(struct net_device *ndev,
205 const void *ctx,
206 const struct switchdev_obj *obj,
207 struct netlink_ext_ack *extack)
208 {
209 struct switchdev_obj_port_mdb *mdb = SWITCHDEV_OBJ_PORT_MDB(obj);
210 struct prueth_emac *emac = netdev_priv(ndev);
211 struct prueth *prueth = emac->prueth;
212 int ret = 0;
213 u8 hash;
214
215 switch (obj->id) {
216 case SWITCHDEV_OBJ_ID_HOST_MDB:
217 dev_dbg(prueth->dev, "MDB add: %s: vid %u:%pM port: %x\n",
218 ndev->name, mdb->vid, mdb->addr, emac->port_id);
219 hash = icssm_emac_get_mc_hash(mdb->addr, emac->mc_filter_mask);
220 icssm_emac_mc_filter_bin_allow(emac, hash);
221 break;
222 default:
223 ret = -EOPNOTSUPP;
224 break;
225 }
226
227 return ret;
228 }
229
icssm_prueth_switchdev_obj_del(struct net_device * ndev,const void * ctx,const struct switchdev_obj * obj)230 static int icssm_prueth_switchdev_obj_del(struct net_device *ndev,
231 const void *ctx,
232 const struct switchdev_obj *obj)
233 {
234 struct switchdev_obj_port_mdb *mdb = SWITCHDEV_OBJ_PORT_MDB(obj);
235 struct prueth_emac *emac = netdev_priv(ndev);
236 struct prueth *prueth = emac->prueth;
237 struct netdev_hw_addr *ha;
238 u8 hash, tmp_hash;
239 int ret = 0;
240 u8 *mask;
241
242 switch (obj->id) {
243 case SWITCHDEV_OBJ_ID_HOST_MDB:
244 dev_dbg(prueth->dev, "MDB del: %s: vid %u:%pM port: %x\n",
245 ndev->name, mdb->vid, mdb->addr, emac->port_id);
246 if (prueth->hw_bridge_dev) {
247 mask = emac->mc_filter_mask;
248 hash = icssm_emac_get_mc_hash(mdb->addr, mask);
249 netdev_for_each_mc_addr(ha, prueth->hw_bridge_dev) {
250 tmp_hash = icssm_emac_get_mc_hash(ha->addr,
251 mask);
252 /* Another MC address is in the bin.
253 * Don't disable.
254 */
255 if (tmp_hash == hash)
256 return 0;
257 }
258 icssm_emac_mc_filter_bin_disallow(emac, hash);
259 }
260 break;
261 default:
262 ret = -EOPNOTSUPP;
263 break;
264 }
265
266 return ret;
267 }
268
269 /* switchdev notifiers */
icssm_prueth_sw_blocking_event(struct notifier_block * unused,unsigned long event,void * ptr)270 static int icssm_prueth_sw_blocking_event(struct notifier_block *unused,
271 unsigned long event, void *ptr)
272 {
273 struct net_device *ndev = switchdev_notifier_info_to_dev(ptr);
274 int err;
275
276 switch (event) {
277 case SWITCHDEV_PORT_OBJ_ADD:
278 err = switchdev_handle_port_obj_add
279 (ndev, ptr, icssm_prueth_sw_port_dev_check,
280 icssm_prueth_switchdev_obj_add);
281 return notifier_from_errno(err);
282
283 case SWITCHDEV_PORT_OBJ_DEL:
284 err = switchdev_handle_port_obj_del
285 (ndev, ptr, icssm_prueth_sw_port_dev_check,
286 icssm_prueth_switchdev_obj_del);
287 return notifier_from_errno(err);
288
289 case SWITCHDEV_PORT_ATTR_SET:
290 err = switchdev_handle_port_attr_set
291 (ndev, ptr, icssm_prueth_sw_port_dev_check,
292 icssm_prueth_sw_attr_set);
293 return notifier_from_errno(err);
294
295 default:
296 break;
297 }
298
299 return NOTIFY_DONE;
300 }
301
icssm_prueth_sw_register_notifiers(struct prueth * prueth)302 int icssm_prueth_sw_register_notifiers(struct prueth *prueth)
303 {
304 int ret = 0;
305
306 prueth->prueth_switchdev_nb.notifier_call =
307 &icssm_prueth_sw_switchdev_event;
308 ret = register_switchdev_notifier(&prueth->prueth_switchdev_nb);
309 if (ret) {
310 dev_err(prueth->dev,
311 "register switchdev notifier failed ret:%d\n", ret);
312 return ret;
313 }
314
315 prueth->prueth_switchdev_bl_nb.notifier_call =
316 &icssm_prueth_sw_blocking_event;
317 ret = register_switchdev_blocking_notifier
318 (&prueth->prueth_switchdev_bl_nb);
319 if (ret) {
320 dev_err(prueth->dev,
321 "register switchdev blocking notifier failed ret:%d\n",
322 ret);
323 unregister_switchdev_notifier(&prueth->prueth_switchdev_nb);
324 }
325
326 return ret;
327 }
328
icssm_prueth_sw_unregister_notifiers(struct prueth * prueth)329 void icssm_prueth_sw_unregister_notifiers(struct prueth *prueth)
330 {
331 unregister_switchdev_blocking_notifier(&prueth->prueth_switchdev_bl_nb);
332 unregister_switchdev_notifier(&prueth->prueth_switchdev_nb);
333 }
334