Blame view

net/dsa/switch.c 9.08 KB
2874c5fd2   Thomas Gleixner   treewide: Replace...
1
  // SPDX-License-Identifier: GPL-2.0-or-later
f515f192a   Vivien Didelot   net: dsa: add swi...
2
3
4
  /*
   * Handling of a single switch chip, part of a switch fabric
   *
4333d619f   Vivien Didelot   net: dsa: fix cop...
5
6
   * Copyright (c) 2017 Savoir-faire Linux Inc.
   *	Vivien Didelot <vivien.didelot@savoirfairelinux.com>
f515f192a   Vivien Didelot   net: dsa: add swi...
7
   */
d371b7c92   Vladimir Oltean   net: dsa: Unset v...
8
  #include <linux/if_bridge.h>
f515f192a   Vivien Didelot   net: dsa: add swi...
9
10
  #include <linux/netdevice.h>
  #include <linux/notifier.h>
061f6a505   Florian Fainelli   net: dsa: Add ndo...
11
  #include <linux/if_vlan.h>
1faabf744   Vivien Didelot   net: dsa: add not...
12
  #include <net/switchdev.h>
ea5dd34be   Vivien Didelot   net: dsa: include...
13
14
  
  #include "dsa_priv.h"
f515f192a   Vivien Didelot   net: dsa: add swi...
15

1faabf744   Vivien Didelot   net: dsa: add not...
16
17
18
19
20
21
  static unsigned int dsa_switch_fastest_ageing_time(struct dsa_switch *ds,
  						   unsigned int ageing_time)
  {
  	int i;
  
  	for (i = 0; i < ds->num_ports; ++i) {
68bb8ea8a   Vivien Didelot   net: dsa: use dsa...
22
  		struct dsa_port *dp = dsa_to_port(ds, i);
1faabf744   Vivien Didelot   net: dsa: add not...
23
24
25
26
27
28
29
30
31
32
33
34
35
  
  		if (dp->ageing_time && dp->ageing_time < ageing_time)
  			ageing_time = dp->ageing_time;
  	}
  
  	return ageing_time;
  }
  
  static int dsa_switch_ageing_time(struct dsa_switch *ds,
  				  struct dsa_notifier_ageing_time_info *info)
  {
  	unsigned int ageing_time = info->ageing_time;
  	struct switchdev_trans *trans = info->trans;
1faabf744   Vivien Didelot   net: dsa: add not...
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
  	if (switchdev_trans_ph_prepare(trans)) {
  		if (ds->ageing_time_min && ageing_time < ds->ageing_time_min)
  			return -ERANGE;
  		if (ds->ageing_time_max && ageing_time > ds->ageing_time_max)
  			return -ERANGE;
  		return 0;
  	}
  
  	/* Program the fastest ageing time in case of multiple bridges */
  	ageing_time = dsa_switch_fastest_ageing_time(ds, ageing_time);
  
  	if (ds->ops->set_ageing_time)
  		return ds->ops->set_ageing_time(ds, ageing_time);
  
  	return 0;
  }
bfcb81320   Vladimir Oltean   net: dsa: configu...
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
  static bool dsa_switch_mtu_match(struct dsa_switch *ds, int port,
  				 struct dsa_notifier_mtu_info *info)
  {
  	if (ds->index == info->sw_index)
  		return (port == info->port) || dsa_is_dsa_port(ds, port);
  
  	if (!info->propagate_upstream)
  		return false;
  
  	if (dsa_is_dsa_port(ds, port) || dsa_is_cpu_port(ds, port))
  		return true;
  
  	return false;
  }
  
  static int dsa_switch_mtu(struct dsa_switch *ds,
  			  struct dsa_notifier_mtu_info *info)
  {
  	int port, ret;
  
  	if (!ds->ops->port_change_mtu)
  		return -EOPNOTSUPP;
  
  	for (port = 0; port < ds->num_ports; port++) {
  		if (dsa_switch_mtu_match(ds, port, info)) {
  			ret = ds->ops->port_change_mtu(ds, port, info->mtu);
  			if (ret)
  				return ret;
  		}
  	}
  
  	return 0;
  }
