xref: /linux/net/dsa/switch.c (revision 1ca4aa9cd4cc075e3fddbba80fd2ed2f479bfb22)
1f515f192SVivien Didelot /*
2f515f192SVivien Didelot  * Handling of a single switch chip, part of a switch fabric
3f515f192SVivien Didelot  *
44333d619SVivien Didelot  * Copyright (c) 2017 Savoir-faire Linux Inc.
54333d619SVivien Didelot  *	Vivien Didelot <vivien.didelot@savoirfairelinux.com>
6f515f192SVivien Didelot  *
7f515f192SVivien Didelot  * This program is free software; you can redistribute it and/or modify
8f515f192SVivien Didelot  * it under the terms of the GNU General Public License as published by
9f515f192SVivien Didelot  * the Free Software Foundation; either version 2 of the License, or
10f515f192SVivien Didelot  * (at your option) any later version.
11f515f192SVivien Didelot  */
12f515f192SVivien Didelot 
13f515f192SVivien Didelot #include <linux/netdevice.h>
14f515f192SVivien Didelot #include <linux/notifier.h>
151faabf74SVivien Didelot #include <net/switchdev.h>
16ea5dd34bSVivien Didelot 
17ea5dd34bSVivien Didelot #include "dsa_priv.h"
18f515f192SVivien Didelot 
191faabf74SVivien Didelot static unsigned int dsa_switch_fastest_ageing_time(struct dsa_switch *ds,
201faabf74SVivien Didelot 						   unsigned int ageing_time)
211faabf74SVivien Didelot {
221faabf74SVivien Didelot 	int i;
231faabf74SVivien Didelot 
241faabf74SVivien Didelot 	for (i = 0; i < ds->num_ports; ++i) {
251faabf74SVivien Didelot 		struct dsa_port *dp = &ds->ports[i];
261faabf74SVivien Didelot 
271faabf74SVivien Didelot 		if (dp->ageing_time && dp->ageing_time < ageing_time)
281faabf74SVivien Didelot 			ageing_time = dp->ageing_time;
291faabf74SVivien Didelot 	}
301faabf74SVivien Didelot 
311faabf74SVivien Didelot 	return ageing_time;
321faabf74SVivien Didelot }
331faabf74SVivien Didelot 
341faabf74SVivien Didelot static int dsa_switch_ageing_time(struct dsa_switch *ds,
351faabf74SVivien Didelot 				  struct dsa_notifier_ageing_time_info *info)
361faabf74SVivien Didelot {
371faabf74SVivien Didelot 	unsigned int ageing_time = info->ageing_time;
381faabf74SVivien Didelot 	struct switchdev_trans *trans = info->trans;
391faabf74SVivien Didelot 
401faabf74SVivien Didelot 	if (switchdev_trans_ph_prepare(trans)) {
411faabf74SVivien Didelot 		if (ds->ageing_time_min && ageing_time < ds->ageing_time_min)
421faabf74SVivien Didelot 			return -ERANGE;
431faabf74SVivien Didelot 		if (ds->ageing_time_max && ageing_time > ds->ageing_time_max)
441faabf74SVivien Didelot 			return -ERANGE;
451faabf74SVivien Didelot 		return 0;
461faabf74SVivien Didelot 	}
471faabf74SVivien Didelot 
481faabf74SVivien Didelot 	/* Program the fastest ageing time in case of multiple bridges */
491faabf74SVivien Didelot 	ageing_time = dsa_switch_fastest_ageing_time(ds, ageing_time);
501faabf74SVivien Didelot 
511faabf74SVivien Didelot 	if (ds->ops->set_ageing_time)
521faabf74SVivien Didelot 		return ds->ops->set_ageing_time(ds, ageing_time);
531faabf74SVivien Didelot 
541faabf74SVivien Didelot 	return 0;
551faabf74SVivien Didelot }
561faabf74SVivien Didelot 
5704d3a4c6SVivien Didelot static int dsa_switch_bridge_join(struct dsa_switch *ds,
5804d3a4c6SVivien Didelot 				  struct dsa_notifier_bridge_info *info)
5904d3a4c6SVivien Didelot {
6004d3a4c6SVivien Didelot 	if (ds->index == info->sw_index && ds->ops->port_bridge_join)
6104d3a4c6SVivien Didelot 		return ds->ops->port_bridge_join(ds, info->port, info->br);
6204d3a4c6SVivien Didelot 
6340ef2c93SVivien Didelot 	if (ds->index != info->sw_index && ds->ops->crosschip_bridge_join)
6440ef2c93SVivien Didelot 		return ds->ops->crosschip_bridge_join(ds, info->sw_index,
6540ef2c93SVivien Didelot 						      info->port, info->br);
6604d3a4c6SVivien Didelot 
6704d3a4c6SVivien Didelot 	return 0;
6804d3a4c6SVivien Didelot }
6904d3a4c6SVivien Didelot 
7004d3a4c6SVivien Didelot static int dsa_switch_bridge_leave(struct dsa_switch *ds,
7104d3a4c6SVivien Didelot 				   struct dsa_notifier_bridge_info *info)
7204d3a4c6SVivien Didelot {
7304d3a4c6SVivien Didelot 	if (ds->index == info->sw_index && ds->ops->port_bridge_leave)
7404d3a4c6SVivien Didelot 		ds->ops->port_bridge_leave(ds, info->port, info->br);
7504d3a4c6SVivien Didelot 
7640ef2c93SVivien Didelot 	if (ds->index != info->sw_index && ds->ops->crosschip_bridge_leave)
7740ef2c93SVivien Didelot 		ds->ops->crosschip_bridge_leave(ds, info->sw_index, info->port,
7840ef2c93SVivien Didelot 						info->br);
7904d3a4c6SVivien Didelot 
8004d3a4c6SVivien Didelot 	return 0;
8104d3a4c6SVivien Didelot }
8204d3a4c6SVivien Didelot 
83685fb6a4SVivien Didelot static int dsa_switch_fdb_add(struct dsa_switch *ds,
84685fb6a4SVivien Didelot 			      struct dsa_notifier_fdb_info *info)
85685fb6a4SVivien Didelot {
86685fb6a4SVivien Didelot 	const struct switchdev_obj_port_fdb *fdb = info->fdb;
87685fb6a4SVivien Didelot 	struct switchdev_trans *trans = info->trans;
88685fb6a4SVivien Didelot 
89685fb6a4SVivien Didelot 	/* Do not care yet about other switch chips of the fabric */
90685fb6a4SVivien Didelot 	if (ds->index != info->sw_index)
91685fb6a4SVivien Didelot 		return 0;
92685fb6a4SVivien Didelot 
93685fb6a4SVivien Didelot 	if (switchdev_trans_ph_prepare(trans)) {
94685fb6a4SVivien Didelot 		if (!ds->ops->port_fdb_prepare || !ds->ops->port_fdb_add)
95685fb6a4SVivien Didelot 			return -EOPNOTSUPP;
96685fb6a4SVivien Didelot 
97685fb6a4SVivien Didelot 		return ds->ops->port_fdb_prepare(ds, info->port, fdb, trans);
98685fb6a4SVivien Didelot 	}
99685fb6a4SVivien Didelot 
100685fb6a4SVivien Didelot 	ds->ops->port_fdb_add(ds, info->port, fdb, trans);
101685fb6a4SVivien Didelot 
102685fb6a4SVivien Didelot 	return 0;
103685fb6a4SVivien Didelot }
104685fb6a4SVivien Didelot 
105685fb6a4SVivien Didelot static int dsa_switch_fdb_del(struct dsa_switch *ds,
106685fb6a4SVivien Didelot 			      struct dsa_notifier_fdb_info *info)
107685fb6a4SVivien Didelot {
108685fb6a4SVivien Didelot 	const struct switchdev_obj_port_fdb *fdb = info->fdb;
109685fb6a4SVivien Didelot 
110685fb6a4SVivien Didelot 	/* Do not care yet about other switch chips of the fabric */
111685fb6a4SVivien Didelot 	if (ds->index != info->sw_index)
112685fb6a4SVivien Didelot 		return 0;
113685fb6a4SVivien Didelot 
114685fb6a4SVivien Didelot 	if (!ds->ops->port_fdb_del)
115685fb6a4SVivien Didelot 		return -EOPNOTSUPP;
116685fb6a4SVivien Didelot 
117685fb6a4SVivien Didelot 	return ds->ops->port_fdb_del(ds, info->port, fdb);
118685fb6a4SVivien Didelot }
119685fb6a4SVivien Didelot 
1208ae5bcdcSVivien Didelot static int dsa_switch_mdb_add(struct dsa_switch *ds,
1218ae5bcdcSVivien Didelot 			      struct dsa_notifier_mdb_info *info)
1228ae5bcdcSVivien Didelot {
1238ae5bcdcSVivien Didelot 	const struct switchdev_obj_port_mdb *mdb = info->mdb;
1248ae5bcdcSVivien Didelot 	struct switchdev_trans *trans = info->trans;
1258ae5bcdcSVivien Didelot 
1268ae5bcdcSVivien Didelot 	/* Do not care yet about other switch chips of the fabric */
1278ae5bcdcSVivien Didelot 	if (ds->index != info->sw_index)
1288ae5bcdcSVivien Didelot 		return 0;
1298ae5bcdcSVivien Didelot 
1308ae5bcdcSVivien Didelot 	if (switchdev_trans_ph_prepare(trans)) {
1318ae5bcdcSVivien Didelot 		if (!ds->ops->port_mdb_prepare || !ds->ops->port_mdb_add)
1328ae5bcdcSVivien Didelot 			return -EOPNOTSUPP;
1338ae5bcdcSVivien Didelot 
1348ae5bcdcSVivien Didelot 		return ds->ops->port_mdb_prepare(ds, info->port, mdb, trans);
1358ae5bcdcSVivien Didelot 	}
1368ae5bcdcSVivien Didelot 
1378ae5bcdcSVivien Didelot 	ds->ops->port_mdb_add(ds, info->port, mdb, trans);
1388ae5bcdcSVivien Didelot 
1398ae5bcdcSVivien Didelot 	return 0;
1408ae5bcdcSVivien Didelot }
1418ae5bcdcSVivien Didelot 
1428ae5bcdcSVivien Didelot static int dsa_switch_mdb_del(struct dsa_switch *ds,
1438ae5bcdcSVivien Didelot 			      struct dsa_notifier_mdb_info *info)
1448ae5bcdcSVivien Didelot {
1458ae5bcdcSVivien Didelot 	const struct switchdev_obj_port_mdb *mdb = info->mdb;
1468ae5bcdcSVivien Didelot 
1478ae5bcdcSVivien Didelot 	/* Do not care yet about other switch chips of the fabric */
1488ae5bcdcSVivien Didelot 	if (ds->index != info->sw_index)
1498ae5bcdcSVivien Didelot 		return 0;
1508ae5bcdcSVivien Didelot 
1518ae5bcdcSVivien Didelot 	if (!ds->ops->port_mdb_del)
1528ae5bcdcSVivien Didelot 		return -EOPNOTSUPP;
1538ae5bcdcSVivien Didelot 
1548ae5bcdcSVivien Didelot 	return ds->ops->port_mdb_del(ds, info->port, mdb);
1558ae5bcdcSVivien Didelot }
1568ae5bcdcSVivien Didelot 
157d0c627b8SVivien Didelot static int dsa_switch_vlan_add(struct dsa_switch *ds,
158d0c627b8SVivien Didelot 			       struct dsa_notifier_vlan_info *info)
159d0c627b8SVivien Didelot {
160d0c627b8SVivien Didelot 	const struct switchdev_obj_port_vlan *vlan = info->vlan;
161d0c627b8SVivien Didelot 	struct switchdev_trans *trans = info->trans;
162*1ca4aa9cSVivien Didelot 	DECLARE_BITMAP(members, ds->num_ports);
163*1ca4aa9cSVivien Didelot 	int port, err;
164d0c627b8SVivien Didelot 
165*1ca4aa9cSVivien Didelot 	/* Build a mask of VLAN members */
166*1ca4aa9cSVivien Didelot 	bitmap_zero(members, ds->num_ports);
167*1ca4aa9cSVivien Didelot 	if (ds->index == info->sw_index)
168*1ca4aa9cSVivien Didelot 		set_bit(info->port, members);
169d0c627b8SVivien Didelot 
170d0c627b8SVivien Didelot 	if (switchdev_trans_ph_prepare(trans)) {
171d0c627b8SVivien Didelot 		if (!ds->ops->port_vlan_prepare || !ds->ops->port_vlan_add)
172d0c627b8SVivien Didelot 			return -EOPNOTSUPP;
173d0c627b8SVivien Didelot 
174*1ca4aa9cSVivien Didelot 		for_each_set_bit(port, members, ds->num_ports) {
175*1ca4aa9cSVivien Didelot 			err = ds->ops->port_vlan_prepare(ds, port, vlan, trans);
176*1ca4aa9cSVivien Didelot 			if (err)
177*1ca4aa9cSVivien Didelot 				return err;
178*1ca4aa9cSVivien Didelot 		}
179d0c627b8SVivien Didelot 	}
180d0c627b8SVivien Didelot 
181*1ca4aa9cSVivien Didelot 	for_each_set_bit(port, members, ds->num_ports)
182*1ca4aa9cSVivien Didelot 		ds->ops->port_vlan_add(ds, port, vlan, trans);
183d0c627b8SVivien Didelot 
184d0c627b8SVivien Didelot 	return 0;
185d0c627b8SVivien Didelot }
186d0c627b8SVivien Didelot 
187d0c627b8SVivien Didelot static int dsa_switch_vlan_del(struct dsa_switch *ds,
188d0c627b8SVivien Didelot 			       struct dsa_notifier_vlan_info *info)
189d0c627b8SVivien Didelot {
190d0c627b8SVivien Didelot 	const struct switchdev_obj_port_vlan *vlan = info->vlan;
191d0c627b8SVivien Didelot 
192d0c627b8SVivien Didelot 	if (!ds->ops->port_vlan_del)
193d0c627b8SVivien Didelot 		return -EOPNOTSUPP;
194d0c627b8SVivien Didelot 
195*1ca4aa9cSVivien Didelot 	if (ds->index == info->sw_index)
196d0c627b8SVivien Didelot 		return ds->ops->port_vlan_del(ds, info->port, vlan);
197*1ca4aa9cSVivien Didelot 
198*1ca4aa9cSVivien Didelot 	return 0;
199d0c627b8SVivien Didelot }
200d0c627b8SVivien Didelot 
201f515f192SVivien Didelot static int dsa_switch_event(struct notifier_block *nb,
202f515f192SVivien Didelot 			    unsigned long event, void *info)
203f515f192SVivien Didelot {
204f515f192SVivien Didelot 	struct dsa_switch *ds = container_of(nb, struct dsa_switch, nb);
205f515f192SVivien Didelot 	int err;
206f515f192SVivien Didelot 
207f515f192SVivien Didelot 	switch (event) {
2081faabf74SVivien Didelot 	case DSA_NOTIFIER_AGEING_TIME:
2091faabf74SVivien Didelot 		err = dsa_switch_ageing_time(ds, info);
2101faabf74SVivien Didelot 		break;
21104d3a4c6SVivien Didelot 	case DSA_NOTIFIER_BRIDGE_JOIN:
21204d3a4c6SVivien Didelot 		err = dsa_switch_bridge_join(ds, info);
21304d3a4c6SVivien Didelot 		break;
21404d3a4c6SVivien Didelot 	case DSA_NOTIFIER_BRIDGE_LEAVE:
21504d3a4c6SVivien Didelot 		err = dsa_switch_bridge_leave(ds, info);
21604d3a4c6SVivien Didelot 		break;
217685fb6a4SVivien Didelot 	case DSA_NOTIFIER_FDB_ADD:
218685fb6a4SVivien Didelot 		err = dsa_switch_fdb_add(ds, info);
219685fb6a4SVivien Didelot 		break;
220685fb6a4SVivien Didelot 	case DSA_NOTIFIER_FDB_DEL:
221685fb6a4SVivien Didelot 		err = dsa_switch_fdb_del(ds, info);
222685fb6a4SVivien Didelot 		break;
2238ae5bcdcSVivien Didelot 	case DSA_NOTIFIER_MDB_ADD:
2248ae5bcdcSVivien Didelot 		err = dsa_switch_mdb_add(ds, info);
2258ae5bcdcSVivien Didelot 		break;
2268ae5bcdcSVivien Didelot 	case DSA_NOTIFIER_MDB_DEL:
2278ae5bcdcSVivien Didelot 		err = dsa_switch_mdb_del(ds, info);
2288ae5bcdcSVivien Didelot 		break;
229d0c627b8SVivien Didelot 	case DSA_NOTIFIER_VLAN_ADD:
230d0c627b8SVivien Didelot 		err = dsa_switch_vlan_add(ds, info);
231d0c627b8SVivien Didelot 		break;
232d0c627b8SVivien Didelot 	case DSA_NOTIFIER_VLAN_DEL:
233d0c627b8SVivien Didelot 		err = dsa_switch_vlan_del(ds, info);
234d0c627b8SVivien Didelot 		break;
235f515f192SVivien Didelot 	default:
236f515f192SVivien Didelot 		err = -EOPNOTSUPP;
237f515f192SVivien Didelot 		break;
238f515f192SVivien Didelot 	}
239f515f192SVivien Didelot 
240f515f192SVivien Didelot 	/* Non-switchdev operations cannot be rolled back. If a DSA driver
241f515f192SVivien Didelot 	 * returns an error during the chained call, switch chips may be in an
242f515f192SVivien Didelot 	 * inconsistent state.
243f515f192SVivien Didelot 	 */
244f515f192SVivien Didelot 	if (err)
245f515f192SVivien Didelot 		dev_dbg(ds->dev, "breaking chain for DSA event %lu (%d)\n",
246f515f192SVivien Didelot 			event, err);
247f515f192SVivien Didelot 
248f515f192SVivien Didelot 	return notifier_from_errno(err);
249f515f192SVivien Didelot }
250f515f192SVivien Didelot 
251f515f192SVivien Didelot int dsa_switch_register_notifier(struct dsa_switch *ds)
252f515f192SVivien Didelot {
253f515f192SVivien Didelot 	ds->nb.notifier_call = dsa_switch_event;
254f515f192SVivien Didelot 
255f515f192SVivien Didelot 	return raw_notifier_chain_register(&ds->dst->nh, &ds->nb);
256f515f192SVivien Didelot }
257f515f192SVivien Didelot 
258f515f192SVivien Didelot void dsa_switch_unregister_notifier(struct dsa_switch *ds)
259f515f192SVivien Didelot {
260f515f192SVivien Didelot 	int err;
261f515f192SVivien Didelot 
262f515f192SVivien Didelot 	err = raw_notifier_chain_unregister(&ds->dst->nh, &ds->nb);
263f515f192SVivien Didelot 	if (err)
264f515f192SVivien Didelot 		dev_err(ds->dev, "failed to unregister notifier (%d)\n", err);
265f515f192SVivien Didelot }
266