Commit | Line | Data |
---|---|---|
f9bbe447 VO |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* Copyright (c) 2019, Vladimir Oltean <olteanv@gmail.com> | |
3 | * | |
4 | * This module is not a complete tagger implementation. It only provides | |
5 | * primitives for taggers that rely on 802.1Q VLAN tags to use. The | |
6 | * dsa_8021q_netdev_ops is registered for API compliance and not used | |
7 | * directly by callers. | |
8 | */ | |
9 | #include <linux/if_bridge.h> | |
10 | #include <linux/if_vlan.h> | |
ac02a451 | 11 | #include <linux/dsa/8021q.h> |
f9bbe447 VO |
12 | |
13 | #include "dsa_priv.h" | |
14 | ||
0471dd42 VO |
15 | /* Binary structure of the fake 12-bit VID field (when the TPID is |
16 | * ETH_P_DSA_8021Q): | |
17 | * | |
18 | * | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | | |
19 | * +-----------+-----+-----------------+-----------+-----------------------+ | |
0fac6aa0 | 20 | * | DIR | RSV | SWITCH_ID | RSV | PORT | |
0471dd42 VO |
21 | * +-----------+-----+-----------------+-----------+-----------------------+ |
22 | * | |
23 | * DIR - VID[11:10]: | |
24 | * Direction flags. | |
25 | * * 1 (0b01) for RX VLAN, | |
26 | * * 2 (0b10) for TX VLAN. | |
27 | * These values make the special VIDs of 0, 1 and 4095 to be left | |
28 | * unused by this coding scheme. | |
29 | * | |
0471dd42 | 30 | * SWITCH_ID - VID[8:6]: |
fcee85f1 | 31 | * Index of switch within DSA tree. Must be between 0 and 7. |
0471dd42 | 32 | * |
0fac6aa0 VO |
33 | * RSV - VID[5:4]: |
34 | * To be used for further expansion of PORT or for other purposes. | |
35 | * Must be transmitted as zero and ignored on receive. | |
36 | * | |
0471dd42 | 37 | * PORT - VID[3:0]: |
fcee85f1 | 38 | * Index of switch port. Must be between 0 and 15. |
f9bbe447 | 39 | */ |
0471dd42 VO |
40 | |
41 | #define DSA_8021Q_DIR_SHIFT 10 | |
42 | #define DSA_8021Q_DIR_MASK GENMASK(11, 10) | |
43 | #define DSA_8021Q_DIR(x) (((x) << DSA_8021Q_DIR_SHIFT) & \ | |
44 | DSA_8021Q_DIR_MASK) | |
45 | #define DSA_8021Q_DIR_RX DSA_8021Q_DIR(1) | |
46 | #define DSA_8021Q_DIR_TX DSA_8021Q_DIR(2) | |
47 | ||
48 | #define DSA_8021Q_SWITCH_ID_SHIFT 6 | |
49 | #define DSA_8021Q_SWITCH_ID_MASK GENMASK(8, 6) | |
50 | #define DSA_8021Q_SWITCH_ID(x) (((x) << DSA_8021Q_SWITCH_ID_SHIFT) & \ | |
51 | DSA_8021Q_SWITCH_ID_MASK) | |
52 | ||
53 | #define DSA_8021Q_PORT_SHIFT 0 | |
54 | #define DSA_8021Q_PORT_MASK GENMASK(3, 0) | |
55 | #define DSA_8021Q_PORT(x) (((x) << DSA_8021Q_PORT_SHIFT) & \ | |
56 | DSA_8021Q_PORT_MASK) | |
f9bbe447 VO |
57 | |
58 | /* Returns the VID to be inserted into the frame from xmit for switch steering | |
59 | * instructions on egress. Encodes switch ID and port ID. | |
60 | */ | |
61 | u16 dsa_8021q_tx_vid(struct dsa_switch *ds, int port) | |
62 | { | |
0471dd42 VO |
63 | return DSA_8021Q_DIR_TX | DSA_8021Q_SWITCH_ID(ds->index) | |
64 | DSA_8021Q_PORT(port); | |
f9bbe447 VO |
65 | } |
66 | EXPORT_SYMBOL_GPL(dsa_8021q_tx_vid); | |
67 | ||
68 | /* Returns the VID that will be installed as pvid for this switch port, sent as | |
69 | * tagged egress towards the CPU port and decoded by the rcv function. | |
70 | */ | |
71 | u16 dsa_8021q_rx_vid(struct dsa_switch *ds, int port) | |
72 | { | |
0471dd42 VO |
73 | return DSA_8021Q_DIR_RX | DSA_8021Q_SWITCH_ID(ds->index) | |
74 | DSA_8021Q_PORT(port); | |
f9bbe447 VO |
75 | } |
76 | EXPORT_SYMBOL_GPL(dsa_8021q_rx_vid); | |
77 | ||
78 | /* Returns the decoded switch ID from the RX VID. */ | |
79 | int dsa_8021q_rx_switch_id(u16 vid) | |
80 | { | |
0471dd42 | 81 | return (vid & DSA_8021Q_SWITCH_ID_MASK) >> DSA_8021Q_SWITCH_ID_SHIFT; |
f9bbe447 VO |
82 | } |
83 | EXPORT_SYMBOL_GPL(dsa_8021q_rx_switch_id); | |
84 | ||
85 | /* Returns the decoded port ID from the RX VID. */ | |
86 | int dsa_8021q_rx_source_port(u16 vid) | |
87 | { | |
0471dd42 | 88 | return (vid & DSA_8021Q_PORT_MASK) >> DSA_8021Q_PORT_SHIFT; |
f9bbe447 VO |
89 | } |
90 | EXPORT_SYMBOL_GPL(dsa_8021q_rx_source_port); | |
91 | ||
9c7caf28 VO |
92 | bool vid_is_dsa_8021q_rxvlan(u16 vid) |
93 | { | |
94 | return (vid & DSA_8021Q_DIR_MASK) == DSA_8021Q_DIR_RX; | |
95 | } | |
96 | EXPORT_SYMBOL_GPL(vid_is_dsa_8021q_rxvlan); | |
97 | ||
98 | bool vid_is_dsa_8021q_txvlan(u16 vid) | |
99 | { | |
100 | return (vid & DSA_8021Q_DIR_MASK) == DSA_8021Q_DIR_TX; | |
101 | } | |
102 | EXPORT_SYMBOL_GPL(vid_is_dsa_8021q_txvlan); | |
103 | ||
1f66b0f0 VO |
104 | bool vid_is_dsa_8021q(u16 vid) |
105 | { | |
9c7caf28 | 106 | return vid_is_dsa_8021q_rxvlan(vid) || vid_is_dsa_8021q_txvlan(vid); |
1f66b0f0 VO |
107 | } |
108 | EXPORT_SYMBOL_GPL(vid_is_dsa_8021q); | |
109 | ||
5f33183b VO |
110 | /* If @enabled is true, installs @vid with @flags into the switch port's HW |
111 | * filter. | |
112 | * If @enabled is false, deletes @vid (ignores @flags) from the port. Had the | |
113 | * user explicitly configured this @vid through the bridge core, then the @vid | |
114 | * is installed again, but this time with the flags from the bridge layer. | |
115 | */ | |
d7b1fd52 | 116 | static int dsa_8021q_vid_apply(struct dsa_switch *ds, int port, u16 vid, |
5f33183b VO |
117 | u16 flags, bool enabled) |
118 | { | |
d7b1fd52 VO |
119 | struct dsa_8021q_context *ctx = ds->tag_8021q_ctx; |
120 | struct dsa_port *dp = dsa_to_port(ds, port); | |
5f33183b VO |
121 | |
122 | if (enabled) | |
5899ee36 | 123 | return ctx->ops->vlan_add(ctx->ds, dp->index, vid, flags); |
5f33183b | 124 | |
5899ee36 | 125 | return ctx->ops->vlan_del(ctx->ds, dp->index, vid); |
5f33183b VO |
126 | } |
127 | ||
f9bbe447 VO |
128 | /* RX VLAN tagging (left) and TX VLAN tagging (right) setup shown for a single |
129 | * front-panel switch port (here swp0). | |
130 | * | |
131 | * Port identification through VLAN (802.1Q) tags has different requirements | |
132 | * for it to work effectively: | |
133 | * - On RX (ingress from network): each front-panel port must have a pvid | |
134 | * that uniquely identifies it, and the egress of this pvid must be tagged | |
135 | * towards the CPU port, so that software can recover the source port based | |
136 | * on the VID in the frame. But this would only work for standalone ports; | |
137 | * if bridged, this VLAN setup would break autonomous forwarding and would | |
138 | * force all switched traffic to pass through the CPU. So we must also make | |
139 | * the other front-panel ports members of this VID we're adding, albeit | |
140 | * we're not making it their PVID (they'll still have their own). | |
141 | * By the way - just because we're installing the same VID in multiple | |
142 | * switch ports doesn't mean that they'll start to talk to one another, even | |
143 | * while not bridged: the final forwarding decision is still an AND between | |
144 | * the L2 forwarding information (which is limiting forwarding in this case) | |
145 | * and the VLAN-based restrictions (of which there are none in this case, | |
146 | * since all ports are members). | |
147 | * - On TX (ingress from CPU and towards network) we are faced with a problem. | |
148 | * If we were to tag traffic (from within DSA) with the port's pvid, all | |
149 | * would be well, assuming the switch ports were standalone. Frames would | |
150 | * have no choice but to be directed towards the correct front-panel port. | |
151 | * But because we also want the RX VLAN to not break bridging, then | |
152 | * inevitably that means that we have to give them a choice (of what | |
153 | * front-panel port to go out on), and therefore we cannot steer traffic | |
154 | * based on the RX VID. So what we do is simply install one more VID on the | |
155 | * front-panel and CPU ports, and profit off of the fact that steering will | |
156 | * work just by virtue of the fact that there is only one other port that's | |
157 | * a member of the VID we're tagging the traffic with - the desired one. | |
158 | * | |
159 | * So at the end, each front-panel port will have one RX VID (also the PVID), | |
160 | * the RX VID of all other front-panel ports, and one TX VID. Whereas the CPU | |
161 | * port will have the RX and TX VIDs of all front-panel ports, and on top of | |
162 | * that, is also tagged-input and tagged-output (VLAN trunk). | |
163 | * | |
164 | * CPU port CPU port | |
165 | * +-------------+-----+-------------+ +-------------+-----+-------------+ | |
166 | * | RX VID | | | | TX VID | | | | |
167 | * | of swp0 | | | | of swp0 | | | | |
168 | * | +-----+ | | +-----+ | | |
169 | * | ^ T | | | Tagged | | |
170 | * | | | | | ingress | | |
171 | * | +-------+---+---+-------+ | | +-----------+ | | |
172 | * | | | | | | | | Untagged | | |
173 | * | | U v U v U v | | v egress | | |
174 | * | +-----+ +-----+ +-----+ +-----+ | | +-----+ +-----+ +-----+ +-----+ | | |
175 | * | | | | | | | | | | | | | | | | | | | | | |
176 | * | |PVID | | | | | | | | | | | | | | | | | | | |
177 | * +-+-----+-+-----+-+-----+-+-----+-+ +-+-----+-+-----+-+-----+-+-----+-+ | |
178 | * swp0 swp1 swp2 swp3 swp0 swp1 swp2 swp3 | |
179 | */ | |
d7b1fd52 | 180 | static int dsa_8021q_setup_port(struct dsa_switch *ds, int port, bool enabled) |
f9bbe447 | 181 | { |
d7b1fd52 VO |
182 | struct dsa_8021q_context *ctx = ds->tag_8021q_ctx; |
183 | int upstream = dsa_upstream_port(ds, port); | |
184 | u16 rx_vid = dsa_8021q_rx_vid(ds, port); | |
185 | u16 tx_vid = dsa_8021q_tx_vid(ds, port); | |
bbed0bbd | 186 | struct net_device *master; |
0fac6aa0 | 187 | int i, err; |
f9bbe447 VO |
188 | |
189 | /* The CPU port is implicitly configured by | |
190 | * configuring the front-panel ports | |
191 | */ | |
d7b1fd52 | 192 | if (!dsa_is_user_port(ds, port)) |
f9bbe447 VO |
193 | return 0; |
194 | ||
d7b1fd52 | 195 | master = dsa_to_port(ds, port)->cpu_dp->master; |
bbed0bbd | 196 | |
f9bbe447 VO |
197 | /* Add this user port's RX VID to the membership list of all others |
198 | * (including itself). This is so that bridging will not be hindered. | |
199 | * L2 forwarding rules still take precedence when there are no VLAN | |
200 | * restrictions, so there are no concerns about leaking traffic. | |
201 | */ | |
d7b1fd52 | 202 | for (i = 0; i < ds->num_ports; i++) { |
f9bbe447 VO |
203 | u16 flags; |
204 | ||
205 | if (i == upstream) | |
d34d2baa | 206 | continue; |
f9bbe447 VO |
207 | else if (i == port) |
208 | /* The RX VID is pvid on this port */ | |
209 | flags = BRIDGE_VLAN_INFO_UNTAGGED | | |
210 | BRIDGE_VLAN_INFO_PVID; | |
211 | else | |
212 | /* The RX VID is a regular VLAN on all others */ | |
213 | flags = BRIDGE_VLAN_INFO_UNTAGGED; | |
214 | ||
d7b1fd52 | 215 | err = dsa_8021q_vid_apply(ds, i, rx_vid, flags, enabled); |
f9bbe447 | 216 | if (err) { |
d7b1fd52 | 217 | dev_err(ds->dev, |
69ebb370 VO |
218 | "Failed to apply RX VID %d to port %d: %pe\n", |
219 | rx_vid, port, ERR_PTR(err)); | |
f9bbe447 VO |
220 | return err; |
221 | } | |
222 | } | |
d34d2baa IC |
223 | |
224 | /* CPU port needs to see this port's RX VID | |
225 | * as tagged egress. | |
226 | */ | |
d7b1fd52 | 227 | err = dsa_8021q_vid_apply(ds, upstream, rx_vid, 0, enabled); |
d34d2baa | 228 | if (err) { |
d7b1fd52 | 229 | dev_err(ds->dev, |
69ebb370 VO |
230 | "Failed to apply RX VID %d to port %d: %pe\n", |
231 | rx_vid, port, ERR_PTR(err)); | |
d34d2baa IC |
232 | return err; |
233 | } | |
234 | ||
0fac6aa0 VO |
235 | /* Add @rx_vid to the master's RX filter. */ |
236 | if (enabled) | |
237 | vlan_vid_add(master, ctx->proto, rx_vid); | |
238 | else | |
239 | vlan_vid_del(master, ctx->proto, rx_vid); | |
bbed0bbd | 240 | |
f9bbe447 | 241 | /* Finally apply the TX VID on this port and on the CPU port */ |
d7b1fd52 | 242 | err = dsa_8021q_vid_apply(ds, port, tx_vid, BRIDGE_VLAN_INFO_UNTAGGED, |
5f33183b | 243 | enabled); |
f9bbe447 | 244 | if (err) { |
d7b1fd52 | 245 | dev_err(ds->dev, |
69ebb370 VO |
246 | "Failed to apply TX VID %d on port %d: %pe\n", |
247 | tx_vid, port, ERR_PTR(err)); | |
f9bbe447 VO |
248 | return err; |
249 | } | |
d7b1fd52 | 250 | err = dsa_8021q_vid_apply(ds, upstream, tx_vid, 0, enabled); |
f9bbe447 | 251 | if (err) { |
d7b1fd52 | 252 | dev_err(ds->dev, |
69ebb370 VO |
253 | "Failed to apply TX VID %d on port %d: %pe\n", |
254 | tx_vid, upstream, ERR_PTR(err)); | |
f9bbe447 VO |
255 | return err; |
256 | } | |
257 | ||
5f33183b | 258 | return err; |
f9bbe447 | 259 | } |
7e092af2 | 260 | |
d7b1fd52 | 261 | int dsa_8021q_setup(struct dsa_switch *ds, bool enabled) |
7e092af2 | 262 | { |
a81a4574 | 263 | int err, port; |
7e092af2 | 264 | |
bbed0bbd VO |
265 | ASSERT_RTNL(); |
266 | ||
d7b1fd52 VO |
267 | for (port = 0; port < ds->num_ports; port++) { |
268 | err = dsa_8021q_setup_port(ds, port, enabled); | |
a81a4574 | 269 | if (err < 0) { |
d7b1fd52 | 270 | dev_err(ds->dev, |
69ebb370 VO |
271 | "Failed to setup VLAN tagging for port %d: %pe\n", |
272 | port, ERR_PTR(err)); | |
a81a4574 | 273 | return err; |
7e092af2 VO |
274 | } |
275 | } | |
276 | ||
277 | return 0; | |
278 | } | |
279 | EXPORT_SYMBOL_GPL(dsa_8021q_setup); | |
f9bbe447 | 280 | |
d7b1fd52 VO |
281 | static int dsa_8021q_crosschip_link_apply(struct dsa_switch *ds, int port, |
282 | struct dsa_switch *other_ds, | |
ec5ae610 | 283 | int other_port, bool enabled) |
ac02a451 | 284 | { |
d7b1fd52 | 285 | u16 rx_vid = dsa_8021q_rx_vid(ds, port); |
ac02a451 VO |
286 | |
287 | /* @rx_vid of local @ds port @port goes to @other_port of | |
288 | * @other_ds | |
289 | */ | |
d7b1fd52 | 290 | return dsa_8021q_vid_apply(other_ds, other_port, rx_vid, |
ac02a451 VO |
291 | BRIDGE_VLAN_INFO_UNTAGGED, enabled); |
292 | } | |
ac02a451 | 293 | |
d7b1fd52 VO |
294 | static int dsa_8021q_crosschip_link_add(struct dsa_switch *ds, int port, |
295 | struct dsa_switch *other_ds, | |
5899ee36 | 296 | int other_port) |
ac02a451 | 297 | { |
d7b1fd52 VO |
298 | struct dsa_8021q_context *other_ctx = other_ds->tag_8021q_ctx; |
299 | struct dsa_8021q_context *ctx = ds->tag_8021q_ctx; | |
ac02a451 VO |
300 | struct dsa_8021q_crosschip_link *c; |
301 | ||
5899ee36 VO |
302 | list_for_each_entry(c, &ctx->crosschip_links, list) { |
303 | if (c->port == port && c->other_ctx == other_ctx && | |
ac02a451 VO |
304 | c->other_port == other_port) { |
305 | refcount_inc(&c->refcount); | |
306 | return 0; | |
307 | } | |
308 | } | |
309 | ||
d7b1fd52 | 310 | dev_dbg(ds->dev, |
5899ee36 | 311 | "adding crosschip link from port %d to %s port %d\n", |
d7b1fd52 | 312 | port, dev_name(other_ds->dev), other_port); |
ac02a451 VO |
313 | |
314 | c = kzalloc(sizeof(*c), GFP_KERNEL); | |
315 | if (!c) | |
316 | return -ENOMEM; | |
317 | ||
318 | c->port = port; | |
5899ee36 | 319 | c->other_ctx = other_ctx; |
ac02a451 VO |
320 | c->other_port = other_port; |
321 | refcount_set(&c->refcount, 1); | |
322 | ||
5899ee36 | 323 | list_add(&c->list, &ctx->crosschip_links); |
ac02a451 VO |
324 | |
325 | return 0; | |
326 | } | |
327 | ||
d7b1fd52 | 328 | static void dsa_8021q_crosschip_link_del(struct dsa_switch *ds, |
ac02a451 | 329 | struct dsa_8021q_crosschip_link *c, |
ac02a451 VO |
330 | bool *keep) |
331 | { | |
332 | *keep = !refcount_dec_and_test(&c->refcount); | |
333 | ||
334 | if (*keep) | |
335 | return; | |
336 | ||
d7b1fd52 | 337 | dev_dbg(ds->dev, |
ac02a451 | 338 | "deleting crosschip link from port %d to %s port %d\n", |
5899ee36 | 339 | c->port, dev_name(c->other_ctx->ds->dev), c->other_port); |
ac02a451 VO |
340 | |
341 | list_del(&c->list); | |
342 | kfree(c); | |
343 | } | |
344 | ||
345 | /* Make traffic from local port @port be received by remote port @other_port. | |
346 | * This means that our @rx_vid needs to be installed on @other_ds's upstream | |
347 | * and user ports. The user ports should be egress-untagged so that they can | |
348 | * pop the dsa_8021q VLAN. But the @other_upstream can be either egress-tagged | |
349 | * or untagged: it doesn't matter, since it should never egress a frame having | |
350 | * our @rx_vid. | |
351 | */ | |
d7b1fd52 VO |
352 | int dsa_8021q_crosschip_bridge_join(struct dsa_switch *ds, int port, |
353 | struct dsa_switch *other_ds, | |
5899ee36 | 354 | int other_port) |
ac02a451 VO |
355 | { |
356 | /* @other_upstream is how @other_ds reaches us. If we are part | |
357 | * of disjoint trees, then we are probably connected through | |
358 | * our CPU ports. If we're part of the same tree though, we should | |
359 | * probably use dsa_towards_port. | |
360 | */ | |
d7b1fd52 | 361 | int other_upstream = dsa_upstream_port(other_ds, other_port); |
a81a4574 | 362 | int err; |
ac02a451 | 363 | |
d7b1fd52 | 364 | err = dsa_8021q_crosschip_link_add(ds, port, other_ds, other_port); |
a81a4574 VO |
365 | if (err) |
366 | return err; | |
ac02a451 | 367 | |
d7b1fd52 | 368 | err = dsa_8021q_crosschip_link_apply(ds, port, other_ds, |
a81a4574 VO |
369 | other_port, true); |
370 | if (err) | |
371 | return err; | |
ac02a451 | 372 | |
d7b1fd52 | 373 | err = dsa_8021q_crosschip_link_add(ds, port, other_ds, other_upstream); |
a81a4574 VO |
374 | if (err) |
375 | return err; | |
ac02a451 | 376 | |
d7b1fd52 | 377 | return dsa_8021q_crosschip_link_apply(ds, port, other_ds, |
ec5ae610 | 378 | other_upstream, true); |
ac02a451 VO |
379 | } |
380 | EXPORT_SYMBOL_GPL(dsa_8021q_crosschip_bridge_join); | |
381 | ||
d7b1fd52 VO |
382 | int dsa_8021q_crosschip_bridge_leave(struct dsa_switch *ds, int port, |
383 | struct dsa_switch *other_ds, | |
5899ee36 | 384 | int other_port) |
ac02a451 | 385 | { |
d7b1fd52 VO |
386 | struct dsa_8021q_context *other_ctx = other_ds->tag_8021q_ctx; |
387 | int other_upstream = dsa_upstream_port(other_ds, other_port); | |
388 | struct dsa_8021q_context *ctx = ds->tag_8021q_ctx; | |
ac02a451 VO |
389 | struct dsa_8021q_crosschip_link *c, *n; |
390 | ||
5899ee36 VO |
391 | list_for_each_entry_safe(c, n, &ctx->crosschip_links, list) { |
392 | if (c->port == port && c->other_ctx == other_ctx && | |
ac02a451 VO |
393 | (c->other_port == other_port || |
394 | c->other_port == other_upstream)) { | |
ac02a451 VO |
395 | int other_port = c->other_port; |
396 | bool keep; | |
a81a4574 | 397 | int err; |
ac02a451 | 398 | |
d7b1fd52 | 399 | dsa_8021q_crosschip_link_del(ds, c, &keep); |
ac02a451 VO |
400 | if (keep) |
401 | continue; | |
402 | ||
d7b1fd52 VO |
403 | err = dsa_8021q_crosschip_link_apply(ds, port, |
404 | other_ds, | |
a81a4574 VO |
405 | other_port, |
406 | false); | |
407 | if (err) | |
408 | return err; | |
ac02a451 VO |
409 | } |
410 | } | |
411 | ||
412 | return 0; | |
413 | } | |
414 | EXPORT_SYMBOL_GPL(dsa_8021q_crosschip_bridge_leave); | |
415 | ||
d7b1fd52 VO |
416 | int dsa_tag_8021q_register(struct dsa_switch *ds, |
417 | const struct dsa_8021q_ops *ops, | |
418 | __be16 proto) | |
cedf4670 VO |
419 | { |
420 | struct dsa_8021q_context *ctx; | |
421 | ||
422 | ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); | |
423 | if (!ctx) | |
d7b1fd52 | 424 | return -ENOMEM; |
cedf4670 VO |
425 | |
426 | ctx->ops = ops; | |
427 | ctx->proto = proto; | |
428 | ctx->ds = ds; | |
429 | ||
430 | INIT_LIST_HEAD(&ctx->crosschip_links); | |
431 | ||
d7b1fd52 VO |
432 | ds->tag_8021q_ctx = ctx; |
433 | ||
434 | return 0; | |
cedf4670 VO |
435 | } |
436 | EXPORT_SYMBOL_GPL(dsa_tag_8021q_register); | |
437 | ||
d7b1fd52 | 438 | void dsa_tag_8021q_unregister(struct dsa_switch *ds) |
cedf4670 | 439 | { |
d7b1fd52 | 440 | struct dsa_8021q_context *ctx = ds->tag_8021q_ctx; |
cedf4670 VO |
441 | struct dsa_8021q_crosschip_link *c, *n; |
442 | ||
443 | list_for_each_entry_safe(c, n, &ctx->crosschip_links, list) { | |
444 | list_del(&c->list); | |
445 | kfree(c); | |
446 | } | |
447 | ||
d7b1fd52 VO |
448 | ds->tag_8021q_ctx = NULL; |
449 | ||
cedf4670 VO |
450 | kfree(ctx); |
451 | } | |
452 | EXPORT_SYMBOL_GPL(dsa_tag_8021q_unregister); | |
453 | ||
f9bbe447 VO |
454 | struct sk_buff *dsa_8021q_xmit(struct sk_buff *skb, struct net_device *netdev, |
455 | u16 tpid, u16 tci) | |
456 | { | |
457 | /* skb->data points at skb_mac_header, which | |
458 | * is fine for vlan_insert_tag. | |
459 | */ | |
460 | return vlan_insert_tag(skb, htons(tpid), tci); | |
461 | } | |
462 | EXPORT_SYMBOL_GPL(dsa_8021q_xmit); | |
463 | ||
0fac6aa0 | 464 | void dsa_8021q_rcv(struct sk_buff *skb, int *source_port, int *switch_id) |
233697b3 VO |
465 | { |
466 | u16 vid, tci; | |
467 | ||
468 | skb_push_rcsum(skb, ETH_HLEN); | |
469 | if (skb_vlan_tag_present(skb)) { | |
470 | tci = skb_vlan_tag_get(skb); | |
471 | __vlan_hwaccel_clear_tag(skb); | |
472 | } else { | |
473 | __skb_vlan_pop(skb, &tci); | |
474 | } | |
475 | skb_pull_rcsum(skb, ETH_HLEN); | |
476 | ||
477 | vid = tci & VLAN_VID_MASK; | |
478 | ||
479 | *source_port = dsa_8021q_rx_source_port(vid); | |
480 | *switch_id = dsa_8021q_rx_switch_id(vid); | |
233697b3 VO |
481 | skb->priority = (tci & VLAN_PRIO_MASK) >> VLAN_PRIO_SHIFT; |
482 | } | |
483 | EXPORT_SYMBOL_GPL(dsa_8021q_rcv); |