04d3a4c6a   Vivien Didelot   net: dsa: introdu...
85
86
87
  static int dsa_switch_bridge_join(struct dsa_switch *ds,
  				  struct dsa_notifier_bridge_info *info)
  {
f66a6a69f   Vladimir Oltean   net: dsa: permit ...
88
89
90
91
  	struct dsa_switch_tree *dst = ds->dst;
  
  	if (dst->index == info->tree_index && ds->index == info->sw_index &&
  	    ds->ops->port_bridge_join)
04d3a4c6a   Vivien Didelot   net: dsa: introdu...
92
  		return ds->ops->port_bridge_join(ds, info->port, info->br);
f66a6a69f   Vladimir Oltean   net: dsa: permit ...
93
94
95
96
  	if ((dst->index != info->tree_index || ds->index != info->sw_index) &&
  	    ds->ops->crosschip_bridge_join)
  		return ds->ops->crosschip_bridge_join(ds, info->tree_index,
  						      info->sw_index,
40ef2c933   Vivien Didelot   net: dsa: add cro...
97
  						      info->port, info->br);
04d3a4c6a   Vivien Didelot   net: dsa: introdu...
98
99
100
101
102
103
104
  
  	return 0;
  }
  
  static int dsa_switch_bridge_leave(struct dsa_switch *ds,
  				   struct dsa_notifier_bridge_info *info)
  {
d371b7c92   Vladimir Oltean   net: dsa: Unset v...
105
  	bool unset_vlan_filtering = br_vlan_enabled(info->br);
f66a6a69f   Vladimir Oltean   net: dsa: permit ...
106
  	struct dsa_switch_tree *dst = ds->dst;
d371b7c92   Vladimir Oltean   net: dsa: Unset v...
107
  	int err, i;
f66a6a69f   Vladimir Oltean   net: dsa: permit ...
108
109
  	if (dst->index == info->tree_index && ds->index == info->sw_index &&
  	    ds->ops->port_bridge_join)
04d3a4c6a   Vivien Didelot   net: dsa: introdu...
110
  		ds->ops->port_bridge_leave(ds, info->port, info->br);
f66a6a69f   Vladimir Oltean   net: dsa: permit ...
111
112
113
114
  	if ((dst->index != info->tree_index || ds->index != info->sw_index) &&
  	    ds->ops->crosschip_bridge_join)
  		ds->ops->crosschip_bridge_leave(ds, info->tree_index,
  						info->sw_index, info->port,
40ef2c933   Vivien Didelot   net: dsa: add cro...
115
  						info->br);
04d3a4c6a   Vivien Didelot   net: dsa: introdu...
116

d371b7c92   Vladimir Oltean   net: dsa: Unset v...
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
  	/* If the bridge was vlan_filtering, the bridge core doesn't trigger an
  	 * event for changing vlan_filtering setting upon slave ports leaving
  	 * it. That is a good thing, because that lets us handle it and also
  	 * handle the case where the switch's vlan_filtering setting is global
  	 * (not per port). When that happens, the correct moment to trigger the
  	 * vlan_filtering callback is only when the last port left this bridge.
  	 */
  	if (unset_vlan_filtering && ds->vlan_filtering_is_global) {
  		for (i = 0; i < ds->num_ports; i++) {
  			if (i == info->port)
  				continue;
  			if (dsa_to_port(ds, i)->bridge_dev == info->br) {
  				unset_vlan_filtering = false;
  				break;
  			}
  		}
  	}
  	if (unset_vlan_filtering) {
2e554a7a5   Vladimir Oltean   net: dsa: propaga...
135
  		struct switchdev_trans trans;
d371b7c92   Vladimir Oltean   net: dsa: Unset v...
136

2e554a7a5   Vladimir Oltean   net: dsa: propaga...
137
138
139
140
141
142
143
  		trans.ph_prepare = true;
  		err = dsa_port_vlan_filtering(dsa_to_port(ds, info->port),
  					      false, &trans);
  		if (err && err != EOPNOTSUPP)
  			return err;
  
  		trans.ph_prepare = false;
68bb8ea8a   Vivien Didelot   net: dsa: use dsa...
144
  		err = dsa_port_vlan_filtering(dsa_to_port(ds, info->port),
d371b7c92   Vladimir Oltean   net: dsa: Unset v...
145
146
147
148
  					      false, &trans);
  		if (err && err != EOPNOTSUPP)
  			return err;
  	}
04d3a4c6a   Vivien Didelot   net: dsa: introdu...
149
150
  	return 0;
  }
