switch.c 6.93 KB
Newer Older
Vivien Didelot's avatar
Vivien Didelot committed
1
2
3
/*
 * Handling of a single switch chip, part of a switch fabric
 *
4
5
 * Copyright (c) 2017 Savoir-faire Linux Inc.
 *	Vivien Didelot <vivien.didelot@savoirfairelinux.com>
Vivien Didelot's avatar
Vivien Didelot committed
6
7
8
9
10
11
12
13
14
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 */

#include <linux/netdevice.h>
#include <linux/notifier.h>
15
#include <net/switchdev.h>
16
17

#include "dsa_priv.h"
Vivien Didelot's avatar
Vivien Didelot committed
18

19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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) {
		struct dsa_port *dp = &ds->ports[i];

		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;

	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;
}

57
58
59
60
61
62
static int dsa_switch_bridge_join(struct dsa_switch *ds,
				  struct dsa_notifier_bridge_info *info)
{
	if (ds->index == info->sw_index && ds->ops->port_bridge_join)
		return ds->ops->port_bridge_join(ds, info->port, info->br);

63
64
65
	if (ds->index != info->sw_index && ds->ops->crosschip_bridge_join)
		return ds->ops->crosschip_bridge_join(ds, info->sw_index,
						      info->port, info->br);
66
67
68
69
70
71
72
73
74
75

	return 0;
}

static int dsa_switch_bridge_leave(struct dsa_switch *ds,
				   struct dsa_notifier_bridge_info *info)
{
	if (ds->index == info->sw_index && ds->ops->port_bridge_leave)
		ds->ops->port_bridge_leave(ds, info->port, info->br);

76
77
78
	if (ds->index != info->sw_index && ds->ops->crosschip_bridge_leave)
		ds->ops->crosschip_bridge_leave(ds, info->sw_index, info->port,
						info->br);
79
80
81
82

	return 0;
}

Vivien Didelot's avatar
Vivien Didelot committed
83
84
85
86
87
88
89
static int dsa_switch_fdb_add(struct dsa_switch *ds,
			      struct dsa_notifier_fdb_info *info)
{
	/* Do not care yet about other switch chips of the fabric */
	if (ds->index != info->sw_index)
		return 0;

90
91
	if (!ds->ops->port_fdb_add)
		return -EOPNOTSUPP;
Vivien Didelot's avatar
Vivien Didelot committed
92

93
94
	return ds->ops->port_fdb_add(ds, info->port, info->addr,
				     info->vid);
Vivien Didelot's avatar
Vivien Didelot committed
95
96
97
98
99
100
101
102
103
104
105
106
}

static int dsa_switch_fdb_del(struct dsa_switch *ds,
			      struct dsa_notifier_fdb_info *info)
{
	/* Do not care yet about other switch chips of the fabric */
	if (ds->index != info->sw_index)
		return 0;

	if (!ds->ops->port_fdb_del)
		return -EOPNOTSUPP;

107
108
	return ds->ops->port_fdb_del(ds, info->port, info->addr,
				     info->vid);
Vivien Didelot's avatar
Vivien Didelot committed
109
110
}

Vivien Didelot's avatar
Vivien Didelot committed
111
112
113
114
115
static int dsa_switch_mdb_add(struct dsa_switch *ds,
			      struct dsa_notifier_mdb_info *info)
{
	const struct switchdev_obj_port_mdb *mdb = info->mdb;
	struct switchdev_trans *trans = info->trans;
116
117
	DECLARE_BITMAP(group, ds->num_ports);
	int port, err;
Vivien Didelot's avatar
Vivien Didelot committed
118

119
120
121
122
123
124
125
	/* Build a mask of Multicast group members */
	bitmap_zero(group, ds->num_ports);
	if (ds->index == info->sw_index)
		set_bit(info->port, group);
	for (port = 0; port < ds->num_ports; port++)
		if (dsa_is_cpu_port(ds, port) || dsa_is_dsa_port(ds, port))
			set_bit(port, group);
Vivien Didelot's avatar
Vivien Didelot committed
126
127
128
129
130

