xref: /linux/drivers/net/ethernet/ti/icssm/icssm_switchdev.c (revision 8934827db5403eae57d4537114a9ff88b0a8460f)
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