685fb6a40   Vivien Didelot   net: dsa: add FDB...
151
152
153
  static int dsa_switch_fdb_add(struct dsa_switch *ds,
  			      struct dsa_notifier_fdb_info *info)
  {
3169241f5   Vivien Didelot   net: dsa: support...
154
  	int port = dsa_towards_port(ds, info->sw_index, info->port);
685fb6a40   Vivien Didelot   net: dsa: add FDB...
155

1b6dd556c   Arkadi Sharshevsky   net: dsa: Remove ...
156
157
  	if (!ds->ops->port_fdb_add)
  		return -EOPNOTSUPP;
685fb6a40   Vivien Didelot   net: dsa: add FDB...
158

3169241f5   Vivien Didelot   net: dsa: support...
159
  	return ds->ops->port_fdb_add(ds, port, info->addr, info->vid);
685fb6a40   Vivien Didelot   net: dsa: add FDB...
160
161
162
163
164
  }
  
  static int dsa_switch_fdb_del(struct dsa_switch *ds,
  			      struct dsa_notifier_fdb_info *info)
  {
3169241f5   Vivien Didelot   net: dsa: support...
165
  	int port = dsa_towards_port(ds, info->sw_index, info->port);
685fb6a40   Vivien Didelot   net: dsa: add FDB...
166
167
168
  
  	if (!ds->ops->port_fdb_del)
  		return -EOPNOTSUPP;
3169241f5   Vivien Didelot   net: dsa: support...
169
  	return ds->ops->port_fdb_del(ds, port, info->addr, info->vid);
685fb6a40   Vivien Didelot   net: dsa: add FDB...
170
  }
e65d45cc3   Vivien Didelot   net: dsa: remove ...
171
172
173
174
175
176
177
178
179
180
181
182
183
184
  static bool dsa_switch_mdb_match(struct dsa_switch *ds, int port,
  				 struct dsa_notifier_mdb_info *info)
  {
  	if (ds->index == info->sw_index && port == info->port)
  		return true;
  
  	if (dsa_is_dsa_port(ds, port))
  		return true;
  
  	return false;
  }
  
  static int dsa_switch_mdb_prepare(struct dsa_switch *ds,
  				  struct dsa_notifier_mdb_info *info)
e6db98db8   Vivien Didelot   net: dsa: add swi...
185
186
187
188
189
  {
  	int port, err;
  
  	if (!ds->ops->port_mdb_prepare || !ds->ops->port_mdb_add)
  		return -EOPNOTSUPP;
e65d45cc3   Vivien Didelot   net: dsa: remove ...
190
191
192
193
194
195
  	for (port = 0; port < ds->num_ports; port++) {
  		if (dsa_switch_mdb_match(ds, port, info)) {
  			err = ds->ops->port_mdb_prepare(ds, port, info->mdb);
  			if (err)
  				return err;
  		}
e6db98db8   Vivien Didelot   net: dsa: add swi...
196
197
198
199
  	}
  
  	return 0;
  }
