1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Broadcom tag support |
4 | * |
5 | * Copyright (C) 2014 Broadcom Corporation |
6 | */ |
7 | |
8 | #include <linux/dsa/brcm.h> |
9 | #include <linux/etherdevice.h> |
10 | #include <linux/if_vlan.h> |
11 | #include <linux/list.h> |
12 | #include <linux/slab.h> |
13 | |
14 | #include "tag.h" |
15 | |
16 | #define BRCM_NAME "brcm" |
17 | #define BRCM_LEGACY_NAME "brcm-legacy" |
18 | #define BRCM_PREPEND_NAME "brcm-prepend" |
19 | |
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) */ |
40 | #define BRCM_TAG_LEN 4 |
41 | |
42 | /* Tag is constructed and deconstructed using byte by byte access |
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 | |
81 | #if IS_ENABLED(CONFIG_NET_DSA_TAG_BRCM) || \ |
82 | IS_ENABLED(CONFIG_NET_DSA_TAG_BRCM_PREPEND) |
83 | |
84 | static struct sk_buff *brcm_tag_xmit_ll(struct sk_buff *skb, |
85 | struct net_device *dev, |
86 | unsigned int offset) |
87 | { |
88 | struct dsa_port *dp = dsa_user_to_port(dev); |
89 | u16 queue = skb_get_queue_mapping(skb); |
90 | u8 *brcm_tag; |
91 | |
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_user_xmit() free the SKB |
100 | */ |
101 | if (__skb_put_padto(skb, ETH_ZLEN + BRCM_TAG_LEN, free_on_error: false)) |
102 | return NULL; |
103 | |
104 | skb_push(skb, BRCM_TAG_LEN); |
105 | |
106 | if (offset) |
107 | dsa_alloc_etype_header(skb, BRCM_TAG_LEN); |
108 | |
109 | brcm_tag = skb->data + offset; |
110 | |
111 | /* Set the ingress opcode, traffic class, tag enforcement is |
112 | * deprecated |
113 | */ |
114 | brcm_tag[0] = (1 << BRCM_OPCODE_SHIFT) | |
115 | ((queue & BRCM_IG_TC_MASK) << BRCM_IG_TC_SHIFT); |
116 | brcm_tag[1] = 0; |
117 | brcm_tag[2] = 0; |
118 | if (dp->index == 8) |
119 | brcm_tag[2] = BRCM_IG_DSTMAP2_MASK; |
120 | brcm_tag[3] = (1 << dp->index) & BRCM_IG_DSTMAP1_MASK; |
121 | |
122 | /* Now tell the conduit network device about the desired output queue |
123 | * as well |
124 | */ |
125 | skb_set_queue_mapping(skb, BRCM_TAG_SET_PORT_QUEUE(dp->index, queue)); |
126 | |
127 | return skb; |
128 | } |
129 | |
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 | */ |
142 | static struct sk_buff *brcm_tag_rcv_ll(struct sk_buff *skb, |
143 | struct net_device *dev, |
144 | unsigned int offset) |
145 | { |
146 | int source_port; |
147 | u8 *brcm_tag; |
148 | |
149 | if (unlikely(!pskb_may_pull(skb, BRCM_TAG_LEN))) |
150 | return NULL; |
151 | |
152 | brcm_tag = skb->data - offset; |
153 | |
154 | /* The opcode should never be different than 0b000 */ |
155 | if (unlikely((brcm_tag[0] >> BRCM_OPCODE_SHIFT) & BRCM_OPCODE_MASK)) |
156 | return NULL; |
157 | |
158 | /* We should never see a reserved reason code without knowing how to |
159 | * handle it |
160 | */ |
161 | if (unlikely(brcm_tag[2] & BRCM_EG_RC_RSVD)) |
162 | return NULL; |
163 | |
164 | /* Locate which port this is coming from */ |
165 | source_port = brcm_tag[3] & BRCM_EG_PID_MASK; |
166 | |
167 | skb->dev = dsa_conduit_find_user(dev, device: 0, port: source_port); |
168 | if (!skb->dev) |
169 | return NULL; |
170 | |
171 | /* Remove Broadcom tag and update checksum */ |
172 | skb_pull_rcsum(skb, BRCM_TAG_LEN); |
173 | |
174 | dsa_default_offload_fwd_mark(skb); |
175 | |
176 | return skb; |
177 | } |
178 | #endif |
179 | |
180 | #if IS_ENABLED(CONFIG_NET_DSA_TAG_BRCM) |
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, offset: 2 * ETH_ALEN); |
186 | } |
187 | |
188 | |
189 | static struct sk_buff *brcm_tag_rcv(struct sk_buff *skb, struct net_device *dev) |
190 | { |
191 | struct sk_buff *nskb; |
192 | |
193 | /* skb->data points to the EtherType, the tag is right before it */ |
194 | nskb = brcm_tag_rcv_ll(skb, dev, offset: 2); |
195 | if (!nskb) |
196 | return nskb; |
197 | |
198 | dsa_strip_etype_header(skb, BRCM_TAG_LEN); |
199 | |
200 | return nskb; |
201 | } |
202 | |
203 | static const struct dsa_device_ops brcm_netdev_ops = { |
204 | .name = BRCM_NAME, |
205 | .proto = DSA_TAG_PROTO_BRCM, |
206 | .xmit = brcm_tag_xmit, |
207 | .rcv = brcm_tag_rcv, |
208 | .needed_headroom = BRCM_TAG_LEN, |
209 | }; |
210 | |
211 | DSA_TAG_DRIVER(brcm_netdev_ops); |
212 | MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_BRCM, BRCM_NAME); |
213 | #endif |
214 | |
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_user_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_user_xmit() free the SKB |
230 | */ |
231 | if (__skb_put_padto(skb, ETH_ZLEN + BRCM_LEG_TAG_LEN, free_on_error: false)) |
232 | return NULL; |
233 | |
234 | skb_push(skb, BRCM_LEG_TAG_LEN); |
235 | |
236 | dsa_alloc_etype_header(skb, BRCM_LEG_TAG_LEN); |
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, |
254 | struct net_device *dev) |
255 | { |
256 | int len = BRCM_LEG_TAG_LEN; |
257 | int source_port; |
258 | u8 *brcm_tag; |
259 | |
260 | if (unlikely(!pskb_may_pull(skb, BRCM_LEG_PORT_ID))) |
261 | return NULL; |
262 | |
263 | brcm_tag = dsa_etype_header_pos_rx(skb); |
264 | |
265 | source_port = brcm_tag[5] & BRCM_LEG_PORT_ID; |
266 | |
267 | skb->dev = dsa_conduit_find_user(dev, device: 0, port: source_port); |
268 | if (!skb->dev) |
269 | return NULL; |
270 | |
271 | /* VLAN tag is added by BCM63xx internal switch */ |
272 | if (netdev_uses_dsa(dev: skb->dev)) |
273 | len += VLAN_HLEN; |
274 | |
275 | /* Remove Broadcom tag and update checksum */ |
276 | skb_pull_rcsum(skb, len); |
277 | |
278 | dsa_default_offload_fwd_mark(skb); |
279 | |
280 | dsa_strip_etype_header(skb, len); |
281 | |
282 | return skb; |
283 | } |
284 | |
285 | static const struct dsa_device_ops brcm_legacy_netdev_ops = { |
286 | .name = BRCM_LEGACY_NAME, |
287 | .proto = DSA_TAG_PROTO_BRCM_LEGACY, |
288 | .xmit = brcm_leg_tag_xmit, |
289 | .rcv = brcm_leg_tag_rcv, |
290 | .needed_headroom = BRCM_LEG_TAG_LEN, |
291 | }; |
292 | |
293 | DSA_TAG_DRIVER(brcm_legacy_netdev_ops); |
294 | MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_BRCM_LEGACY, BRCM_LEGACY_NAME); |
295 | #endif /* CONFIG_NET_DSA_TAG_BRCM_LEGACY */ |
296 | |
297 | #if IS_ENABLED(CONFIG_NET_DSA_TAG_BRCM_PREPEND) |
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, offset: 0); |
303 | } |
304 | |
305 | static struct sk_buff *brcm_tag_rcv_prepend(struct sk_buff *skb, |
306 | struct net_device *dev) |
307 | { |
308 | /* tag is prepended to the packet */ |
309 | return brcm_tag_rcv_ll(skb, dev, ETH_HLEN); |
310 | } |
311 | |
312 | static const struct dsa_device_ops brcm_prepend_netdev_ops = { |
313 | .name = BRCM_PREPEND_NAME, |
314 | .proto = DSA_TAG_PROTO_BRCM_PREPEND, |
315 | .xmit = brcm_tag_xmit_prepend, |
316 | .rcv = brcm_tag_rcv_prepend, |
317 | .needed_headroom = BRCM_TAG_LEN, |
318 | }; |
319 | |
320 | DSA_TAG_DRIVER(brcm_prepend_netdev_ops); |
321 | MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_BRCM_PREPEND, BRCM_PREPEND_NAME); |
322 | #endif |
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 |
328 | #if IS_ENABLED(CONFIG_NET_DSA_TAG_BRCM_LEGACY) |
329 | &DSA_TAG_DRIVER_NAME(brcm_legacy_netdev_ops), |
330 | #endif |
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_DESCRIPTION("DSA tag driver for Broadcom switches using in-frame headers" ); |
339 | MODULE_LICENSE("GPL" ); |
340 | |