xref: /linux/net/dsa/switch.c (revision 1958d5815c91353960df2198a74f5ca15785ed28)
12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2f515f192SVivien Didelot /*
3f515f192SVivien Didelot  * Handling of a single switch chip, part of a switch fabric
4f515f192SVivien Didelot  *
54333d619SVivien Didelot  * Copyright (c) 2017 Savoir-faire Linux Inc.
64333d619SVivien Didelot  *	Vivien Didelot <vivien.didelot@savoirfairelinux.com>
7f515f192SVivien Didelot  */
8f515f192SVivien Didelot 
9d371b7c9SVladimir Oltean #include <linux/if_bridge.h>
10f515f192SVivien Didelot #include <linux/netdevice.h>
11f515f192SVivien Didelot #include <linux/notifier.h>
12061f6a50SFlorian Fainelli #include <linux/if_vlan.h>
131faabf74SVivien Didelot #include <net/switchdev.h>
14ea5dd34bSVivien Didelot 
15ea5dd34bSVivien Didelot #include "dsa_priv.h"
16f515f192SVivien Didelot 
171faabf74SVivien Didelot static unsigned int dsa_switch_fastest_ageing_time(struct dsa_switch *ds,
181faabf74SVivien Didelot 						   unsigned int ageing_time)
191faabf74SVivien Didelot {
201faabf74SVivien Didelot 	int i;
211faabf74SVivien Didelot 
221faabf74SVivien Didelot 	for (i = 0; i < ds->num_ports; ++i) {
2368bb8ea8SVivien Didelot 		struct dsa_port *dp = dsa_to_port(ds, i);
241faabf74SVivien Didelot 
251faabf74SVivien Didelot 		if (dp->ageing_time && dp->ageing_time < ageing_time)
261faabf74SVivien Didelot 			ageing_time = dp->ageing_time;
271faabf74SVivien Didelot 	}
281faabf74SVivien Didelot 
291faabf74SVivien Didelot 	return ageing_time;
301faabf74SVivien Didelot }
311faabf74SVivien Didelot 
321faabf74SVivien Didelot static int dsa_switch_ageing_time(struct dsa_switch *ds,
331faabf74SVivien Didelot 				  struct dsa_notifier_ageing_time_info *info)
341faabf74SVivien Didelot {
351faabf74SVivien Didelot 	unsigned int ageing_time = info->ageing_time;
361faabf74SVivien Didelot 
371faabf74SVivien Didelot 	if (ds->ageing_time_min && ageing_time < ds->ageing_time_min)
381faabf74SVivien Didelot 		return -ERANGE;
3977b61365SVladimir Oltean 
401faabf74SVivien Didelot 	if (ds->ageing_time_max && ageing_time > ds->ageing_time_max)
411faabf74SVivien Didelot 		return -ERANGE;
421faabf74SVivien Didelot 
431faabf74SVivien Didelot 	/* Program the fastest ageing time in case of multiple bridges */
441faabf74SVivien Didelot 	ageing_time = dsa_switch_fastest_ageing_time(ds, ageing_time);
451faabf74SVivien Didelot 
461faabf74SVivien Didelot 	if (ds->ops->set_ageing_time)
471faabf74SVivien Didelot 		return ds->ops->set_ageing_time(ds, ageing_time);
481faabf74SVivien Didelot 
491faabf74SVivien Didelot 	return 0;
501faabf74SVivien Didelot }
511faabf74SVivien Didelot 
52bfcb8132SVladimir Oltean static bool dsa_switch_mtu_match(struct dsa_switch *ds, int port,
53bfcb8132SVladimir Oltean 				 struct dsa_notifier_mtu_info *info)
54bfcb8132SVladimir Oltean {
55bfcb8132SVladimir Oltean 	if (ds->index == info->sw_index)
56bfcb8132SVladimir Oltean 		return (port == info->port) || dsa_is_dsa_port(ds, port);
57bfcb8132SVladimir Oltean 
58bfcb8132SVladimir Oltean 	if (!info->propagate_upstream)
59bfcb8132SVladimir Oltean 		return false;
60bfcb8132SVladimir Oltean 
61bfcb8132SVladimir Oltean 	if (dsa_is_dsa_port(ds, port) || dsa_is_cpu_port(ds, port))
62bfcb8132SVladimir Oltean 		return true;
63bfcb8132SVladimir Oltean 
64bfcb8132SVladimir Oltean 	return false;
65bfcb8132SVladimir Oltean }
66bfcb8132SVladimir Oltean 
67bfcb8132SVladimir Oltean static int dsa_switch_mtu(struct dsa_switch *ds,
68bfcb8132SVladimir Oltean 			  struct dsa_notifier_mtu_info *info)
69bfcb8132SVladimir Oltean {
70bfcb8132SVladimir Oltean 	int port, ret;
71bfcb8132SVladimir Oltean 
72bfcb8132SVladimir Oltean 	if (!ds->ops->port_change_mtu)
73bfcb8132SVladimir Oltean 		return -EOPNOTSUPP;
74bfcb8132SVladimir Oltean 
75bfcb8132SVladimir Oltean 	for (port = 0; port < ds->num_ports; port++) {
76bfcb8132SVladimir Oltean 		if (dsa_switch_mtu_match(ds, port, info)) {
77bfcb8132SVladimir Oltean 			ret = ds->ops->port_change_mtu(ds, port, info->mtu);
78bfcb8132SVladimir Oltean 			if (ret)
79bfcb8132SVladimir Oltean 				return ret;
80bfcb8132SVladimir Oltean 		}
81bfcb8132SVladimir Oltean 	}
82bfcb8132SVladimir Oltean 
83bfcb8132SVladimir Oltean 	return 0;
84bfcb8132SVladimir Oltean }
85bfcb8132SVladimir Oltean 
8604d3a4c6SVivien Didelot static int dsa_switch_bridge_join(struct dsa_switch *ds,
8704d3a4c6SVivien Didelot 				  struct dsa_notifier_bridge_info *info)
8804d3a4c6SVivien Didelot {
89f66a6a69SVladimir Oltean 	struct dsa_switch_tree *dst = ds->dst;
90f66a6a69SVladimir Oltean 
91f66a6a69SVladimir Oltean 	if (dst->index == info->tree_index && ds->index == info->sw_index &&
92f66a6a69SVladimir Oltean 	    ds->ops->port_bridge_join)
9304d3a4c6SVivien Didelot 		return ds->ops->port_bridge_join(ds, info->port, info->br);
9404d3a4c6SVivien Didelot 
95f66a6a69SVladimir Oltean 	if ((dst->index != info->tree_index || ds->index != info->sw_index) &&
96f66a6a69SVladimir Oltean 	    ds->ops->crosschip_bridge_join)
97f66a6a69SVladimir Oltean 		return ds->ops->crosschip_bridge_join(ds, info->tree_index,
98f66a6a69SVladimir Oltean 						      info->sw_index,
9940ef2c93SVivien Didelot 						      info->port, info->br);
10004d3a4c6SVivien Didelot 
10104d3a4c6SVivien Didelot 	return 0;
10204d3a4c6SVivien Didelot }
10304d3a4c6SVivien Didelot 
10404d3a4c6SVivien Didelot static int dsa_switch_bridge_leave(struct dsa_switch *ds,
10504d3a4c6SVivien Didelot 				   struct dsa_notifier_bridge_info *info)
10604d3a4c6SVivien Didelot {
107d371b7c9SVladimir Oltean 	bool unset_vlan_filtering = br_vlan_enabled(info->br);
108f66a6a69SVladimir Oltean 	struct dsa_switch_tree *dst = ds->dst;
109d371b7c9SVladimir Oltean 	int err, i;
110d371b7c9SVladimir Oltean 
111f66a6a69SVladimir Oltean 	if (dst->index == info->tree_index && ds->index == info->sw_index &&
112f66a6a69SVladimir Oltean 	    ds->ops->port_bridge_join)
11304d3a4c6SVivien Didelot 		ds->ops->port_bridge_leave(ds, info->port, info->br);
11404d3a4c6SVivien Didelot 
115f66a6a69SVladimir Oltean 	if ((dst->index != info->tree_index || ds->index != info->sw_index) &&
116f66a6a69SVladimir Oltean 	    ds->ops->crosschip_bridge_join)
117f66a6a69SVladimir Oltean 		ds->ops->crosschip_bridge_leave(ds, info->tree_index,
118f66a6a69SVladimir Oltean 						info->sw_index, info->port,
11940ef2c93SVivien Didelot 						info->br);
12004d3a4c6SVivien Didelot 
121d371b7c9SVladimir Oltean 	/* If the bridge was vlan_filtering, the bridge core doesn't trigger an
122d371b7c9SVladimir Oltean 	 * event for changing vlan_filtering setting upon slave ports leaving
123d371b7c9SVladimir Oltean 	 * it. That is a good thing, because that lets us handle it and also
124d371b7c9SVladimir Oltean 	 * handle the case where the switch's vlan_filtering setting is global
125d371b7c9SVladimir Oltean 	 * (not per port). When that happens, the correct moment to trigger the
126d371b7c9SVladimir Oltean 	 * vlan_filtering callback is only when the last port left this bridge.
127d371b7c9SVladimir Oltean 	 */
128d371b7c9SVladimir Oltean 	if (unset_vlan_filtering && ds->vlan_filtering_is_global) {
129d371b7c9SVladimir Oltean 		for (i = 0; i < ds->num_ports; i++) {
130d371b7c9SVladimir Oltean 			if (i == info->port)
131d371b7c9SVladimir Oltean 				continue;
132d371b7c9SVladimir Oltean 			if (dsa_to_port(ds, i)->bridge_dev == info->br) {
133d371b7c9SVladimir Oltean 				unset_vlan_filtering = false;
134d371b7c9SVladimir Oltean 				break;
135d371b7c9SVladimir Oltean 			}
136d371b7c9SVladimir Oltean 		}
137d371b7c9SVladimir Oltean 	}
138d371b7c9SVladimir Oltean 	if (unset_vlan_filtering) {
1392e554a7aSVladimir Oltean 		err = dsa_port_vlan_filtering(dsa_to_port(ds, info->port),
140bae33f2bSVladimir Oltean 					      false);
141d371b7c9SVladimir Oltean 		if (err && err != EOPNOTSUPP)
142d371b7c9SVladimir Oltean 			return err;
143d371b7c9SVladimir Oltean 	}
14404d3a4c6SVivien Didelot 	return 0;
14504d3a4c6SVivien Didelot }
14604d3a4c6SVivien Didelot 
147685fb6a4SVivien Didelot static int dsa_switch_fdb_add(struct dsa_switch *ds,
148685fb6a4SVivien Didelot 			      struct dsa_notifier_fdb_info *info)
149685fb6a4SVivien Didelot {
1503169241fSVivien Didelot 	int port = dsa_towards_port(ds, info->sw_index, info->port);
151685fb6a4SVivien Didelot 
1521b6dd556SArkadi Sharshevsky 	if (!ds->ops->port_fdb_add)
153685fb6a4SVivien Didelot 		return -EOPNOTSUPP;
154685fb6a4SVivien Didelot 
1553169241fSVivien Didelot 	return ds->ops->port_fdb_add(ds, port, info->addr, info->vid);
156685fb6a4SVivien Didelot }
157685fb6a4SVivien Didelot 
158685fb6a4SVivien Didelot static int dsa_switch_fdb_del(struct dsa_switch *ds,
159685fb6a4SVivien Didelot 			      struct dsa_notifier_fdb_info *info)
160685fb6a4SVivien Didelot {
1613169241fSVivien Didelot 	int port = dsa_towards_port(ds, info->sw_index, info->port);
162685fb6a4SVivien Didelot 
163685fb6a4SVivien Didelot 	if (!ds->ops->port_fdb_del)
164685fb6a4SVivien Didelot 		return -EOPNOTSUPP;
165685fb6a4SVivien Didelot 
1663169241fSVivien Didelot 	return ds->ops->port_fdb_del(ds, port, info->addr, info->vid);
167685fb6a4SVivien Didelot }
168685fb6a4SVivien Didelot 
169e65d45ccSVivien Didelot static bool dsa_switch_mdb_match(struct dsa_switch *ds, int port,
170e65d45ccSVivien Didelot 				 struct dsa_notifier_mdb_info *info)
171e65d45ccSVivien Didelot {
172e65d45ccSVivien Didelot 	if (ds->index == info->sw_index && port == info->port)
173e65d45ccSVivien Didelot 		return true;
174e65d45ccSVivien Didelot 
175e65d45ccSVivien Didelot 	if (dsa_is_dsa_port(ds, port))
176e65d45ccSVivien Didelot 		return true;
177e65d45ccSVivien Didelot 
178e65d45ccSVivien Didelot 	return false;
179e65d45ccSVivien Didelot }
180e65d45ccSVivien Didelot 
181ffb68fc5SVladimir Oltean static int dsa_switch_mdb_add(struct dsa_switch *ds,
182e65d45ccSVivien Didelot 			      struct dsa_notifier_mdb_info *info)
183e6db98dbSVivien Didelot {
184a52b2da7SVladimir Oltean 	int err = 0;
185a52b2da7SVladimir Oltean 	int port;
186e6db98dbSVivien Didelot 
187a52b2da7SVladimir Oltean 	if (!ds->ops->port_mdb_add)
188e6db98dbSVivien Didelot 		return -EOPNOTSUPP;
189e6db98dbSVivien Didelot 
190e65d45ccSVivien Didelot 	for (port = 0; port < ds->num_ports; port++) {
191e65d45ccSVivien Didelot 		if (dsa_switch_mdb_match(ds, port, info)) {
192a52b2da7SVladimir Oltean 			err = ds->ops->port_mdb_add(ds, port, info->mdb);
193e6db98dbSVivien Didelot 			if (err)
194a52b2da7SVladimir Oltean 				break;
195a52b2da7SVladimir Oltean 		}
196a52b2da7SVladimir Oltean 	}
197a52b2da7SVladimir Oltean 
198e6db98dbSVivien Didelot 	return err;
199e6db98dbSVivien Didelot }
2008ae5bcdcSVivien Didelot 
2018ae5bcdcSVivien Didelot static int dsa_switch_mdb_del(struct dsa_switch *ds,
2028ae5bcdcSVivien Didelot 			      struct dsa_notifier_mdb_info *info)
2038ae5bcdcSVivien Didelot {
2048ae5bcdcSVivien Didelot 	if (!ds->ops->port_mdb_del)
2058ae5bcdcSVivien Didelot 		return -EOPNOTSUPP;
2068ae5bcdcSVivien Didelot 
207a1a6b7eaSVivien Didelot 	if (ds->index == info->sw_index)
208e65d45ccSVivien Didelot 		return ds->ops->port_mdb_del(ds, info->port, info->mdb);
209a1a6b7eaSVivien Didelot 
210a1a6b7eaSVivien Didelot 	return 0;
2118ae5bcdcSVivien Didelot }
2128ae5bcdcSVivien Didelot 
213e65d45ccSVivien Didelot static bool dsa_switch_vlan_match(struct dsa_switch *ds, int port,
214e65d45ccSVivien Didelot 				  struct dsa_notifier_vlan_info *info)
215e65d45ccSVivien Didelot {
216e65d45ccSVivien Didelot 	if (ds->index == info->sw_index && port == info->port)
217e65d45ccSVivien Didelot 		return true;
218e65d45ccSVivien Didelot 
2197e1741b4SVivien Didelot 	if (dsa_is_dsa_port(ds, port))
220e65d45ccSVivien Didelot 		return true;
221e65d45ccSVivien Didelot 
222e65d45ccSVivien Didelot 	return false;
223e65d45ccSVivien Didelot }
224e65d45ccSVivien Didelot 
225ffb68fc5SVladimir Oltean static int dsa_switch_vlan_add(struct dsa_switch *ds,
226e65d45ccSVivien Didelot 			       struct dsa_notifier_vlan_info *info)
2279c428c59SVivien Didelot {
2289c428c59SVivien Didelot 	int port, err;
2299c428c59SVivien Didelot 
230*1958d581SVladimir Oltean 	if (!ds->ops->port_vlan_add)
2319c428c59SVivien Didelot 		return -EOPNOTSUPP;
2329c428c59SVivien Didelot 
233e65d45ccSVivien Didelot 	for (port = 0; port < ds->num_ports; port++) {
234e65d45ccSVivien Didelot 		if (dsa_switch_vlan_match(ds, port, info)) {
235*1958d581SVladimir Oltean 			err = ds->ops->port_vlan_add(ds, port, info->vlan);
2369c428c59SVivien Didelot 			if (err)
2379c428c59SVivien Didelot 				return err;
2389c428c59SVivien Didelot 		}
239e65d45ccSVivien Didelot 	}
2409c428c59SVivien Didelot 
241d0c627b8SVivien Didelot 	return 0;
242d0c627b8SVivien Didelot }
243d0c627b8SVivien Didelot 
244d0c627b8SVivien Didelot static int dsa_switch_vlan_del(struct dsa_switch *ds,
245d0c627b8SVivien Didelot 			       struct dsa_notifier_vlan_info *info)
246d0c627b8SVivien Didelot {
247d0c627b8SVivien Didelot 	if (!ds->ops->port_vlan_del)
248d0c627b8SVivien Didelot 		return -EOPNOTSUPP;
249d0c627b8SVivien Didelot 
2501ca4aa9cSVivien Didelot 	if (ds->index == info->sw_index)
251e65d45ccSVivien Didelot 		return ds->ops->port_vlan_del(ds, info->port, info->vlan);
2521ca4aa9cSVivien Didelot 
2537e1741b4SVivien Didelot 	/* Do not deprogram the DSA links as they may be used as conduit
2547e1741b4SVivien Didelot 	 * for other VLAN members in the fabric.
2557e1741b4SVivien Didelot 	 */
2561ca4aa9cSVivien Didelot 	return 0;
257d0c627b8SVivien Didelot }
258d0c627b8SVivien Didelot 
259f515f192SVivien Didelot static int dsa_switch_event(struct notifier_block *nb,
260f515f192SVivien Didelot 			    unsigned long event, void *info)
261f515f192SVivien Didelot {
262f515f192SVivien Didelot 	struct dsa_switch *ds = container_of(nb, struct dsa_switch, nb);
263f515f192SVivien Didelot 	int err;
264f515f192SVivien Didelot 
265f515f192SVivien Didelot 	switch (event) {
2661faabf74SVivien Didelot 	case DSA_NOTIFIER_AGEING_TIME:
2671faabf74SVivien Didelot 		err = dsa_switch_ageing_time(ds, info);
2681faabf74SVivien Didelot 		break;
26904d3a4c6SVivien Didelot 	case DSA_NOTIFIER_BRIDGE_JOIN:
27004d3a4c6SVivien Didelot 		err = dsa_switch_bridge_join(ds, info);
27104d3a4c6SVivien Didelot 		break;
27204d3a4c6SVivien Didelot 	case DSA_NOTIFIER_BRIDGE_LEAVE:
27304d3a4c6SVivien Didelot 		err = dsa_switch_bridge_leave(ds, info);
27404d3a4c6SVivien Didelot 		break;
275685fb6a4SVivien Didelot 	case DSA_NOTIFIER_FDB_ADD:
276685fb6a4SVivien Didelot 		err = dsa_switch_fdb_add(ds, info);
277685fb6a4SVivien Didelot 		break;
278685fb6a4SVivien Didelot 	case DSA_NOTIFIER_FDB_DEL:
279685fb6a4SVivien Didelot 		err = dsa_switch_fdb_del(ds, info);
280685fb6a4SVivien Didelot 		break;
2818ae5bcdcSVivien Didelot 	case DSA_NOTIFIER_MDB_ADD:
2828ae5bcdcSVivien Didelot 		err = dsa_switch_mdb_add(ds, info);
2838ae5bcdcSVivien Didelot 		break;
2848ae5bcdcSVivien Didelot 	case DSA_NOTIFIER_MDB_DEL:
2858ae5bcdcSVivien Didelot 		err = dsa_switch_mdb_del(ds, info);
2868ae5bcdcSVivien Didelot 		break;
287d0c627b8SVivien Didelot 	case DSA_NOTIFIER_VLAN_ADD:
288d0c627b8SVivien Didelot 		err = dsa_switch_vlan_add(ds, info);
289d0c627b8SVivien Didelot 		break;
290d0c627b8SVivien Didelot 	case DSA_NOTIFIER_VLAN_DEL:
291d0c627b8SVivien Didelot 		err = dsa_switch_vlan_del(ds, info);
292d0c627b8SVivien Didelot 		break;
293bfcb8132SVladimir Oltean 	case DSA_NOTIFIER_MTU:
294bfcb8132SVladimir Oltean 		err = dsa_switch_mtu(ds, info);
295bfcb8132SVladimir Oltean 		break;
296f515f192SVivien Didelot 	default:
297f515f192SVivien Didelot 		err = -EOPNOTSUPP;
298f515f192SVivien Didelot 		break;
299f515f192SVivien Didelot 	}
300f515f192SVivien Didelot 
301f515f192SVivien Didelot 	/* Non-switchdev operations cannot be rolled back. If a DSA driver
302f515f192SVivien Didelot 	 * returns an error during the chained call, switch chips may be in an
303f515f192SVivien Didelot 	 * inconsistent state.
304f515f192SVivien Didelot 	 */
305f515f192SVivien Didelot 	if (err)
306f515f192SVivien Didelot 		dev_dbg(ds->dev, "breaking chain for DSA event %lu (%d)\n",
307f515f192SVivien Didelot 			event, err);
308f515f192SVivien Didelot 
309f515f192SVivien Didelot 	return notifier_from_errno(err);
310f515f192SVivien Didelot }
311f515f192SVivien Didelot 
312f515f192SVivien Didelot int dsa_switch_register_notifier(struct dsa_switch *ds)
313f515f192SVivien Didelot {
314f515f192SVivien Didelot 	ds->nb.notifier_call = dsa_switch_event;
315f515f192SVivien Didelot 
316f515f192SVivien Didelot 	return raw_notifier_chain_register(&ds->dst->nh, &ds->nb);
317f515f192SVivien Didelot }
318f515f192SVivien Didelot 
319f515f192SVivien Didelot void dsa_switch_unregister_notifier(struct dsa_switch *ds)
320f515f192SVivien Didelot {
321f515f192SVivien Didelot 	int err;
322f515f192SVivien Didelot 
323f515f192SVivien Didelot 	err = raw_notifier_chain_unregister(&ds->dst->nh, &ds->nb);
324f515f192SVivien Didelot 	if (err)
325f515f192SVivien Didelot 		dev_err(ds->dev, "failed to unregister notifier (%d)\n", err);
326f515f192SVivien Didelot }
327