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 | */ | |
f9bbe447 | 9 | #include <linux/if_vlan.h> |
ac02a451 | 10 | #include <linux/dsa/8021q.h> |
f9bbe447 VO |
11 | |
12 | #include "dsa_priv.h" | |
13 | ||
0471dd42 VO |
14 | /* Binary structure of the fake 12-bit VID field (when the TPID is |
15 | * ETH_P_DSA_8021Q): | |
16 | * | |
17 | * | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | | |
18 | * +-----------+-----+-----------------+-----------+-----------------------+ | |
b6ad86e6 | 19 | * | DIR | VBID| SWITCH_ID | VBID | PORT | |
0471dd42 VO |
20 | * +-----------+-----+-----------------+-----------+-----------------------+ |
21 | * | |
22 | * DIR - VID[11:10]: | |
23 | * Direction flags. | |
24 | * * 1 (0b01) for RX VLAN, | |
25 | * * 2 (0b10) for TX VLAN. | |
26 | * These values make the special VIDs of 0, 1 and 4095 to be left | |
27 | * unused by this coding scheme. | |
28 | * | |
0471dd42 | 29 | * SWITCH_ID - VID[8:6]: |
fcee85f1 | 30 | * Index of switch within DSA tree. Must be between 0 and 7. |
0471dd42 | 31 | * |
b6ad86e6 VO |
32 | * VBID - { VID[9], VID[5:4] }: |
33 | * Virtual bridge ID. If between 1 and 7, packet targets the broadcast | |
34 | * domain of a bridge. If transmitted as zero, packet targets a single | |
35 | * port. Field only valid on transmit, must be ignored on receive. | |
0fac6aa0 | 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 | ||
b6ad86e6 VO |
53 | #define DSA_8021Q_VBID_HI_SHIFT 9 |
54 | #define DSA_8021Q_VBID_HI_MASK GENMASK(9, 9) | |
55 | #define DSA_8021Q_VBID_LO_SHIFT 4 | |
56 | #define DSA_8021Q_VBID_LO_MASK GENMASK(5, 4) | |
57 | #define DSA_8021Q_VBID_HI(x) (((x) & GENMASK(2, 2)) >> 2) | |
58 | #define DSA_8021Q_VBID_LO(x) ((x) & GENMASK(1, 0)) | |
59 | #define DSA_8021Q_VBID(x) \ | |
60 | (((DSA_8021Q_VBID_LO(x) << DSA_8021Q_VBID_LO_SHIFT) & \ | |
61 | DSA_8021Q_VBID_LO_MASK) | \ | |
62 | ((DSA_8021Q_VBID_HI(x) << DSA_8021Q_VBID_HI_SHIFT) & \ | |
63 | DSA_8021Q_VBID_HI_MASK)) | |
64 | ||
0471dd42 VO |
65 | #define DSA_8021Q_PORT_SHIFT 0 |
66 | #define DSA_8021Q_PORT_MASK GENMASK(3, 0) | |
67 | #define DSA_8021Q_PORT(x) (((x) << DSA_8021Q_PORT_SHIFT) & \ | |
68 | DSA_8021Q_PORT_MASK) | |
f9bbe447 | 69 | |
3f9bb030 | 70 | u16 dsa_8021q_bridge_tx_fwd_offload_vid(unsigned int bridge_num) |
b6ad86e6 | 71 | { |
3f9bb030 VO |
72 | /* The VBID value of 0 is reserved for precise TX, but it is also |
73 | * reserved/invalid for the bridge_num, so all is well. | |
74 | */ | |
75 | return DSA_8021Q_DIR_TX | DSA_8021Q_VBID(bridge_num); | |
b6ad86e6 VO |
76 | } |
77 | EXPORT_SYMBOL_GPL(dsa_8021q_bridge_tx_fwd_offload_vid); | |
78 | ||
f9bbe447 VO |
79 | /* Returns the VID to be inserted into the frame from xmit for switch steering |
80 | * instructions on egress. Encodes switch ID and port ID. | |
81 | */ | |
992e5cc7 | 82 | u16 dsa_tag_8021q_tx_vid(const struct dsa_port *dp) |
f9bbe447 | 83 | { |
992e5cc7 VO |
84 | return DSA_8021Q_DIR_TX | DSA_8021Q_SWITCH_ID(dp->ds->index) | |
85 | DSA_8021Q_PORT(dp->index); | |
f9bbe447 | 86 | } |
992e5cc7 | 87 | EXPORT_SYMBOL_GPL(dsa_tag_8021q_tx_vid); |
f9bbe447 VO |
88 | |
89 | /* Returns the VID that will be installed as pvid for this switch port, sent as | |
90 | * tagged egress towards the CPU port and decoded by the rcv function. | |
91 | */ | |
992e5cc7 | 92 | u16 dsa_tag_8021q_rx_vid(const struct dsa_port *dp) |
f9bbe447 | 93 | { |
992e5cc7 VO |
94 | return DSA_8021Q_DIR_RX | DSA_8021Q_SWITCH_ID(dp->ds->index) | |
95 | DSA_8021Q_PORT(dp->index); | |
f9bbe447 | 96 | } |
992e5cc7 | 97 | EXPORT_SYMBOL_GPL(dsa_tag_8021q_rx_vid); |
f9bbe447 VO |
98 | |
99 | /* Returns the decoded switch ID from the RX VID. */ | |
100 | int dsa_8021q_rx_switch_id(u16 vid) | |
101 | { | |
0471dd42 | 102 | return (vid & DSA_8021Q_SWITCH_ID_MASK) >> DSA_8021Q_SWITCH_ID_SHIFT; |
f9bbe447 VO |
103 | } |
104 | EXPORT_SYMBOL_GPL(dsa_8021q_rx_switch_id); | |
105 | ||
106 | /* Returns the decoded port ID from the RX VID. */ | |
107 | int dsa_8021q_rx_source_port(u16 vid) | |
108 | { | |
0471dd42 | 109 | return (vid & DSA_8021Q_PORT_MASK) >> DSA_8021Q_PORT_SHIFT; |
f9bbe447 VO |
110 | } |
111 | EXPORT_SYMBOL_GPL(dsa_8021q_rx_source_port); | |
112 | ||
91495f21 VO |
113 | /* Returns the decoded VBID from the RX VID. */ |
114 | static int dsa_tag_8021q_rx_vbid(u16 vid) | |
115 | { | |
116 | u16 vbid_hi = (vid & DSA_8021Q_VBID_HI_MASK) >> DSA_8021Q_VBID_HI_SHIFT; | |
117 | u16 vbid_lo = (vid & DSA_8021Q_VBID_LO_MASK) >> DSA_8021Q_VBID_LO_SHIFT; | |
118 | ||
119 | return (vbid_hi << 2) | vbid_lo; | |
120 | } | |
121 | ||
9c7caf28 VO |
122 | bool vid_is_dsa_8021q_rxvlan(u16 vid) |
123 | { | |
124 | return (vid & DSA_8021Q_DIR_MASK) == DSA_8021Q_DIR_RX; | |
125 | } | |
126 | EXPORT_SYMBOL_GPL(vid_is_dsa_8021q_rxvlan); | |
127 | ||
128 | bool vid_is_dsa_8021q_txvlan(u16 vid) | |
129 | { | |
130 | return (vid & DSA_8021Q_DIR_MASK) == DSA_8021Q_DIR_TX; | |
131 | } | |
132 | EXPORT_SYMBOL_GPL(vid_is_dsa_8021q_txvlan); | |
133 | ||
1f66b0f0 VO |
134 | bool vid_is_dsa_8021q(u16 vid) |
135 | { | |
9c7caf28 | 136 | return vid_is_dsa_8021q_rxvlan(vid) || vid_is_dsa_8021q_txvlan(vid); |
1f66b0f0 VO |
137 | } |
138 | EXPORT_SYMBOL_GPL(vid_is_dsa_8021q); | |
139 | ||
c64b9c05 VO |
140 | static struct dsa_tag_8021q_vlan * |
141 | dsa_tag_8021q_vlan_find(struct dsa_8021q_context *ctx, int port, u16 vid) | |
142 | { | |
143 | struct dsa_tag_8021q_vlan *v; | |
144 | ||
145 | list_for_each_entry(v, &ctx->vlans, list) | |
146 | if (v->vid == vid && v->port == port) | |
147 | return v; | |
148 | ||
149 | return NULL; | |
150 | } | |
151 | ||
fac6abd5 VO |
152 | static int dsa_port_do_tag_8021q_vlan_add(struct dsa_port *dp, u16 vid, |
153 | u16 flags) | |
5f33183b | 154 | { |
fac6abd5 VO |
155 | struct dsa_8021q_context *ctx = dp->ds->tag_8021q_ctx; |
156 | struct dsa_switch *ds = dp->ds; | |
c64b9c05 | 157 | struct dsa_tag_8021q_vlan *v; |
fac6abd5 | 158 | int port = dp->index; |
c64b9c05 VO |
159 | int err; |
160 | ||
161 | /* No need to bother with refcounting for user ports */ | |
162 | if (!(dsa_port_is_cpu(dp) || dsa_port_is_dsa(dp))) | |
163 | return ds->ops->tag_8021q_vlan_add(ds, port, vid, flags); | |
164 | ||
165 | v = dsa_tag_8021q_vlan_find(ctx, port, vid); | |
166 | if (v) { | |
167 | refcount_inc(&v->refcount); | |
168 | return 0; | |
169 | } | |
170 | ||
171 | v = kzalloc(sizeof(*v), GFP_KERNEL); | |
172 | if (!v) | |
173 | return -ENOMEM; | |
174 | ||
175 | err = ds->ops->tag_8021q_vlan_add(ds, port, vid, flags); | |
176 | if (err) { | |
177 | kfree(v); | |
178 | return err; | |
179 | } | |
180 | ||
181 | v->vid = vid; | |
182 | v->port = port; | |
183 | refcount_set(&v->refcount, 1); | |
184 | list_add_tail(&v->list, &ctx->vlans); | |
185 | ||
186 | return 0; | |
187 | } | |
188 | ||
fac6abd5 | 189 | static int dsa_port_do_tag_8021q_vlan_del(struct dsa_port *dp, u16 vid) |
c64b9c05 | 190 | { |
fac6abd5 VO |
191 | struct dsa_8021q_context *ctx = dp->ds->tag_8021q_ctx; |
192 | struct dsa_switch *ds = dp->ds; | |
c64b9c05 | 193 | struct dsa_tag_8021q_vlan *v; |
fac6abd5 | 194 | int port = dp->index; |
c64b9c05 VO |
195 | int err; |
196 | ||
197 | /* No need to bother with refcounting for user ports */ | |
198 | if (!(dsa_port_is_cpu(dp) || dsa_port_is_dsa(dp))) | |
199 | return ds->ops->tag_8021q_vlan_del(ds, port, vid); | |
200 | ||
201 | v = dsa_tag_8021q_vlan_find(ctx, port, vid); | |
202 | if (!v) | |
203 | return -ENOENT; | |
204 | ||
205 | if (!refcount_dec_and_test(&v->refcount)) | |
206 | return 0; | |
207 | ||
208 | err = ds->ops->tag_8021q_vlan_del(ds, port, vid); | |
209 | if (err) { | |
210 | refcount_inc(&v->refcount); | |
211 | return err; | |
212 | } | |
213 | ||
214 | list_del(&v->list); | |
215 | kfree(v); | |
216 | ||
217 | return 0; | |
218 | } | |
5f33183b | 219 | |
c64b9c05 | 220 | static bool |
fac6abd5 VO |
221 | dsa_port_tag_8021q_vlan_match(struct dsa_port *dp, |
222 | struct dsa_notifier_tag_8021q_vlan_info *info) | |
c64b9c05 | 223 | { |
fac6abd5 VO |
224 | struct dsa_switch *ds = dp->ds; |
225 | ||
226 | if (dsa_port_is_dsa(dp) || dsa_port_is_cpu(dp)) | |
c64b9c05 VO |
227 | return true; |
228 | ||
229 | if (ds->dst->index == info->tree_index && ds->index == info->sw_index) | |
fac6abd5 | 230 | return dp->index == info->port; |
c64b9c05 VO |
231 | |
232 | return false; | |
233 | } | |
234 | ||
235 | int dsa_switch_tag_8021q_vlan_add(struct dsa_switch *ds, | |
236 | struct dsa_notifier_tag_8021q_vlan_info *info) | |
237 | { | |
fac6abd5 VO |
238 | struct dsa_port *dp; |
239 | int err; | |
c64b9c05 VO |
240 | |
241 | /* Since we use dsa_broadcast(), there might be other switches in other | |
242 | * trees which don't support tag_8021q, so don't return an error. | |
243 | * Or they might even support tag_8021q but have not registered yet to | |
244 | * use it (maybe they use another tagger currently). | |
245 | */ | |
246 | if (!ds->ops->tag_8021q_vlan_add || !ds->tag_8021q_ctx) | |
247 | return 0; | |
5f33183b | 248 | |
fac6abd5 VO |
249 | dsa_switch_for_each_port(dp, ds) { |
250 | if (dsa_port_tag_8021q_vlan_match(dp, info)) { | |
c64b9c05 VO |
251 | u16 flags = 0; |
252 | ||
fac6abd5 | 253 | if (dsa_port_is_user(dp)) |
c64b9c05 VO |
254 | flags |= BRIDGE_VLAN_INFO_UNTAGGED; |
255 | ||
91495f21 | 256 | /* Standalone VLANs are PVIDs */ |
c64b9c05 VO |
257 | if (vid_is_dsa_8021q_rxvlan(info->vid) && |
258 | dsa_8021q_rx_switch_id(info->vid) == ds->index && | |
fac6abd5 | 259 | dsa_8021q_rx_source_port(info->vid) == dp->index) |
c64b9c05 VO |
260 | flags |= BRIDGE_VLAN_INFO_PVID; |
261 | ||
91495f21 VO |
262 | /* And bridging VLANs are PVIDs too on user ports */ |
263 | if (dsa_tag_8021q_rx_vbid(info->vid) && | |
264 | dsa_port_is_user(dp)) | |
265 | flags |= BRIDGE_VLAN_INFO_PVID; | |
266 | ||
fac6abd5 VO |
267 | err = dsa_port_do_tag_8021q_vlan_add(dp, info->vid, |
268 | flags); | |
c64b9c05 VO |
269 | if (err) |
270 | return err; | |
271 | } | |
272 | } | |
273 | ||
274 | return 0; | |
275 | } | |
276 | ||
277 | int dsa_switch_tag_8021q_vlan_del(struct dsa_switch *ds, | |
278 | struct dsa_notifier_tag_8021q_vlan_info *info) | |
279 | { | |
fac6abd5 VO |
280 | struct dsa_port *dp; |
281 | int err; | |
c64b9c05 VO |
282 | |
283 | if (!ds->ops->tag_8021q_vlan_del || !ds->tag_8021q_ctx) | |
284 | return 0; | |
285 | ||
fac6abd5 VO |
286 | dsa_switch_for_each_port(dp, ds) { |
287 | if (dsa_port_tag_8021q_vlan_match(dp, info)) { | |
288 | err = dsa_port_do_tag_8021q_vlan_del(dp, info->vid); | |
c64b9c05 VO |
289 | if (err) |
290 | return err; | |
291 | } | |
292 | } | |
293 | ||
294 | return 0; | |
5f33183b VO |
295 | } |
296 | ||
f9bbe447 VO |
297 | /* RX VLAN tagging (left) and TX VLAN tagging (right) setup shown for a single |
298 | * front-panel switch port (here swp0). | |
299 | * | |
300 | * Port identification through VLAN (802.1Q) tags has different requirements | |
301 | * for it to work effectively: | |
302 | * - On RX (ingress from network): each front-panel port must have a pvid | |
303 | * that uniquely identifies it, and the egress of this pvid must be tagged | |
304 | * towards the CPU port, so that software can recover the source port based | |
305 | * on the VID in the frame. But this would only work for standalone ports; | |
306 | * if bridged, this VLAN setup would break autonomous forwarding and would | |
307 | * force all switched traffic to pass through the CPU. So we must also make | |
308 | * the other front-panel ports members of this VID we're adding, albeit | |
309 | * we're not making it their PVID (they'll still have their own). | |
f9bbe447 VO |
310 | * - On TX (ingress from CPU and towards network) we are faced with a problem. |
311 | * If we were to tag traffic (from within DSA) with the port's pvid, all | |
312 | * would be well, assuming the switch ports were standalone. Frames would | |
313 | * have no choice but to be directed towards the correct front-panel port. | |
314 | * But because we also want the RX VLAN to not break bridging, then | |
315 | * inevitably that means that we have to give them a choice (of what | |
316 | * front-panel port to go out on), and therefore we cannot steer traffic | |
317 | * based on the RX VID. So what we do is simply install one more VID on the | |
318 | * front-panel and CPU ports, and profit off of the fact that steering will | |
319 | * work just by virtue of the fact that there is only one other port that's | |
320 | * a member of the VID we're tagging the traffic with - the desired one. | |
321 | * | |
322 | * So at the end, each front-panel port will have one RX VID (also the PVID), | |
e19cc13c VO |
323 | * the RX VID of all other front-panel ports that are in the same bridge, and |
324 | * one TX VID. Whereas the CPU port will have the RX and TX VIDs of all | |
325 | * front-panel ports, and on top of that, is also tagged-input and | |
326 | * tagged-output (VLAN trunk). | |
f9bbe447 VO |
327 | * |
328 | * CPU port CPU port | |
329 | * +-------------+-----+-------------+ +-------------+-----+-------------+ | |
330 | * | RX VID | | | | TX VID | | | | |
331 | * | of swp0 | | | | of swp0 | | | | |
332 | * | +-----+ | | +-----+ | | |
333 | * | ^ T | | | Tagged | | |
334 | * | | | | | ingress | | |
335 | * | +-------+---+---+-------+ | | +-----------+ | | |
336 | * | | | | | | | | Untagged | | |
337 | * | | U v U v U v | | v egress | | |
338 | * | +-----+ +-----+ +-----+ +-----+ | | +-----+ +-----+ +-----+ +-----+ | | |
339 | * | | | | | | | | | | | | | | | | | | | | | |
340 | * | |PVID | | | | | | | | | | | | | | | | | | | |
341 | * +-+-----+-+-----+-+-----+-+-----+-+ +-+-----+-+-----+-+-----+-+-----+-+ | |
342 | * swp0 swp1 swp2 swp3 swp0 swp1 swp2 swp3 | |
343 | */ | |
91495f21 VO |
344 | int dsa_tag_8021q_bridge_join(struct dsa_switch *ds, int port, |
345 | struct dsa_bridge bridge) | |
e19cc13c | 346 | { |
91495f21 VO |
347 | struct dsa_port *dp = dsa_to_port(ds, port); |
348 | u16 standalone_vid, bridge_vid; | |
fac6abd5 | 349 | int err; |
e19cc13c | 350 | |
91495f21 VO |
351 | /* Delete the standalone VLAN of the port and replace it with a |
352 | * bridging VLAN | |
353 | */ | |
354 | standalone_vid = dsa_tag_8021q_rx_vid(dp); | |
355 | bridge_vid = dsa_8021q_bridge_tx_fwd_offload_vid(bridge.num); | |
e19cc13c | 356 | |
91495f21 VO |
357 | err = dsa_port_tag_8021q_vlan_add(dp, bridge_vid, true); |
358 | if (err) | |
359 | return err; | |
e19cc13c | 360 | |
91495f21 | 361 | dsa_port_tag_8021q_vlan_del(dp, standalone_vid, false); |
e19cc13c VO |
362 | |
363 | return 0; | |
364 | } | |
91495f21 | 365 | EXPORT_SYMBOL_GPL(dsa_tag_8021q_bridge_join); |
e19cc13c | 366 | |
91495f21 VO |
367 | void dsa_tag_8021q_bridge_leave(struct dsa_switch *ds, int port, |
368 | struct dsa_bridge bridge) | |
e19cc13c | 369 | { |
91495f21 VO |
370 | struct dsa_port *dp = dsa_to_port(ds, port); |
371 | u16 standalone_vid, bridge_vid; | |
372 | int err; | |
e19cc13c | 373 | |
91495f21 VO |
374 | /* Delete the bridging VLAN of the port and replace it with a |
375 | * standalone VLAN | |
376 | */ | |
377 | standalone_vid = dsa_tag_8021q_rx_vid(dp); | |
378 | bridge_vid = dsa_8021q_bridge_tx_fwd_offload_vid(bridge.num); | |
e19cc13c | 379 | |
91495f21 VO |
380 | err = dsa_port_tag_8021q_vlan_add(dp, standalone_vid, false); |
381 | if (err) { | |
382 | dev_err(ds->dev, | |
383 | "Failed to delete tag_8021q standalone VLAN %d from port %d: %pe\n", | |
384 | standalone_vid, port, ERR_PTR(err)); | |
e19cc13c VO |
385 | } |
386 | ||
91495f21 | 387 | dsa_port_tag_8021q_vlan_del(dp, bridge_vid, true); |
b6ad86e6 | 388 | } |
91495f21 | 389 | EXPORT_SYMBOL_GPL(dsa_tag_8021q_bridge_leave); |
b6ad86e6 | 390 | |
e19cc13c | 391 | /* Set up a port's tag_8021q RX and TX VLAN for standalone mode operation */ |
c64b9c05 | 392 | static int dsa_tag_8021q_port_setup(struct dsa_switch *ds, int port) |
f9bbe447 | 393 | { |
d7b1fd52 | 394 | struct dsa_8021q_context *ctx = ds->tag_8021q_ctx; |
c64b9c05 | 395 | struct dsa_port *dp = dsa_to_port(ds, port); |
992e5cc7 VO |
396 | u16 rx_vid = dsa_tag_8021q_rx_vid(dp); |
397 | u16 tx_vid = dsa_tag_8021q_tx_vid(dp); | |
bbed0bbd | 398 | struct net_device *master; |
e19cc13c | 399 | int err; |
f9bbe447 VO |
400 | |
401 | /* The CPU port is implicitly configured by | |
402 | * configuring the front-panel ports | |
403 | */ | |
c64b9c05 | 404 | if (!dsa_port_is_user(dp)) |
f9bbe447 VO |
405 | return 0; |
406 | ||
c64b9c05 | 407 | master = dp->cpu_dp->master; |
bbed0bbd | 408 | |
f9bbe447 VO |
409 | /* Add this user port's RX VID to the membership list of all others |
410 | * (including itself). This is so that bridging will not be hindered. | |
411 | * L2 forwarding rules still take precedence when there are no VLAN | |
412 | * restrictions, so there are no concerns about leaking traffic. | |
413 | */ | |
b2b89133 | 414 | err = dsa_port_tag_8021q_vlan_add(dp, rx_vid, false); |
d34d2baa | 415 | if (err) { |
d7b1fd52 | 416 | dev_err(ds->dev, |
69ebb370 VO |
417 | "Failed to apply RX VID %d to port %d: %pe\n", |
418 | rx_vid, port, ERR_PTR(err)); | |
d34d2baa IC |
419 | return err; |
420 | } | |
421 | ||
0fac6aa0 | 422 | /* Add @rx_vid to the master's RX filter. */ |
c64b9c05 | 423 | vlan_vid_add(master, ctx->proto, rx_vid); |
bbed0bbd | 424 | |
f9bbe447 | 425 | /* Finally apply the TX VID on this port and on the CPU port */ |
b2b89133 | 426 | err = dsa_port_tag_8021q_vlan_add(dp, tx_vid, false); |
f9bbe447 | 427 | if (err) { |
d7b1fd52 | 428 | dev_err(ds->dev, |
69ebb370 VO |
429 | "Failed to apply TX VID %d on port %d: %pe\n", |
430 | tx_vid, port, ERR_PTR(err)); | |
f9bbe447 VO |
431 | return err; |
432 | } | |
f9bbe447 | 433 | |
5f33183b | 434 | return err; |
f9bbe447 | 435 | } |
7e092af2 | 436 | |
c64b9c05 VO |
437 | static void dsa_tag_8021q_port_teardown(struct dsa_switch *ds, int port) |
438 | { | |
439 | struct dsa_8021q_context *ctx = ds->tag_8021q_ctx; | |
440 | struct dsa_port *dp = dsa_to_port(ds, port); | |
992e5cc7 VO |
441 | u16 rx_vid = dsa_tag_8021q_rx_vid(dp); |
442 | u16 tx_vid = dsa_tag_8021q_tx_vid(dp); | |
c64b9c05 VO |
443 | struct net_device *master; |
444 | ||
445 | /* The CPU port is implicitly configured by | |
446 | * configuring the front-panel ports | |
447 | */ | |
448 | if (!dsa_port_is_user(dp)) | |
449 | return; | |
450 | ||
451 | master = dp->cpu_dp->master; | |
452 | ||
724395f4 | 453 | dsa_port_tag_8021q_vlan_del(dp, rx_vid, false); |
c64b9c05 VO |
454 | |
455 | vlan_vid_del(master, ctx->proto, rx_vid); | |
456 | ||
724395f4 | 457 | dsa_port_tag_8021q_vlan_del(dp, tx_vid, false); |
c64b9c05 VO |
458 | } |
459 | ||
460 | static int dsa_tag_8021q_setup(struct dsa_switch *ds) | |
7e092af2 | 461 | { |
a81a4574 | 462 | int err, port; |
7e092af2 | 463 | |
bbed0bbd VO |
464 | ASSERT_RTNL(); |
465 | ||
d7b1fd52 | 466 | for (port = 0; port < ds->num_ports; port++) { |
c64b9c05 | 467 | err = dsa_tag_8021q_port_setup(ds, port); |
a81a4574 | 468 | if (err < 0) { |
d7b1fd52 | 469 | dev_err(ds->dev, |
69ebb370 VO |
470 | "Failed to setup VLAN tagging for port %d: %pe\n", |
471 | port, ERR_PTR(err)); | |
a81a4574 | 472 | return err; |
7e092af2 VO |
473 | } |
474 | } | |
475 | ||
476 | return 0; | |
477 | } | |
f9bbe447 | 478 | |
c64b9c05 | 479 | static void dsa_tag_8021q_teardown(struct dsa_switch *ds) |
ac02a451 | 480 | { |
c64b9c05 | 481 | int port; |
ac02a451 | 482 | |
c64b9c05 | 483 | ASSERT_RTNL(); |
ac02a451 | 484 | |
c64b9c05 VO |
485 | for (port = 0; port < ds->num_ports; port++) |
486 | dsa_tag_8021q_port_teardown(ds, port); | |
ac02a451 | 487 | } |
ac02a451 | 488 | |
5da11eb4 | 489 | int dsa_tag_8021q_register(struct dsa_switch *ds, __be16 proto) |
cedf4670 VO |
490 | { |
491 | struct dsa_8021q_context *ctx; | |
492 | ||
493 | ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); | |
494 | if (!ctx) | |
d7b1fd52 | 495 | return -ENOMEM; |
cedf4670 | 496 | |
cedf4670 VO |
497 | ctx->proto = proto; |
498 | ctx->ds = ds; | |
499 | ||
c64b9c05 | 500 | INIT_LIST_HEAD(&ctx->vlans); |
cedf4670 | 501 | |
d7b1fd52 VO |
502 | ds->tag_8021q_ctx = ctx; |
503 | ||
c64b9c05 | 504 | return dsa_tag_8021q_setup(ds); |
cedf4670 VO |
505 | } |
506 | EXPORT_SYMBOL_GPL(dsa_tag_8021q_register); | |
507 | ||
d7b1fd52 | 508 | void dsa_tag_8021q_unregister(struct dsa_switch *ds) |
cedf4670 | 509 | { |
d7b1fd52 | 510 | struct dsa_8021q_context *ctx = ds->tag_8021q_ctx; |
c64b9c05 | 511 | struct dsa_tag_8021q_vlan *v, *n; |
328621f6 | 512 | |
c64b9c05 | 513 | dsa_tag_8021q_teardown(ds); |
cedf4670 | 514 | |
c64b9c05 VO |
515 | list_for_each_entry_safe(v, n, &ctx->vlans, list) { |
516 | list_del(&v->list); | |
517 | kfree(v); | |
cedf4670 VO |
518 | } |
519 | ||
d7b1fd52 VO |
520 | ds->tag_8021q_ctx = NULL; |
521 | ||
cedf4670 VO |
522 | kfree(ctx); |
523 | } | |
524 | EXPORT_SYMBOL_GPL(dsa_tag_8021q_unregister); | |
525 | ||
f9bbe447 VO |
526 | struct sk_buff *dsa_8021q_xmit(struct sk_buff *skb, struct net_device *netdev, |
527 | u16 tpid, u16 tci) | |
528 | { | |
529 | /* skb->data points at skb_mac_header, which | |
530 | * is fine for vlan_insert_tag. | |
531 | */ | |
532 | return vlan_insert_tag(skb, htons(tpid), tci); | |
533 | } | |
534 | EXPORT_SYMBOL_GPL(dsa_8021q_xmit); | |
535 | ||
0fac6aa0 | 536 | void dsa_8021q_rcv(struct sk_buff *skb, int *source_port, int *switch_id) |
233697b3 VO |
537 | { |
538 | u16 vid, tci; | |
539 | ||
233697b3 VO |
540 | if (skb_vlan_tag_present(skb)) { |
541 | tci = skb_vlan_tag_get(skb); | |
542 | __vlan_hwaccel_clear_tag(skb); | |
543 | } else { | |
c8620335 | 544 | skb_push_rcsum(skb, ETH_HLEN); |
233697b3 | 545 | __skb_vlan_pop(skb, &tci); |
c8620335 | 546 | skb_pull_rcsum(skb, ETH_HLEN); |
233697b3 | 547 | } |
233697b3 VO |
548 | |
549 | vid = tci & VLAN_VID_MASK; | |
550 | ||
551 | *source_port = dsa_8021q_rx_source_port(vid); | |
552 | *switch_id = dsa_8021q_rx_switch_id(vid); | |
233697b3 VO |
553 | skb->priority = (tci & VLAN_PRIO_MASK) >> VLAN_PRIO_SHIFT; |
554 | } | |
555 | EXPORT_SYMBOL_GPL(dsa_8021q_rcv); |