	if (switchdev_trans_ph_prepare(trans)) {
		if (!ds->ops->port_mdb_prepare || !ds->ops->port_mdb_add)
			return -EOPNOTSUPP;

131
132
133
134
135
		for_each_set_bit(port, group, ds->num_ports) {
			err = ds->ops->port_mdb_prepare(ds, port, mdb, trans);
			if (err)
				return err;
		}
136
137

		return 0;
Vivien Didelot's avatar
Vivien Didelot committed
138
139
	}

140
141
	for_each_set_bit(port, group, ds->num_ports)
		ds->ops->port_mdb_add(ds, port, mdb, trans);
Vivien Didelot's avatar
Vivien Didelot committed
142
143
144
145
146
147
148
149
150
151
152
153

	return 0;
}

static int dsa_switch_mdb_del(struct dsa_switch *ds,
			      struct dsa_notifier_mdb_info *info)
{
	const struct switchdev_obj_port_mdb *mdb = info->mdb;

	if (!ds->ops->port_mdb_del)
		return -EOPNOTSUPP;

154
155
156
157
	if (ds->index == info->sw_index)
		return ds->ops->port_mdb_del(ds, info->port, mdb);

	return 0;
Vivien Didelot's avatar
Vivien Didelot committed
158
159
}

Vivien Didelot's avatar
Vivien Didelot committed
160
161
162
163
164
static int dsa_switch_vlan_add(struct dsa_switch *ds,
			       struct dsa_notifier_vlan_info *info)
{
	const struct switchdev_obj_port_vlan *vlan = info->vlan;
	struct switchdev_trans *trans = info->trans;
165
166
	DECLARE_BITMAP(members, ds->num_ports);
	int port, err;
Vivien Didelot's avatar
Vivien Didelot committed
167

168
169
170
171
	/* Build a mask of VLAN members */
	bitmap_zero(members, ds->num_ports);
	if (ds->index == info->sw_index)
		set_bit(info->port, members);
172
173
174
	for (port = 0; port < ds->num_ports; port++)
		if (dsa_is_cpu_port(ds, port) || dsa_is_dsa_port(ds, port))
			set_bit(port, members);
Vivien Didelot's avatar
Vivien Didelot committed
175
176
177
178
179

	if (switchdev_trans_ph_prepare(trans)) {
		if (!ds->ops->port_vlan_prepare || !ds->ops->port_vlan_add)
			return -EOPNOTSUPP;

180
181
182
183
184
		for_each_set_bit(port, members, ds->num_ports) {
			err = ds->ops->port_vlan_prepare(ds, port, vlan, trans);
			if (err)
				return err;
		}
Vivien Didelot's avatar
Vivien Didelot committed
185
186
	}

187
188
	for_each_set_bit(port, members, ds->num_ports)
		ds->ops->port_vlan_add(ds, port, vlan, trans);
Vivien Didelot's avatar
Vivien Didelot committed
189
190
191
192
193
194
195
196
197
198
199
200

	return 0;
}

static int dsa_switch_vlan_del(struct dsa_switch *ds,
			       struct dsa_notifier_vlan_info *info)
{
	const struct switchdev_obj_port_vlan *vlan = info->vlan;

	if (!ds->ops->port_vlan_del)
		return -EOPNOTSUPP;

201
202
203
204
	if (ds->index == info->sw_index)
		return ds->ops->port_vlan_del(ds, info->port, vlan);

	return 0;
Vivien Didelot's avatar
Vivien Didelot committed
205
206
}

Vivien Didelot's avatar
Vivien Didelot committed
207
208
209
210
211
212
213
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) {
214
215
216
	case DSA_NOTIFIER_AGEING_TIME:
		err = dsa_switch_ageing_time(ds, info);
		break;
217
218
219
220
221
222
	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;
Vivien Didelot's avatar
Vivien Didelot committed
223
224
225
226
227
228
	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;
Vivien Didelot's avatar
Vivien Didelot committed
229
230
231
232
233
234
	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;
Vivien Didelot's avatar
Vivien Didelot committed
235
236
237
238
239
240
	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;
Vivien Didelot's avatar
Vivien Didelot committed
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
	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)\n",
			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)\n", err);
}