xref: /kernel/linux/linux-5.10/net/dsa/switch.c (revision 8c2ecf20)
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Handling of a single switch chip, part of a switch fabric
4 *
5 * Copyright (c) 2017 Savoir-faire Linux Inc.
6 *	Vivien Didelot <vivien.didelot@savoirfairelinux.com>
7 */
8
9#include <linux/if_bridge.h>
10#include <linux/netdevice.h>
11#include <linux/notifier.h>
12#include <linux/if_vlan.h>
13#include <net/switchdev.h>
14
15#include "dsa_priv.h"
16
17static unsigned int dsa_switch_fastest_ageing_time(struct dsa_switch *ds,
18						   unsigned int ageing_time)
19{
20	int i;
21
22	for (i = 0; i < ds->num_ports; ++i) {
23		struct dsa_port *dp = dsa_to_port(ds, i);
24
25		if (dp->ageing_time && dp->ageing_time < ageing_time)
26			ageing_time = dp->ageing_time;
27	}
28
29	return ageing_time;
30}
31
32static int dsa_switch_ageing_time(struct dsa_switch *ds,
33				  struct dsa_notifier_ageing_time_info *info)
34{
35	unsigned int ageing_time = info->ageing_time;
36	struct switchdev_trans *trans = info->trans;
37
38	if (switchdev_trans_ph_prepare(trans)) {
39		if (ds->ageing_time_min && ageing_time < ds->ageing_time_min)
40			return -ERANGE;
41		if (ds->ageing_time_max && ageing_time > ds->ageing_time_max)
42			return -ERANGE;
43		return 0;
44	}
45
46	/* Program the fastest ageing time in case of multiple bridges */
47	ageing_time = dsa_switch_fastest_ageing_time(ds, ageing_time);
48
49	if (ds->ops->set_ageing_time)
50		return ds->ops->set_ageing_time(ds, ageing_time);
51
52	return 0;
53}
54
55static bool dsa_switch_mtu_match(struct dsa_switch *ds, int port,
56				 struct dsa_notifier_mtu_info *info)
57{
58	if (ds->index == info->sw_index)
59		return (port == info->port) || dsa_is_dsa_port(ds, port);
60
61	if (!info->propagate_upstream)
62		return false;
63
64	if (dsa_is_dsa_port(ds, port) || dsa_is_cpu_port(ds, port))
65		return true;
66
67	return false;
68}
69
70static int dsa_switch_mtu(struct dsa_switch *ds,
71			  struct dsa_notifier_mtu_info *info)
72{
73	int port, ret;
74
75	if (!ds->ops->port_change_mtu)
76		return -EOPNOTSUPP;
77
78	for (port = 0; port < ds->num_ports; port++) {
79		if (dsa_switch_mtu_match(ds, port, info)) {
80			ret = ds->ops->port_change_mtu(ds, port, info->mtu);
81			if (ret)
82				return ret;
83		}
84	}
85
86	return 0;
87}
88
89static int dsa_switch_bridge_join(struct dsa_switch *ds,
90				  struct dsa_notifier_bridge_info *info)
91{
92	struct dsa_switch_tree *dst = ds->dst;
93
94	if (dst->index == info->tree_index && ds->index == info->sw_index &&
95	    ds->ops->port_bridge_join)
96		return ds->ops->port_bridge_join(ds, info->port, info->br);
97
98	if ((dst->index != info->tree_index || ds->index != info->sw_index) &&
99	    ds->ops->crosschip_bridge_join)
100		return ds->ops->crosschip_bridge_join(ds, info->tree_index,
101						      info->sw_index,
102						      info->port, info->br);
103
104	return 0;
105}
106
107static int dsa_switch_bridge_leave(struct dsa_switch *ds,
108				   struct dsa_notifier_bridge_info *info)
109{
110	bool unset_vlan_filtering = br_vlan_enabled(info->br);
111	struct dsa_switch_tree *dst = ds->dst;
112	int err, i;
113
114	if (dst->index == info->tree_index && ds->index == info->sw_index &&
115	    ds->ops->port_bridge_leave)
116		ds->ops->port_bridge_leave(ds, info->port, info->br);
117
118	if ((dst->index != info->tree_index || ds->index != info->sw_index) &&
119	    ds->ops->crosschip_bridge_leave)
120		ds->ops->crosschip_bridge_leave(ds, info->tree_index,
121						info->sw_index, info->port,
122						info->br);
123
124	/* If the bridge was vlan_filtering, the bridge core doesn't trigger an
125	 * event for changing vlan_filtering setting upon slave ports leaving
126	 * it. That is a good thing, because that lets us handle it and also
127	 * handle the case where the switch's vlan_filtering setting is global
128	 * (not per port). When that happens, the correct moment to trigger the
129	 * vlan_filtering callback is only when the last port left this bridge.
130	 */
131	if (unset_vlan_filtering && ds->vlan_filtering_is_global) {
132		for (i = 0; i < ds->num_ports; i++) {
133			if (i == info->port)
134				continue;
135			if (dsa_to_port(ds, i)->bridge_dev == info->br) {
136				unset_vlan_filtering = false;
137				break;
138			}
139		}
140	}
141	if (unset_vlan_filtering) {
142		struct switchdev_trans trans;
143
144		trans.ph_prepare = true;
145		err = dsa_port_vlan_filtering(dsa_to_port(ds, info->port),
146					      false, &trans);
147		if (err && err != EOPNOTSUPP)
148			return err;
149
150		trans.ph_prepare = false;
151		err = dsa_port_vlan_filtering(dsa_to_port(ds, info->port),
152					      false, &trans);
153		if (err && err != EOPNOTSUPP)
154			return err;
155	}
156	return 0;
157}
158
159static int dsa_switch_fdb_add(struct dsa_switch *ds,
160			      struct dsa_notifier_fdb_info *info)
161{
162	int port = dsa_towards_port(ds, info->sw_index, info->port);
163
164	if (!ds->ops->port_fdb_add)
165		return -EOPNOTSUPP;
166
167	return ds->ops->port_fdb_add(ds, port, info->addr, info->vid);
168}
169
170static int dsa_switch_fdb_del(struct dsa_switch *ds,
171			      struct dsa_notifier_fdb_info *info)
172{
173	int port = dsa_towards_port(ds, info->sw_index, info->port);
174
175	if (!ds->ops->port_fdb_del)
176		return -EOPNOTSUPP;
177
178	return ds->ops->port_fdb_del(ds, port, info->addr, info->vid);
179}
180
181static bool dsa_switch_mdb_match(struct dsa_switch *ds, int port,
182				 struct dsa_notifier_mdb_info *info)
183{
184	if (ds->index == info->sw_index && port == info->port)
185		return true;
186
187	if (dsa_is_dsa_port(ds, port))
188		return true;
189
190	return false;
191}
192
193static int dsa_switch_mdb_prepare(struct dsa_switch *ds,
194				  struct dsa_notifier_mdb_info *info)
195{
196	int port, err;
197
198	if (!ds->ops->port_mdb_prepare || !ds->ops->port_mdb_add)
199		return -EOPNOTSUPP;
200
201	for (port = 0; port < ds->num_ports; port++) {
202		if (dsa_switch_mdb_match(ds, port, info)) {
203			err = ds->ops->port_mdb_prepare(ds, port, info->mdb);
204			if (err)
205				return err;
206		}
207	}
208
209	return 0;
210}
211
212static int dsa_switch_mdb_add(struct dsa_switch *ds,
213			      struct dsa_notifier_mdb_info *info)
214{
215	int port;
216
217	if (switchdev_trans_ph_prepare(info->trans))
218		return dsa_switch_mdb_prepare(ds, info);
219
220	if (!ds->ops->port_mdb_add)
221		return 0;
222
223	for (port = 0; port < ds->num_ports; port++)
224		if (dsa_switch_mdb_match(ds, port, info))
225			ds->ops->port_mdb_add(ds, port, info->mdb);
226
227	return 0;
228}
229
230static int dsa_switch_mdb_del(struct dsa_switch *ds,
231			      struct dsa_notifier_mdb_info *info)
232{
233	if (!ds->ops->port_mdb_del)
234		return -EOPNOTSUPP;
235
236	if (ds->index == info->sw_index)
237		return ds->ops->port_mdb_del(ds, info->port, info->mdb);
238
239	return 0;
240}
241
242static bool dsa_switch_vlan_match(struct dsa_switch *ds, int port,
243				  struct dsa_notifier_vlan_info *info)
244{
245	if (ds->index == info->sw_index && port == info->port)
246		return true;
247
248	if (dsa_is_dsa_port(ds, port))
249		return true;
250
251	return false;
252}
253
254static int dsa_switch_vlan_prepare(struct dsa_switch *ds,
255				   struct dsa_notifier_vlan_info *info)
256{
257	int port, err;
258
259	if (!ds->ops->port_vlan_prepare || !ds->ops->port_vlan_add)
260		return -EOPNOTSUPP;
261
262	for (port = 0; port < ds->num_ports; port++) {
263		if (dsa_switch_vlan_match(ds, port, info)) {
264			err = ds->ops->port_vlan_prepare(ds, port, info->vlan);
265			if (err)
266				return err;
267		}
268	}
269
270	return 0;
271}
272
273static int dsa_switch_vlan_add(struct dsa_switch *ds,
274			       struct dsa_notifier_vlan_info *info)
275{
276	int port;
277
278	if (switchdev_trans_ph_prepare(info->trans))
279		return dsa_switch_vlan_prepare(ds, info);
280
281	if (!ds->ops->port_vlan_add)
282		return 0;
283
284	for (port = 0; port < ds->num_ports; port++)
285		if (dsa_switch_vlan_match(ds, port, info))
286			ds->ops->port_vlan_add(ds, port, info->vlan);
287
288	return 0;
289}
290
291static int dsa_switch_vlan_del(struct dsa_switch *ds,
292			       struct dsa_notifier_vlan_info *info)
293{
294	if (!ds->ops->port_vlan_del)
295		return -EOPNOTSUPP;
296
297	if (ds->index == info->sw_index)
298		return ds->ops->port_vlan_del(ds, info->port, info->vlan);
299
300	/* Do not deprogram the DSA links as they may be used as conduit
301	 * for other VLAN members in the fabric.
302	 */
303	return 0;
304}
305
306static int dsa_switch_event(struct notifier_block *nb,
307			    unsigned long event, void *info)
308{
309	struct dsa_switch *ds = container_of(nb, struct dsa_switch, nb);
310	int err;
311
312	switch (event) {
313	case DSA_NOTIFIER_AGEING_TIME:
314		err = dsa_switch_ageing_time(ds, info);
315		break;
316	case DSA_NOTIFIER_BRIDGE_JOIN:
317		err = dsa_switch_bridge_join(ds, info);
318		break;
319	case DSA_NOTIFIER_BRIDGE_LEAVE:
320		err = dsa_switch_bridge_leave(ds, info);
321		break;
322	case DSA_NOTIFIER_FDB_ADD:
323		err = dsa_switch_fdb_add(ds, info);
324		break;
325	case DSA_NOTIFIER_FDB_DEL:
326		err = dsa_switch_fdb_del(ds, info);
327		break;
328	case DSA_NOTIFIER_MDB_ADD:
329		err = dsa_switch_mdb_add(ds, info);
330		break;
331	case DSA_NOTIFIER_MDB_DEL:
332		err = dsa_switch_mdb_del(ds, info);
333		break;
334	case DSA_NOTIFIER_VLAN_ADD:
335		err = dsa_switch_vlan_add(ds, info);
336		break;
337	case DSA_NOTIFIER_VLAN_DEL:
338		err = dsa_switch_vlan_del(ds, info);
339		break;
340	case DSA_NOTIFIER_MTU:
341		err = dsa_switch_mtu(ds, info);
342		break;
343	default:
344		err = -EOPNOTSUPP;
345		break;
346	}
347
348	/* Non-switchdev operations cannot be rolled back. If a DSA driver
349	 * returns an error during the chained call, switch chips may be in an
350	 * inconsistent state.
351	 */
352	if (err)
353		dev_dbg(ds->dev, "breaking chain for DSA event %lu (%d)\n",
354			event, err);
355
356	return notifier_from_errno(err);
357}
358
359int dsa_switch_register_notifier(struct dsa_switch *ds)
360{
361	ds->nb.notifier_call = dsa_switch_event;
362
363	return raw_notifier_chain_register(&ds->dst->nh, &ds->nb);
364}
365
366void dsa_switch_unregister_notifier(struct dsa_switch *ds)
367{
368	int err;
369
370	err = raw_notifier_chain_unregister(&ds->dst->nh, &ds->nb);
371	if (err)
372		dev_err(ds->dev, "failed to unregister notifier (%d)\n", err);
373}
374