8ae5bcdc5   Vivien Didelot   net: dsa: add MDB...
200
201
202
  static int dsa_switch_mdb_add(struct dsa_switch *ds,
  			      struct dsa_notifier_mdb_info *info)
  {
e6db98db8   Vivien Didelot   net: dsa: add swi...
203
  	int port;
8ae5bcdc5   Vivien Didelot   net: dsa: add MDB...
204

e65d45cc3   Vivien Didelot   net: dsa: remove ...
205
206
  	if (switchdev_trans_ph_prepare(info->trans))
  		return dsa_switch_mdb_prepare(ds, info);
8ae5bcdc5   Vivien Didelot   net: dsa: add MDB...
207

e65d45cc3   Vivien Didelot   net: dsa: remove ...
208
209
  	if (!ds->ops->port_mdb_add)
  		return 0;
8ae5bcdc5   Vivien Didelot   net: dsa: add MDB...
210

e65d45cc3   Vivien Didelot   net: dsa: remove ...
211
212
213
  	for (port = 0; port < ds->num_ports; port++)
  		if (dsa_switch_mdb_match(ds, port, info))
  			ds->ops->port_mdb_add(ds, port, info->mdb);
8ae5bcdc5   Vivien Didelot   net: dsa: add MDB...
214
215
216
217
218
219
220
  
  	return 0;
  }
  
  static int dsa_switch_mdb_del(struct dsa_switch *ds,
  			      struct dsa_notifier_mdb_info *info)
  {
8ae5bcdc5   Vivien Didelot   net: dsa: add MDB...
221
222
  	if (!ds->ops->port_mdb_del)
  		return -EOPNOTSUPP;
a1a6b7ea7   Vivien Didelot   net: dsa: add cro...
223
  	if (ds->index == info->sw_index)
e65d45cc3   Vivien Didelot   net: dsa: remove ...
224
  		return ds->ops->port_mdb_del(ds, info->port, info->mdb);
a1a6b7ea7   Vivien Didelot   net: dsa: add cro...
225
226
  
  	return 0;
8ae5bcdc5   Vivien Didelot   net: dsa: add MDB...
227
  }
e65d45cc3   Vivien Didelot   net: dsa: remove ...
228
229
230
231
232
  static bool dsa_switch_vlan_match(struct dsa_switch *ds, int port,
  				  struct dsa_notifier_vlan_info *info)
  {
  	if (ds->index == info->sw_index && port == info->port)
  		return true;
7e1741b47   Vivien Didelot   net: dsa: program...
233
  	if (dsa_is_dsa_port(ds, port))
e65d45cc3   Vivien Didelot   net: dsa: remove ...
234
235
236
237
238
239
240
  		return true;
  
  	return false;
  }
  
  static int dsa_switch_vlan_prepare(struct dsa_switch *ds,
  				   struct dsa_notifier_vlan_info *info)
9c428c593   Vivien Didelot   net: dsa: add swi...
241
242
243
244
245
  {
  	int port, err;
  
  	if (!ds->ops->port_vlan_prepare || !ds->ops->port_vlan_add)
  		return -EOPNOTSUPP;
e65d45cc3   Vivien Didelot   net: dsa: remove ...
246
247
  	for (port = 0; port < ds->num_ports; port++) {
  		if (dsa_switch_vlan_match(ds, port, info)) {
e65d45cc3   Vivien Didelot   net: dsa: remove ...
248
249
250
251
  			err = ds->ops->port_vlan_prepare(ds, port, info->vlan);
  			if (err)
  				return err;
  		}
9c428c593   Vivien Didelot   net: dsa: add swi...
252
253
254
255
  	}
  
  	return 0;
  }
d0c627b87   Vivien Didelot   net: dsa: add VLA...
256
257
258
  static int dsa_switch_vlan_add(struct dsa_switch *ds,
  			       struct dsa_notifier_vlan_info *info)
  {
9c428c593   Vivien Didelot   net: dsa: add swi...
259
  	int port;
d0c627b87   Vivien Didelot   net: dsa: add VLA...
260

e65d45cc3   Vivien Didelot   net: dsa: remove ...
261
262
  	if (switchdev_trans_ph_prepare(info->trans))
  		return dsa_switch_vlan_prepare(ds, info);
d0c627b87   Vivien Didelot   net: dsa: add VLA...
263

e65d45cc3   Vivien Didelot   net: dsa: remove ...
264
265
  	if (!ds->ops->port_vlan_add)
  		return 0;
d0c627b87   Vivien Didelot   net: dsa: add VLA...
266

e65d45cc3   Vivien Didelot   net: dsa: remove ...
267
268
269
  	for (port = 0; port < ds->num_ports; port++)
  		if (dsa_switch_vlan_match(ds, port, info))
  			ds->ops->port_vlan_add(ds, port, info->vlan);
d0c627b87   Vivien Didelot   net: dsa: add VLA...
270
271
272
273
274
275
276
  
  	return 0;
  }
  
  static int dsa_switch_vlan_del(struct dsa_switch *ds,
  			       struct dsa_notifier_vlan_info *info)
  {
d0c627b87   Vivien Didelot   net: dsa: add VLA...
277
278
  	if (!ds->ops->port_vlan_del)
  		return -EOPNOTSUPP;
1ca4aa9cd   Vivien Didelot   net: dsa: check V...
279
  	if (ds->index == info->sw_index)
e65d45cc3   Vivien Didelot   net: dsa: remove ...
280
  		return ds->ops->port_vlan_del(ds, info->port, info->vlan);
1ca4aa9cd   Vivien Didelot   net: dsa: check V...
281

7e1741b47   Vivien Didelot   net: dsa: program...
282
283
284
  	/* Do not deprogram the DSA links as they may be used as conduit
  	 * for other VLAN members in the fabric.
  	 */
1ca4aa9cd   Vivien Didelot   net: dsa: check V...
285
  	return 0;
d0c627b87   Vivien Didelot   net: dsa: add VLA...
286
  }
