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 | ||
8 | #include <linux/etherdevice.h> | |
9 | #include <linux/list.h> | |
5037d532 | 10 | #include <linux/slab.h> |
ea5dd34b | 11 | |
5037d532 FF |
12 | #include "dsa_priv.h" |
13 | ||
14 | /* This tag length is 4 bytes, older ones were 6 bytes, we do not | |
15 | * handle them | |
16 | */ | |
17 | #define BRCM_TAG_LEN 4 | |
18 | ||
19 | /* Tag is constructed and desconstructed using byte by byte access | |
20 | * because the tag is placed after the MAC Source Address, which does | |
21 | * not make it 4-bytes aligned, so this might cause unaligned accesses | |
22 | * on most systems where this is used. | |
23 | */ | |
24 | ||
25 | /* Ingress and egress opcodes */ | |
26 | #define BRCM_OPCODE_SHIFT 5 | |
27 | #define BRCM_OPCODE_MASK 0x7 | |
28 | ||
29 | /* Ingress fields */ | |
30 | /* 1st byte in the tag */ | |
31 | #define BRCM_IG_TC_SHIFT 2 | |
32 | #define BRCM_IG_TC_MASK 0x7 | |
33 | /* 2nd byte in the tag */ | |
34 | #define BRCM_IG_TE_MASK 0x3 | |
35 | #define BRCM_IG_TS_SHIFT 7 | |
36 | /* 3rd byte in the tag */ | |
37 | #define BRCM_IG_DSTMAP2_MASK 1 | |
38 | #define BRCM_IG_DSTMAP1_MASK 0xff | |
39 | ||
40 | /* Egress fields */ | |
41 | ||
42 | /* 2nd byte in the tag */ | |
43 | #define BRCM_EG_CID_MASK 0xff | |
44 | ||
45 | /* 3rd byte in the tag */ | |
46 | #define BRCM_EG_RC_MASK 0xff | |
47 | #define BRCM_EG_RC_RSVD (3 << 6) | |
48 | #define BRCM_EG_RC_EXCEPTION (1 << 5) | |
49 | #define BRCM_EG_RC_PROT_SNOOP (1 << 4) | |
50 | #define BRCM_EG_RC_PROT_TERM (1 << 3) | |
51 | #define BRCM_EG_RC_SWITCH (1 << 2) | |
52 | #define BRCM_EG_RC_MAC_LEARN (1 << 1) | |
53 | #define BRCM_EG_RC_MIRROR (1 << 0) | |
54 | #define BRCM_EG_TC_SHIFT 5 | |
55 | #define BRCM_EG_TC_MASK 0x7 | |
56 | #define BRCM_EG_PID_MASK 0x1f | |
57 | ||
3aa475e1 AL |
58 | #if IS_ENABLED(CONFIG_NET_DSA_TAG_BRCM) || \ |
59 | IS_ENABLED(CONFIG_NET_DSA_TAG_BRCM_PREPEND) | |
60 | ||
f7c39e3d FF |
61 | static struct sk_buff *brcm_tag_xmit_ll(struct sk_buff *skb, |
62 | struct net_device *dev, | |
63 | unsigned int offset) | |
5037d532 | 64 | { |
d945097b | 65 | struct dsa_port *dp = dsa_slave_to_port(dev); |
0f15b098 | 66 | u16 queue = skb_get_queue_mapping(skb); |
5037d532 FF |
67 | u8 *brcm_tag; |
68 | ||
5037d532 | 69 | if (skb_cow_head(skb, BRCM_TAG_LEN) < 0) |
fe47d563 | 70 | return NULL; |
5037d532 | 71 | |
bf08c340 FF |
72 | /* The Ethernet switch we are interfaced with needs packets to be at |
73 | * least 64 bytes (including FCS) otherwise they will be discarded when | |
74 | * they enter the switch port logic. When Broadcom tags are enabled, we | |
75 | * need to make sure that packets are at least 68 bytes | |
76 | * (including FCS and tag) because the length verification is done after | |
77 | * the Broadcom tag is stripped off the ingress packet. | |
78 | * | |
79 | * Let dsa_slave_xmit() free the SKB | |
80 | */ | |
81 | if (__skb_put_padto(skb, ETH_ZLEN + BRCM_TAG_LEN, false)) | |
82 | return NULL; | |
83 | ||
5037d532 FF |
84 | skb_push(skb, BRCM_TAG_LEN); |
85 | ||
f7c39e3d FF |
86 | if (offset) |
87 | memmove(skb->data, skb->data + BRCM_TAG_LEN, offset); | |
5037d532 | 88 | |
f7c39e3d | 89 | brcm_tag = skb->data + offset; |
5037d532 FF |
90 | |
91 | /* Set the ingress opcode, traffic class, tag enforcment is | |
92 | * deprecated | |
93 | */ | |
94 | brcm_tag[0] = (1 << BRCM_OPCODE_SHIFT) | | |
0f15b098 | 95 | ((queue & BRCM_IG_TC_MASK) << BRCM_IG_TC_SHIFT); |
5037d532 FF |
96 | brcm_tag[1] = 0; |
97 | brcm_tag[2] = 0; | |
d945097b | 98 | if (dp->index == 8) |
5037d532 | 99 | brcm_tag[2] = BRCM_IG_DSTMAP2_MASK; |
d945097b | 100 | brcm_tag[3] = (1 << dp->index) & BRCM_IG_DSTMAP1_MASK; |
5037d532 | 101 | |
0a5f14ce FF |
102 | /* Now tell the master network device about the desired output queue |
103 | * as well | |
104 | */ | |
d945097b | 105 | skb_set_queue_mapping(skb, BRCM_TAG_SET_PORT_QUEUE(dp->index, queue)); |
0a5f14ce | 106 | |
4ed70ce9 | 107 | return skb; |
5037d532 FF |
108 | } |
109 | ||
f7c39e3d FF |
110 | static struct sk_buff *brcm_tag_rcv_ll(struct sk_buff *skb, |
111 | struct net_device *dev, | |
112 | struct packet_type *pt, | |
113 | unsigned int offset) | |
5037d532 | 114 | { |
5037d532 FF |
115 | int source_port; |
116 | u8 *brcm_tag; | |
117 | ||
5037d532 | 118 | if (unlikely(!pskb_may_pull(skb, BRCM_TAG_LEN))) |
54709795 | 119 | return NULL; |
5037d532 | 120 | |
f7c39e3d | 121 | brcm_tag = skb->data - offset; |
5037d532 FF |
122 | |
123 | /* The opcode should never be different than 0b000 */ | |
124 | if (unlikely((brcm_tag[0] >> BRCM_OPCODE_SHIFT) & BRCM_OPCODE_MASK)) | |
54709795 | 125 | return NULL; |
5037d532 FF |
126 | |
127 | /* We should never see a reserved reason code without knowing how to | |
128 | * handle it | |
129 | */ | |
82272db8 | 130 | if (unlikely(brcm_tag[2] & BRCM_EG_RC_RSVD)) |
54709795 | 131 | return NULL; |
5037d532 FF |
132 | |
133 | /* Locate which port this is coming from */ | |
134 | source_port = brcm_tag[3] & BRCM_EG_PID_MASK; | |
135 | ||
2231c43b | 136 | skb->dev = dsa_master_find_slave(dev, 0, source_port); |
3775b1b7 | 137 | if (!skb->dev) |
54709795 | 138 | return NULL; |
5037d532 FF |
139 | |
140 | /* Remove Broadcom tag and update checksum */ | |
141 | skb_pull_rcsum(skb, BRCM_TAG_LEN); | |
142 | ||
f7c39e3d FF |
143 | return skb; |
144 | } | |
3aa475e1 | 145 | #endif |
f7c39e3d | 146 | |
3aa475e1 | 147 | #if IS_ENABLED(CONFIG_NET_DSA_TAG_BRCM) |
b74b70c4 FF |
148 | static struct sk_buff *brcm_tag_xmit(struct sk_buff *skb, |
149 | struct net_device *dev) | |
150 | { | |
151 | /* Build the tag after the MAC Source Address */ | |
152 | return brcm_tag_xmit_ll(skb, dev, 2 * ETH_ALEN); | |
153 | } | |
154 | ||
155 | ||
f7c39e3d FF |
156 | static struct sk_buff *brcm_tag_rcv(struct sk_buff *skb, struct net_device *dev, |
157 | struct packet_type *pt) | |
158 | { | |
159 | struct sk_buff *nskb; | |
160 | ||
161 | /* skb->data points to the EtherType, the tag is right before it */ | |
162 | nskb = brcm_tag_rcv_ll(skb, dev, pt, 2); | |
163 | if (!nskb) | |
164 | return nskb; | |
165 | ||
5037d532 | 166 | /* Move the Ethernet DA and SA */ |
f7c39e3d FF |
167 | memmove(nskb->data - ETH_HLEN, |
168 | nskb->data - ETH_HLEN - BRCM_TAG_LEN, | |
5037d532 FF |
169 | 2 * ETH_ALEN); |
170 | ||
f7c39e3d | 171 | return nskb; |
5037d532 FF |
172 | } |
173 | ||
f81a43e8 | 174 | static const struct dsa_device_ops brcm_netdev_ops = { |
875138f8 | 175 | .name = "brcm", |
056eed2f | 176 | .proto = DSA_TAG_PROTO_BRCM, |
5037d532 FF |
177 | .xmit = brcm_tag_xmit, |
178 | .rcv = brcm_tag_rcv, | |
a5dd3087 | 179 | .overhead = BRCM_TAG_LEN, |
5037d532 | 180 | }; |
0b42f033 | 181 | |
d3b8c049 | 182 | DSA_TAG_DRIVER(brcm_netdev_ops); |
0b42f033 | 183 | MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_BRCM); |
b74b70c4 FF |
184 | #endif |
185 | ||
3aa475e1 | 186 | #if IS_ENABLED(CONFIG_NET_DSA_TAG_BRCM_PREPEND) |
b74b70c4 FF |
187 | static struct sk_buff *brcm_tag_xmit_prepend(struct sk_buff *skb, |
188 | struct net_device *dev) | |
189 | { | |
190 | /* tag is prepended to the packet */ | |
191 | return brcm_tag_xmit_ll(skb, dev, 0); | |
192 | } | |
193 | ||
194 | static struct sk_buff *brcm_tag_rcv_prepend(struct sk_buff *skb, | |
195 | struct net_device *dev, | |
196 | struct packet_type *pt) | |
197 | { | |
198 | /* tag is prepended to the packet */ | |
199 | return brcm_tag_rcv_ll(skb, dev, pt, ETH_HLEN); | |
200 | } | |
201 | ||
f81a43e8 | 202 | static const struct dsa_device_ops brcm_prepend_netdev_ops = { |
875138f8 | 203 | .name = "brcm-prepend", |
056eed2f | 204 | .proto = DSA_TAG_PROTO_BRCM_PREPEND, |
b74b70c4 FF |
205 | .xmit = brcm_tag_xmit_prepend, |
206 | .rcv = brcm_tag_rcv_prepend, | |
a5dd3087 | 207 | .overhead = BRCM_TAG_LEN, |
b74b70c4 | 208 | }; |
0b42f033 | 209 | |
d3b8c049 | 210 | DSA_TAG_DRIVER(brcm_prepend_netdev_ops); |
0b42f033 | 211 | MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_BRCM_PREPEND); |
b96a5415 | 212 | #endif |
d3b8c049 AL |
213 | |
214 | static struct dsa_tag_driver *dsa_tag_driver_array[] = { | |
215 | #if IS_ENABLED(CONFIG_NET_DSA_TAG_BRCM) | |
216 | &DSA_TAG_DRIVER_NAME(brcm_netdev_ops), | |
217 | #endif | |
218 | #if IS_ENABLED(CONFIG_NET_DSA_TAG_BRCM_PREPEND) | |
219 | &DSA_TAG_DRIVER_NAME(brcm_prepend_netdev_ops), | |
220 | #endif | |
221 | }; | |
222 | ||
223 | module_dsa_tag_drivers(dsa_tag_driver_array); | |
224 | ||
225 | MODULE_LICENSE("GPL"); |