Commit | Line | Data |
---|---|---|
dfedd3b6 | 1 | // SPDX-License-Identifier: GPL-2.0+ |
5037d532 FF |
2 | /* |
3 | * Broadcom tag support | |
4 | * | |
5 | * Copyright (C) 2014 Broadcom Corporation | |
5037d532 FF |
6 | */ |
7 | ||
f46b9b8e | 8 | #include <linux/dsa/brcm.h> |
5037d532 | 9 | #include <linux/etherdevice.h> |
032a9540 | 10 | #include <linux/if_vlan.h> |
5037d532 | 11 | #include <linux/list.h> |
5037d532 | 12 | #include <linux/slab.h> |
ea5dd34b | 13 | |
bd954b82 | 14 | #include "tag.h" |
5037d532 | 15 | |
94793a56 VO |
16 | #define BRCM_NAME "brcm" |
17 | #define BRCM_LEGACY_NAME "brcm-legacy" | |
18 | #define BRCM_PREPEND_NAME "brcm-prepend" | |
19 | ||
964dbf18 ÁFR |
20 | /* Legacy Broadcom tag (6 bytes) */ |
21 | #define BRCM_LEG_TAG_LEN 6 | |
22 | ||
23 | /* Type fields */ | |
24 | /* 1st byte in the tag */ | |
25 | #define BRCM_LEG_TYPE_HI 0x88 | |
26 | /* 2nd byte in the tag */ | |
27 | #define BRCM_LEG_TYPE_LO 0x74 | |
28 | ||
29 | /* Tag fields */ | |
30 | /* 3rd byte in the tag */ | |
31 | #define BRCM_LEG_UNICAST (0 << 5) | |
32 | #define BRCM_LEG_MULTICAST (1 << 5) | |
33 | #define BRCM_LEG_EGRESS (2 << 5) | |
34 | #define BRCM_LEG_INGRESS (3 << 5) | |
35 | ||
36 | /* 6th byte in the tag */ | |
37 | #define BRCM_LEG_PORT_ID (0xf) | |
38 | ||
39 | /* Newer Broadcom tag (4 bytes) */ | |
5037d532 FF |
40 | #define BRCM_TAG_LEN 4 |
41 | ||
062cf5eb | 42 | /* Tag is constructed and deconstructed using byte by byte access |
5037d532 FF |
43 | * because the tag is placed after the MAC Source Address, which does |
44 | * not make it 4-bytes aligned, so this might cause unaligned accesses | |
45 | * on most systems where this is used. | |
46 | */ | |
47 | ||
48 | /* Ingress and egress opcodes */ | |
49 | #define BRCM_OPCODE_SHIFT 5 | |
50 | #define BRCM_OPCODE_MASK 0x7 | |
51 | ||
52 | /* Ingress fields */ | |
53 | /* 1st byte in the tag */ | |
54 | #define BRCM_IG_TC_SHIFT 2 | |
55 | #define BRCM_IG_TC_MASK 0x7 | |
56 | /* 2nd byte in the tag */ | |
57 | #define BRCM_IG_TE_MASK 0x3 | |
58 | #define BRCM_IG_TS_SHIFT 7 | |
59 | /* 3rd byte in the tag */ | |
60 | #define BRCM_IG_DSTMAP2_MASK 1 | |
61 | #define BRCM_IG_DSTMAP1_MASK 0xff | |
62 | ||
63 | /* Egress fields */ | |
64 | ||
65 | /* 2nd byte in the tag */ | |
66 | #define BRCM_EG_CID_MASK 0xff | |
67 | ||
68 | /* 3rd byte in the tag */ | |
69 | #define BRCM_EG_RC_MASK 0xff | |
70 | #define BRCM_EG_RC_RSVD (3 << 6) | |
71 | #define BRCM_EG_RC_EXCEPTION (1 << 5) | |
72 | #define BRCM_EG_RC_PROT_SNOOP (1 << 4) | |
73 | #define BRCM_EG_RC_PROT_TERM (1 << 3) | |
74 | #define BRCM_EG_RC_SWITCH (1 << 2) | |
75 | #define BRCM_EG_RC_MAC_LEARN (1 << 1) | |
76 | #define BRCM_EG_RC_MIRROR (1 << 0) | |
77 | #define BRCM_EG_TC_SHIFT 5 | |
78 | #define BRCM_EG_TC_MASK 0x7 | |
79 | #define BRCM_EG_PID_MASK 0x1f | |
80 | ||
3aa475e1 AL |
81 | #if IS_ENABLED(CONFIG_NET_DSA_TAG_BRCM) || \ |
82 | IS_ENABLED(CONFIG_NET_DSA_TAG_BRCM_PREPEND) | |
83 | ||
f7c39e3d FF |
84 | static struct sk_buff *brcm_tag_xmit_ll(struct sk_buff *skb, |
85 | struct net_device *dev, | |
86 | unsigned int offset) | |
5037d532 | 87 | { |
d945097b | 88 | struct dsa_port *dp = dsa_slave_to_port(dev); |
0f15b098 | 89 | u16 queue = skb_get_queue_mapping(skb); |
5037d532 FF |
90 | u8 *brcm_tag; |
91 | ||
bf08c340 FF |
92 | /* The Ethernet switch we are interfaced with needs packets to be at |
93 | * least 64 bytes (including FCS) otherwise they will be discarded when | |
94 | * they enter the switch port logic. When Broadcom tags are enabled, we | |
95 | * need to make sure that packets are at least 68 bytes | |
96 | * (including FCS and tag) because the length verification is done after | |
97 | * the Broadcom tag is stripped off the ingress packet. | |
98 | * | |
99 | * Let dsa_slave_xmit() free the SKB | |
100 | */ | |
101 | if (__skb_put_padto(skb, ETH_ZLEN + BRCM_TAG_LEN, false)) | |
102 | return NULL; | |
103 | ||
5037d532 FF |
104 | skb_push(skb, BRCM_TAG_LEN); |
105 | ||
f7c39e3d | 106 | if (offset) |
6bef794d | 107 | dsa_alloc_etype_header(skb, BRCM_TAG_LEN); |
5037d532 | 108 | |
f7c39e3d | 109 | brcm_tag = skb->data + offset; |
5037d532 | 110 | |
062cf5eb | 111 | /* Set the ingress opcode, traffic class, tag enforcement is |
5037d532 FF |
112 | * deprecated |
113 | */ | |
114 | brcm_tag[0] = (1 << BRCM_OPCODE_SHIFT) | | |
0f15b098 | 115 | ((queue & BRCM_IG_TC_MASK) << BRCM_IG_TC_SHIFT); |
5037d532 FF |
116 | brcm_tag[1] = 0; |
117 | brcm_tag[2] = 0; | |
d945097b | 118 | if (dp->index == 8) |
5037d532 | 119 | brcm_tag[2] = BRCM_IG_DSTMAP2_MASK; |
d945097b | 120 | brcm_tag[3] = (1 << dp->index) & BRCM_IG_DSTMAP1_MASK; |
5037d532 | 121 | |
0a5f14ce FF |
122 | /* Now tell the master network device about the desired output queue |
123 | * as well | |
124 | */ | |
d945097b | 125 | skb_set_queue_mapping(skb, BRCM_TAG_SET_PORT_QUEUE(dp->index, queue)); |
0a5f14ce | 126 | |
4ed70ce9 | 127 | return skb; |
5037d532 FF |
128 | } |
129 | ||
f569ad52 VO |
130 | /* Frames with this tag have one of these two layouts: |
131 | * ----------------------------------- | |
132 | * | MAC DA | MAC SA | 4b tag | Type | DSA_TAG_PROTO_BRCM | |
133 | * ----------------------------------- | |
134 | * ----------------------------------- | |
135 | * | 4b tag | MAC DA | MAC SA | Type | DSA_TAG_PROTO_BRCM_PREPEND | |
136 | * ----------------------------------- | |
137 | * In both cases, at receive time, skb->data points 2 bytes before the actual | |
138 | * Ethernet type field and we have an offset of 4bytes between where skb->data | |
139 | * and where the payload starts. So the same low-level receive function can be | |
140 | * used. | |
141 | */ | |
f7c39e3d FF |
142 | static struct sk_buff *brcm_tag_rcv_ll(struct sk_buff *skb, |
143 | struct net_device *dev, | |
f7c39e3d | 144 | unsigned int offset) |
5037d532 | 145 | { |
5037d532 FF |
146 | int source_port; |
147 | u8 *brcm_tag; | |
148 | ||
5037d532 | 149 | if (unlikely(!pskb_may_pull(skb, BRCM_TAG_LEN))) |
54709795 | 150 | return NULL; |
5037d532 | 151 | |
f7c39e3d | 152 | brcm_tag = skb->data - offset; |
5037d532 FF |
153 | |
154 | /* The opcode should never be different than 0b000 */ | |
155 | if (unlikely((brcm_tag[0] >> BRCM_OPCODE_SHIFT) & BRCM_OPCODE_MASK)) | |
54709795 | 156 | return NULL; |
5037d532 FF |
157 | |
158 | /* We should never see a reserved reason code without knowing how to | |
159 | * handle it | |
160 | */ | |
82272db8 | 161 | if (unlikely(brcm_tag[2] & BRCM_EG_RC_RSVD)) |
54709795 | 162 | return NULL; |
5037d532 FF |
163 | |
164 | /* Locate which port this is coming from */ | |
165 | source_port = brcm_tag[3] & BRCM_EG_PID_MASK; | |
166 | ||
2231c43b | 167 | skb->dev = dsa_master_find_slave(dev, 0, source_port); |
3775b1b7 | 168 | if (!skb->dev) |
54709795 | 169 | return NULL; |
5037d532 FF |
170 | |
171 | /* Remove Broadcom tag and update checksum */ | |
172 | skb_pull_rcsum(skb, BRCM_TAG_LEN); | |
173 | ||
bea79078 | 174 | dsa_default_offload_fwd_mark(skb); |
0e62f543 | 175 | |
f7c39e3d FF |
176 | return skb; |
177 | } | |
3aa475e1 | 178 | #endif |
f7c39e3d | 179 | |
3aa475e1 | 180 | #if IS_ENABLED(CONFIG_NET_DSA_TAG_BRCM) |
b74b70c4 FF |
181 | static struct sk_buff *brcm_tag_xmit(struct sk_buff *skb, |
182 | struct net_device *dev) | |
183 | { | |
184 | /* Build the tag after the MAC Source Address */ | |
185 | return brcm_tag_xmit_ll(skb, dev, 2 * ETH_ALEN); | |
186 | } | |
187 | ||
188 | ||
29a097b7 | 189 | static struct sk_buff *brcm_tag_rcv(struct sk_buff *skb, struct net_device *dev) |
f7c39e3d FF |
190 | { |
191 | struct sk_buff *nskb; | |
192 | ||
193 | /* skb->data points to the EtherType, the tag is right before it */ | |
29a097b7 | 194 | nskb = brcm_tag_rcv_ll(skb, dev, 2); |
f7c39e3d FF |
195 | if (!nskb) |
196 | return nskb; | |
197 | ||
f1dacd7a | 198 | dsa_strip_etype_header(skb, BRCM_TAG_LEN); |
5037d532 | 199 | |
1c5ad5a9 | 200 | return nskb; |
5037d532 FF |
201 | } |
202 | ||
f81a43e8 | 203 | static const struct dsa_device_ops brcm_netdev_ops = { |
94793a56 | 204 | .name = BRCM_NAME, |
056eed2f | 205 | .proto = DSA_TAG_PROTO_BRCM, |
5037d532 FF |
206 | .xmit = brcm_tag_xmit, |
207 | .rcv = brcm_tag_rcv, | |
4e500251 | 208 | .needed_headroom = BRCM_TAG_LEN, |
5037d532 | 209 | }; |
0b42f033 | 210 | |
d3b8c049 | 211 | DSA_TAG_DRIVER(brcm_netdev_ops); |
94793a56 | 212 | MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_BRCM, BRCM_NAME); |
b74b70c4 FF |
213 | #endif |
214 | ||
964dbf18 ÁFR |
215 | #if IS_ENABLED(CONFIG_NET_DSA_TAG_BRCM_LEGACY) |
216 | static struct sk_buff *brcm_leg_tag_xmit(struct sk_buff *skb, | |
217 | struct net_device *dev) | |
218 | { | |
219 | struct dsa_port *dp = dsa_slave_to_port(dev); | |
220 | u8 *brcm_tag; | |
221 | ||
222 | /* The Ethernet switch we are interfaced with needs packets to be at | |
223 | * least 64 bytes (including FCS) otherwise they will be discarded when | |
224 | * they enter the switch port logic. When Broadcom tags are enabled, we | |
225 | * need to make sure that packets are at least 70 bytes | |
226 | * (including FCS and tag) because the length verification is done after | |
227 | * the Broadcom tag is stripped off the ingress packet. | |
228 | * | |
229 | * Let dsa_slave_xmit() free the SKB | |
230 | */ | |
231 | if (__skb_put_padto(skb, ETH_ZLEN + BRCM_LEG_TAG_LEN, false)) | |
232 | return NULL; | |
233 | ||
234 | skb_push(skb, BRCM_LEG_TAG_LEN); | |
235 | ||
6bef794d | 236 | dsa_alloc_etype_header(skb, BRCM_LEG_TAG_LEN); |
964dbf18 ÁFR |
237 | |
238 | brcm_tag = skb->data + 2 * ETH_ALEN; | |
239 | ||
240 | /* Broadcom tag type */ | |
241 | brcm_tag[0] = BRCM_LEG_TYPE_HI; | |
242 | brcm_tag[1] = BRCM_LEG_TYPE_LO; | |
243 | ||
244 | /* Broadcom tag value */ | |
245 | brcm_tag[2] = BRCM_LEG_EGRESS; | |
246 | brcm_tag[3] = 0; | |
247 | brcm_tag[4] = 0; | |
248 | brcm_tag[5] = dp->index & BRCM_LEG_PORT_ID; | |
249 | ||
250 | return skb; | |
251 | } | |
252 | ||
253 | static struct sk_buff *brcm_leg_tag_rcv(struct sk_buff *skb, | |
29a097b7 | 254 | struct net_device *dev) |
964dbf18 | 255 | { |
032a9540 | 256 | int len = BRCM_LEG_TAG_LEN; |
964dbf18 ÁFR |
257 | int source_port; |
258 | u8 *brcm_tag; | |
259 | ||
260 | if (unlikely(!pskb_may_pull(skb, BRCM_LEG_PORT_ID))) | |
261 | return NULL; | |
262 | ||
5d928ff4 | 263 | brcm_tag = dsa_etype_header_pos_rx(skb); |
964dbf18 ÁFR |
264 | |
265 | source_port = brcm_tag[5] & BRCM_LEG_PORT_ID; | |
266 | ||
267 | skb->dev = dsa_master_find_slave(dev, 0, source_port); | |
268 | if (!skb->dev) | |
269 | return NULL; | |
270 | ||
032a9540 ÁFR |
271 | /* VLAN tag is added by BCM63xx internal switch */ |
272 | if (netdev_uses_dsa(skb->dev)) | |
273 | len += VLAN_HLEN; | |
274 | ||
964dbf18 | 275 | /* Remove Broadcom tag and update checksum */ |
032a9540 | 276 | skb_pull_rcsum(skb, len); |
964dbf18 | 277 | |
bea79078 | 278 | dsa_default_offload_fwd_mark(skb); |
964dbf18 | 279 | |
032a9540 | 280 | dsa_strip_etype_header(skb, len); |
964dbf18 ÁFR |
281 | |
282 | return skb; | |
283 | } | |
284 | ||
285 | static const struct dsa_device_ops brcm_legacy_netdev_ops = { | |
94793a56 | 286 | .name = BRCM_LEGACY_NAME, |
964dbf18 ÁFR |
287 | .proto = DSA_TAG_PROTO_BRCM_LEGACY, |
288 | .xmit = brcm_leg_tag_xmit, | |
289 | .rcv = brcm_leg_tag_rcv, | |
4e500251 | 290 | .needed_headroom = BRCM_LEG_TAG_LEN, |
964dbf18 ÁFR |
291 | }; |
292 | ||
293 | DSA_TAG_DRIVER(brcm_legacy_netdev_ops); | |
94793a56 | 294 | MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_BRCM_LEGACY, BRCM_LEGACY_NAME); |
964dbf18 ÁFR |
295 | #endif /* CONFIG_NET_DSA_TAG_BRCM_LEGACY */ |
296 | ||
3aa475e1 | 297 | #if IS_ENABLED(CONFIG_NET_DSA_TAG_BRCM_PREPEND) |
b74b70c4 FF |
298 | static struct sk_buff *brcm_tag_xmit_prepend(struct sk_buff *skb, |
299 | struct net_device *dev) | |
300 | { | |
301 | /* tag is prepended to the packet */ | |
302 | return brcm_tag_xmit_ll(skb, dev, 0); | |
303 | } | |
304 | ||
305 | static struct sk_buff *brcm_tag_rcv_prepend(struct sk_buff *skb, | |
29a097b7 | 306 | struct net_device *dev) |
b74b70c4 FF |
307 | { |
308 | /* tag is prepended to the packet */ | |
29a097b7 | 309 | return brcm_tag_rcv_ll(skb, dev, ETH_HLEN); |
b74b70c4 FF |
310 | } |
311 | ||
f81a43e8 | 312 | static const struct dsa_device_ops brcm_prepend_netdev_ops = { |
94793a56 | 313 | .name = BRCM_PREPEND_NAME, |
056eed2f | 314 | .proto = DSA_TAG_PROTO_BRCM_PREPEND, |
b74b70c4 FF |
315 | .xmit = brcm_tag_xmit_prepend, |
316 | .rcv = brcm_tag_rcv_prepend, | |
4e500251 | 317 | .needed_headroom = BRCM_TAG_LEN, |
b74b70c4 | 318 | }; |
0b42f033 | 319 | |
d3b8c049 | 320 | DSA_TAG_DRIVER(brcm_prepend_netdev_ops); |
94793a56 | 321 | MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_BRCM_PREPEND, BRCM_PREPEND_NAME); |
b96a5415 | 322 | #endif |
d3b8c049 AL |
323 | |
324 | static struct dsa_tag_driver *dsa_tag_driver_array[] = { | |
325 | #if IS_ENABLED(CONFIG_NET_DSA_TAG_BRCM) | |
326 | &DSA_TAG_DRIVER_NAME(brcm_netdev_ops), | |
327 | #endif | |
964dbf18 ÁFR |
328 | #if IS_ENABLED(CONFIG_NET_DSA_TAG_BRCM_LEGACY) |
329 | &DSA_TAG_DRIVER_NAME(brcm_legacy_netdev_ops), | |
330 | #endif | |
d3b8c049 AL |
331 | #if IS_ENABLED(CONFIG_NET_DSA_TAG_BRCM_PREPEND) |
332 | &DSA_TAG_DRIVER_NAME(brcm_prepend_netdev_ops), | |
333 | #endif | |
334 | }; | |
335 | ||
336 | module_dsa_tag_drivers(dsa_tag_driver_array); | |
337 | ||
338 | MODULE_LICENSE("GPL"); |