f515f192a   Vivien Didelot   net: dsa: add swi...
287
288
289
290
291
292
293
  static int dsa_switch_event(struct notifier_block *nb,
  			    unsigned long event, void *info)
  {
  	struct dsa_switch *ds = container_of(nb, struct dsa_switch, nb);
  	int err;
  
  	switch (event) {
1faabf744   Vivien Didelot   net: dsa: add not...
294
295
296
  	case DSA_NOTIFIER_AGEING_TIME:
  		err = dsa_switch_ageing_time(ds, info);
  		break;
04d3a4c6a   Vivien Didelot   net: dsa: introdu...
297
298
299
300
301
302
  	case DSA_NOTIFIER_BRIDGE_JOIN:
  		err = dsa_switch_bridge_join(ds, info);
  		break;
  	case DSA_NOTIFIER_BRIDGE_LEAVE:
  		err = dsa_switch_bridge_leave(ds, info);
  		break;
685fb6a40   Vivien Didelot   net: dsa: add FDB...
303
304
305
306
307
308
  	case DSA_NOTIFIER_FDB_ADD:
  		err = dsa_switch_fdb_add(ds, info);
  		break;
  	case DSA_NOTIFIER_FDB_DEL:
  		err = dsa_switch_fdb_del(ds, info);
  		break;
8ae5bcdc5   Vivien Didelot   net: dsa: add MDB...
309
310
311
312
313
314
  	case DSA_NOTIFIER_MDB_ADD:
  		err = dsa_switch_mdb_add(ds, info);
  		break;
  	case DSA_NOTIFIER_MDB_DEL:
  		err = dsa_switch_mdb_del(ds, info);
  		break;
d0c627b87   Vivien Didelot   net: dsa: add VLA...
315
316
317
318
319
320
  	case DSA_NOTIFIER_VLAN_ADD:
  		err = dsa_switch_vlan_add(ds, info);
  		break;
  	case DSA_NOTIFIER_VLAN_DEL:
  		err = dsa_switch_vlan_del(ds, info);
  		break;
bfcb81320   Vladimir Oltean   net: dsa: configu...
321
322
323
  	case DSA_NOTIFIER_MTU:
  		err = dsa_switch_mtu(ds, info);
  		break;
f515f192a   Vivien Didelot   net: dsa: add swi...
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
  	default:
  		err = -EOPNOTSUPP;
  		break;
  	}
  
  	/* Non-switchdev operations cannot be rolled back. If a DSA driver
  	 * returns an error during the chained call, switch chips may be in an
  	 * inconsistent state.
  	 */
  	if (err)
  		dev_dbg(ds->dev, "breaking chain for DSA event %lu (%d)
  ",
  			event, err);
  
  	return notifier_from_errno(err);
  }
  
  int dsa_switch_register_notifier(struct dsa_switch *ds)
  {
  	ds->nb.notifier_call = dsa_switch_event;
  
  	return raw_notifier_chain_register(&ds->dst->nh, &ds->nb);
  }
  
  void dsa_switch_unregister_notifier(struct dsa_switch *ds)
  {
  	int err;
  
  	err = raw_notifier_chain_unregister(&ds->dst->nh, &ds->nb);
  	if (err)
  		dev_err(ds->dev, "failed to unregister notifier (%d)
  ", err);
  }