Commit | Line | Data |
---|---|---|
dfedd3b6 | 1 | // SPDX-License-Identifier: GPL-2.0+ |
cf85d08f | 2 | /* |
469ee5fe | 3 | * Regular and Ethertype DSA tagging |
e84665c9 | 4 | * Copyright (c) 2008-2009 Marvell Semiconductor |
469ee5fe TW |
5 | * |
6 | * Regular DSA | |
7 | * ----------- | |
8 | ||
9 | * For untagged (in 802.1Q terms) packets, the switch will splice in | |
10 | * the tag between the SA and the ethertype of the original | |
11 | * packet. Tagged frames will instead have their outermost .1Q tag | |
12 | * converted to a DSA tag. It expects the same layout when receiving | |
13 | * packets from the CPU. | |
14 | * | |
15 | * Example: | |
16 | * | |
17 | * .----.----.----.--------- | |
18 | * Pu: | DA | SA | ET | Payload ... | |
19 | * '----'----'----'--------- | |
20 | * 6 6 2 N | |
21 | * .----.----.--------.-----.----.--------- | |
22 | * Pt: | DA | SA | 0x8100 | TCI | ET | Payload ... | |
23 | * '----'----'--------'-----'----'--------- | |
24 | * 6 6 2 2 2 N | |
25 | * .----.----.-----.----.--------- | |
26 | * Pd: | DA | SA | DSA | ET | Payload ... | |
27 | * '----'----'-----'----'--------- | |
28 | * 6 6 4 2 N | |
29 | * | |
30 | * No matter if a packet is received untagged (Pu) or tagged (Pt), | |
31 | * they will both have the same layout (Pd) when they are sent to the | |
32 | * CPU. This is done by ignoring 802.3, replacing the ethertype field | |
33 | * with more metadata, among which is a bit to signal if the original | |
34 | * packet was tagged or not. | |
35 | * | |
36 | * Ethertype DSA | |
37 | * ------------- | |
38 | * Uses the exact same tag format as regular DSA, but also includes a | |
39 | * proper ethertype field (which the mv88e6xxx driver sets to | |
40 | * ETH_P_EDSA/0xdada) followed by two zero bytes: | |
41 | * | |
42 | * .----.----.--------.--------.-----.----.--------- | |
43 | * | DA | SA | 0xdada | 0x0000 | DSA | ET | Payload ... | |
44 | * '----'----'--------'--------'-----'----'--------- | |
45 | * 6 6 2 2 4 2 N | |
cf85d08f LB |
46 | */ |
47 | ||
48 | #include <linux/etherdevice.h> | |
49 | #include <linux/list.h> | |
5a0e3ad6 | 50 | #include <linux/slab.h> |
ea5dd34b | 51 | |
cf85d08f LB |
52 | #include "dsa_priv.h" |
53 | ||
54 | #define DSA_HLEN 4 | |
55 | ||
469ee5fe TW |
56 | /** |
57 | * enum dsa_cmd - DSA Command | |
58 | * @DSA_CMD_TO_CPU: Set on packets that were trapped or mirrored to | |
59 | * the CPU port. This is needed to implement control protocols, | |
60 | * e.g. STP and LLDP, that must not allow those control packets to | |
61 | * be switched according to the normal rules. | |
62 | * @DSA_CMD_FROM_CPU: Used by the CPU to send a packet to a specific | |
63 | * port, ignoring all the barriers that the switch normally | |
64 | * enforces (VLANs, STP port states etc.). No source address | |
65 | * learning takes place. "sudo send packet" | |
66 | * @DSA_CMD_TO_SNIFFER: Set on the copies of packets that matched some | |
67 | * user configured ingress or egress monitor criteria. These are | |
68 | * forwarded by the switch tree to the user configured ingress or | |
69 | * egress monitor port, which can be set to the CPU port or a | |
70 | * regular port. If the destination is a regular port, the tag | |
71 | * will be removed before egressing the port. If the destination | |
72 | * is the CPU port, the tag will not be removed. | |
73 | * @DSA_CMD_FORWARD: This tag is used on all bulk traffic passing | |
74 | * through the switch tree, including the flows that are directed | |
75 | * towards the CPU. Its device/port tuple encodes the original | |
76 | * source port on which the packet ingressed. It can also be used | |
77 | * on transmit by the CPU to defer the forwarding decision to the | |
78 | * hardware, based on the current config of PVT/VTU/ATU | |
79 | * etc. Source address learning takes places if enabled on the | |
80 | * receiving DSA/CPU port. | |
81 | */ | |
82 | enum dsa_cmd { | |
83 | DSA_CMD_TO_CPU = 0, | |
84 | DSA_CMD_FROM_CPU = 1, | |
85 | DSA_CMD_TO_SNIFFER = 2, | |
86 | DSA_CMD_FORWARD = 3 | |
87 | }; | |
e468d141 | 88 | |
469ee5fe TW |
89 | /** |
90 | * enum dsa_code - TO_CPU Code | |
91 | * | |
92 | * @DSA_CODE_MGMT_TRAP: DA was classified as a management | |
93 | * address. Typical examples include STP BPDUs and LLDP. | |
94 | * @DSA_CODE_FRAME2REG: Response to a "remote management" request. | |
95 | * @DSA_CODE_IGMP_MLD_TRAP: IGMP/MLD signaling. | |
96 | * @DSA_CODE_POLICY_TRAP: Frame matched some policy configuration on | |
97 | * the device. Typical examples are matching on DA/SA/VID and DHCP | |
98 | * snooping. | |
99 | * @DSA_CODE_ARP_MIRROR: The name says it all really. | |
100 | * @DSA_CODE_POLICY_MIRROR: Same as @DSA_CODE_POLICY_TRAP, but the | |
101 | * particular policy was set to trigger a mirror instead of a | |
102 | * trap. | |
103 | * @DSA_CODE_RESERVED_6: Unused on all devices up to at least 6393X. | |
104 | * @DSA_CODE_RESERVED_7: Unused on all devices up to at least 6393X. | |
105 | * | |
106 | * A 3-bit code is used to relay why a particular frame was sent to | |
107 | * the CPU. We only use this to determine if the packet was mirrored | |
108 | * or trapped, i.e. whether the packet has been forwarded by hardware | |
109 | * or not. | |
110 | * | |
111 | * This is the superset of all possible codes. Any particular device | |
112 | * may only implement a subset. | |
113 | */ | |
114 | enum dsa_code { | |
115 | DSA_CODE_MGMT_TRAP = 0, | |
116 | DSA_CODE_FRAME2REG = 1, | |
117 | DSA_CODE_IGMP_MLD_TRAP = 2, | |
118 | DSA_CODE_POLICY_TRAP = 3, | |
119 | DSA_CODE_ARP_MIRROR = 4, | |
120 | DSA_CODE_POLICY_MIRROR = 5, | |
121 | DSA_CODE_RESERVED_6 = 6, | |
122 | DSA_CODE_RESERVED_7 = 7 | |
123 | }; | |
e468d141 | 124 | |
469ee5fe TW |
125 | static struct sk_buff *dsa_xmit_ll(struct sk_buff *skb, struct net_device *dev, |
126 | u8 extra) | |
cf85d08f | 127 | { |
d945097b | 128 | struct dsa_port *dp = dsa_slave_to_port(dev); |
cf85d08f LB |
129 | u8 *dsa_header; |
130 | ||
cf85d08f | 131 | if (skb->protocol == htons(ETH_P_8021Q)) { |
469ee5fe TW |
132 | if (extra) { |
133 | skb_push(skb, extra); | |
134 | memmove(skb->data, skb->data + extra, 2 * ETH_ALEN); | |
135 | } | |
136 | ||
13f49b6f | 137 | /* Construct tagged FROM_CPU DSA tag from 802.1Q tag. */ |
469ee5fe TW |
138 | dsa_header = skb->data + 2 * ETH_ALEN + extra; |
139 | dsa_header[0] = (DSA_CMD_FROM_CPU << 6) | 0x20 | dp->ds->index; | |
d945097b | 140 | dsa_header[1] = dp->index << 3; |
cf85d08f | 141 | |
13f49b6f | 142 | /* Move CFI field from byte 2 to byte 1. */ |
cf85d08f LB |
143 | if (dsa_header[2] & 0x10) { |
144 | dsa_header[1] |= 0x01; | |
145 | dsa_header[2] &= ~0x10; | |
146 | } | |
147 | } else { | |
469ee5fe TW |
148 | skb_push(skb, DSA_HLEN + extra); |
149 | memmove(skb->data, skb->data + DSA_HLEN + extra, 2 * ETH_ALEN); | |
cf85d08f | 150 | |
13f49b6f | 151 | /* Construct untagged FROM_CPU DSA tag. */ |
469ee5fe TW |
152 | dsa_header = skb->data + 2 * ETH_ALEN + extra; |
153 | dsa_header[0] = (DSA_CMD_FROM_CPU << 6) | dp->ds->index; | |
d945097b | 154 | dsa_header[1] = dp->index << 3; |
cf85d08f LB |
155 | dsa_header[2] = 0x00; |
156 | dsa_header[3] = 0x00; | |
157 | } | |
158 | ||
4ed70ce9 | 159 | return skb; |
cf85d08f LB |
160 | } |
161 | ||
469ee5fe TW |
162 | static struct sk_buff *dsa_rcv_ll(struct sk_buff *skb, struct net_device *dev, |
163 | u8 extra) | |
cf85d08f | 164 | { |
469ee5fe | 165 | int source_device, source_port; |
5b60dadb | 166 | bool trunk = false; |
469ee5fe TW |
167 | enum dsa_code code; |
168 | enum dsa_cmd cmd; | |
cf85d08f | 169 | u8 *dsa_header; |
cf85d08f | 170 | |
13f49b6f | 171 | /* The ethertype field is part of the DSA header. */ |
cf85d08f LB |
172 | dsa_header = skb->data - 2; |
173 | ||
469ee5fe TW |
174 | cmd = dsa_header[0] >> 6; |
175 | switch (cmd) { | |
176 | case DSA_CMD_FORWARD: | |
177 | skb->offload_fwd_mark = 1; | |
5b60dadb TW |
178 | |
179 | trunk = !!(dsa_header[1] & 7); | |
469ee5fe | 180 | break; |
e468d141 | 181 | |
469ee5fe | 182 | case DSA_CMD_TO_CPU: |
e468d141 TW |
183 | code = (dsa_header[1] & 0x6) | ((dsa_header[2] >> 4) & 1); |
184 | ||
469ee5fe TW |
185 | switch (code) { |
186 | case DSA_CODE_FRAME2REG: | |
187 | /* Remote management is not implemented yet, | |
188 | * drop. | |
189 | */ | |
190 | return NULL; | |
191 | case DSA_CODE_ARP_MIRROR: | |
192 | case DSA_CODE_POLICY_MIRROR: | |
193 | /* Mark mirrored packets to notify any upper | |
194 | * device (like a bridge) that forwarding has | |
195 | * already been done by hardware. | |
196 | */ | |
e468d141 | 197 | skb->offload_fwd_mark = 1; |
469ee5fe TW |
198 | break; |
199 | case DSA_CODE_MGMT_TRAP: | |
200 | case DSA_CODE_IGMP_MLD_TRAP: | |
201 | case DSA_CODE_POLICY_TRAP: | |
202 | /* Traps have, by definition, not been | |
203 | * forwarded by hardware, so don't mark them. | |
204 | */ | |
205 | break; | |
206 | default: | |
207 | /* Reserved code, this could be anything. Drop | |
208 | * seems like the safest option. | |
209 | */ | |
210 | return NULL; | |
211 | } | |
e468d141 TW |
212 | |
213 | break; | |
214 | ||
e468d141 | 215 | default: |
54709795 | 216 | return NULL; |
e468d141 | 217 | } |
cf85d08f | 218 | |
e84665c9 | 219 | source_device = dsa_header[0] & 0x1f; |
cf85d08f | 220 | source_port = (dsa_header[1] >> 3) & 0x1f; |
e84665c9 | 221 | |
5b60dadb TW |
222 | if (trunk) { |
223 | struct dsa_port *cpu_dp = dev->dsa_ptr; | |
224 | ||
225 | /* The exact source port is not available in the tag, | |
226 | * so we inject the frame directly on the upper | |
227 | * team/bond. | |
228 | */ | |
229 | skb->dev = dsa_lag_dev(cpu_dp->dst, source_port); | |
230 | } else { | |
231 | skb->dev = dsa_master_find_slave(dev, source_device, | |
232 | source_port); | |
233 | } | |
234 | ||
3775b1b7 | 235 | if (!skb->dev) |
54709795 | 236 | return NULL; |
cf85d08f | 237 | |
469ee5fe TW |
238 | /* If the 'tagged' bit is set; convert the DSA tag to a 802.1Q |
239 | * tag, and delete the ethertype (extra) if applicable. If the | |
240 | * 'tagged' bit is cleared; delete the DSA tag, and ethertype | |
241 | * if applicable. | |
cf85d08f LB |
242 | */ |
243 | if (dsa_header[0] & 0x20) { | |
244 | u8 new_header[4]; | |
245 | ||
13f49b6f | 246 | /* Insert 802.1Q ethertype and copy the VLAN-related |
cf85d08f LB |
247 | * fields, but clear the bit that will hold CFI (since |
248 | * DSA uses that bit location for another purpose). | |
249 | */ | |
250 | new_header[0] = (ETH_P_8021Q >> 8) & 0xff; | |
251 | new_header[1] = ETH_P_8021Q & 0xff; | |
252 | new_header[2] = dsa_header[2] & ~0x10; | |
253 | new_header[3] = dsa_header[3]; | |
254 | ||
13f49b6f TW |
255 | /* Move CFI bit from its place in the DSA header to |
256 | * its 802.1Q-designated place. | |
cf85d08f LB |
257 | */ |
258 | if (dsa_header[1] & 0x01) | |
259 | new_header[2] |= 0x10; | |
260 | ||
13f49b6f | 261 | /* Update packet checksum if skb is CHECKSUM_COMPLETE. */ |
cf85d08f LB |
262 | if (skb->ip_summed == CHECKSUM_COMPLETE) { |
263 | __wsum c = skb->csum; | |
264 | c = csum_add(c, csum_partial(new_header + 2, 2, 0)); | |
265 | c = csum_sub(c, csum_partial(dsa_header + 2, 2, 0)); | |
266 | skb->csum = c; | |
267 | } | |
268 | ||
269 | memcpy(dsa_header, new_header, DSA_HLEN); | |
469ee5fe TW |
270 | |
271 | if (extra) | |
272 | memmove(skb->data - ETH_HLEN, | |
273 | skb->data - ETH_HLEN - extra, | |
274 | 2 * ETH_ALEN); | |
cf85d08f | 275 | } else { |
cf85d08f LB |
276 | skb_pull_rcsum(skb, DSA_HLEN); |
277 | memmove(skb->data - ETH_HLEN, | |
469ee5fe | 278 | skb->data - ETH_HLEN - DSA_HLEN - extra, |
cf85d08f LB |
279 | 2 * ETH_ALEN); |
280 | } | |
281 | ||
a86d8bec | 282 | return skb; |
cf85d08f LB |
283 | } |
284 | ||
469ee5fe TW |
285 | #if IS_ENABLED(CONFIG_NET_DSA_TAG_DSA) |
286 | ||
287 | static struct sk_buff *dsa_xmit(struct sk_buff *skb, struct net_device *dev) | |
288 | { | |
289 | return dsa_xmit_ll(skb, dev, 0); | |
290 | } | |
291 | ||
292 | static struct sk_buff *dsa_rcv(struct sk_buff *skb, struct net_device *dev, | |
293 | struct packet_type *pt) | |
294 | { | |
295 | if (unlikely(!pskb_may_pull(skb, DSA_HLEN))) | |
296 | return NULL; | |
297 | ||
298 | return dsa_rcv_ll(skb, dev, 0); | |
299 | } | |
300 | ||
f81a43e8 | 301 | static const struct dsa_device_ops dsa_netdev_ops = { |
469ee5fe TW |
302 | .name = "dsa", |
303 | .proto = DSA_TAG_PROTO_DSA, | |
304 | .xmit = dsa_xmit, | |
305 | .rcv = dsa_rcv, | |
a5dd3087 | 306 | .overhead = DSA_HLEN, |
cf85d08f | 307 | }; |
0b42f033 | 308 | |
469ee5fe | 309 | DSA_TAG_DRIVER(dsa_netdev_ops); |
0b42f033 | 310 | MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_DSA); |
469ee5fe TW |
311 | #endif /* CONFIG_NET_DSA_TAG_DSA */ |
312 | ||
313 | #if IS_ENABLED(CONFIG_NET_DSA_TAG_EDSA) | |
314 | ||
315 | #define EDSA_HLEN 8 | |
316 | ||
317 | static struct sk_buff *edsa_xmit(struct sk_buff *skb, struct net_device *dev) | |
318 | { | |
319 | u8 *edsa_header; | |
320 | ||
321 | skb = dsa_xmit_ll(skb, dev, EDSA_HLEN - DSA_HLEN); | |
322 | if (!skb) | |
323 | return NULL; | |
d3b8c049 | 324 | |
469ee5fe TW |
325 | edsa_header = skb->data + 2 * ETH_ALEN; |
326 | edsa_header[0] = (ETH_P_EDSA >> 8) & 0xff; | |
327 | edsa_header[1] = ETH_P_EDSA & 0xff; | |
328 | edsa_header[2] = 0x00; | |
329 | edsa_header[3] = 0x00; | |
330 | return skb; | |
331 | } | |
332 | ||
333 | static struct sk_buff *edsa_rcv(struct sk_buff *skb, struct net_device *dev, | |
334 | struct packet_type *pt) | |
335 | { | |
336 | if (unlikely(!pskb_may_pull(skb, EDSA_HLEN))) | |
337 | return NULL; | |
338 | ||
339 | skb_pull_rcsum(skb, EDSA_HLEN - DSA_HLEN); | |
340 | ||
341 | return dsa_rcv_ll(skb, dev, EDSA_HLEN - DSA_HLEN); | |
342 | } | |
343 | ||
344 | static const struct dsa_device_ops edsa_netdev_ops = { | |
345 | .name = "edsa", | |
346 | .proto = DSA_TAG_PROTO_EDSA, | |
347 | .xmit = edsa_xmit, | |
348 | .rcv = edsa_rcv, | |
349 | .overhead = EDSA_HLEN, | |
350 | }; | |
351 | ||
352 | DSA_TAG_DRIVER(edsa_netdev_ops); | |
353 | MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_EDSA); | |
354 | #endif /* CONFIG_NET_DSA_TAG_EDSA */ | |
355 | ||
356 | static struct dsa_tag_driver *dsa_tag_drivers[] = { | |
357 | #if IS_ENABLED(CONFIG_NET_DSA_TAG_DSA) | |
358 | &DSA_TAG_DRIVER_NAME(dsa_netdev_ops), | |
359 | #endif | |
360 | #if IS_ENABLED(CONFIG_NET_DSA_TAG_EDSA) | |
361 | &DSA_TAG_DRIVER_NAME(edsa_netdev_ops), | |
362 | #endif | |
363 | }; | |
364 | ||
365 | module_dsa_tag_drivers(dsa_tag_drivers); | |
366 | ||
367 | MODULE_LICENSE("GPL"); |