Commit | Line | Data |
---|---|---|
1521d5ad AŠ |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Handler for Realtek 8 byte switch tags | |
4 | * | |
5 | * Copyright (C) 2021 Alvin Šipraga <alsi@bang-olufsen.dk> | |
6 | * | |
7 | * NOTE: Currently only supports protocol "4" found in the RTL8365MB, hence | |
8 | * named tag_rtl8_4. | |
9 | * | |
cd87fecd | 10 | * This tag has the following format: |
1521d5ad | 11 | * |
1521d5ad AŠ |
12 | * 0 7|8 15 |
13 | * |-----------------------------------+-----------------------------------|--- | |
14 | * | (16-bit) | ^ | |
15 | * | Realtek EtherType [0x8899] | | | |
16 | * |-----------------------------------+-----------------------------------| 8 | |
17 | * | (8-bit) | (8-bit) | | |
18 | * | Protocol [0x04] | REASON | b | |
19 | * |-----------------------------------+-----------------------------------| y | |
20 | * | (1) | (1) | (2) | (1) | (3) | (1) | (1) | (1) | (5) | t | |
21 | * | FID_EN | X | FID | PRI_EN | PRI | KEEP | X | LEARN_DIS | X | e | |
22 | * |-----------------------------------+-----------------------------------| s | |
23 | * | (1) | (15-bit) | | | |
24 | * | ALLOW | TX/RX | v | |
25 | * |-----------------------------------+-----------------------------------|--- | |
26 | * | |
27 | * With the following field descriptions: | |
28 | * | |
29 | * field | description | |
30 | * ------------+------------- | |
31 | * Realtek | 0x8899: indicates that this is a proprietary Realtek tag; | |
32 | * EtherType | note that Realtek uses the same EtherType for | |
33 | * | other incompatible tag formats (e.g. tag_rtl4_a.c) | |
34 | * Protocol | 0x04: indicates that this tag conforms to this format | |
35 | * X | reserved | |
36 | * ------------+------------- | |
37 | * REASON | reason for forwarding packet to CPU | |
38 | * | 0: packet was forwarded or flooded to CPU | |
39 | * | 80: packet was trapped to CPU | |
40 | * FID_EN | 1: packet has an FID | |
41 | * | 0: no FID | |
42 | * FID | FID of packet (if FID_EN=1) | |
43 | * PRI_EN | 1: force priority of packet | |
44 | * | 0: don't force priority | |
45 | * PRI | priority of packet (if PRI_EN=1) | |
46 | * KEEP | preserve packet VLAN tag format | |
47 | * LEARN_DIS | don't learn the source MAC address of the packet | |
48 | * ALLOW | 1: treat TX/RX field as an allowance port mask, meaning the | |
49 | * | packet may only be forwarded to ports specified in the | |
50 | * | mask | |
51 | * | 0: no allowance port mask, TX/RX field is the forwarding | |
52 | * | port mask | |
53 | * TX/RX | TX (switch->CPU): port number the packet was received on | |
54 | * | RX (CPU->switch): forwarding port mask (if ALLOW=0) | |
55 | * | allowance port mask (if ALLOW=1) | |
cd87fecd LADL |
56 | * |
57 | * The tag can be positioned before Ethertype, using tag "rtl8_4": | |
58 | * | |
59 | * +--------+--------+------------+------+----- | |
60 | * | MAC DA | MAC SA | 8 byte tag | Type | ... | |
61 | * +--------+--------+------------+------+----- | |
62 | * | |
63 | * The tag can also appear between the end of the payload and before the CRC, | |
64 | * using tag "rtl8_4t": | |
65 | * | |
66 | * +--------+--------+------+-----+---------+------------+-----+ | |
67 | * | MAC DA | MAC SA | TYPE | ... | payload | 8-byte tag | CRC | | |
68 | * +--------+--------+------+-----+---------+------------+-----+ | |
69 | * | |
70 | * The added bytes after the payload will break most checksums, either in | |
71 | * software or hardware. To avoid this issue, if the checksum is still pending, | |
72 | * this tagger checksums the packet in software before adding the tag. | |
73 | * | |
1521d5ad AŠ |
74 | */ |
75 | ||
76 | #include <linux/bitfield.h> | |
77 | #include <linux/bits.h> | |
78 | #include <linux/etherdevice.h> | |
79 | ||
80 | #include "dsa_priv.h" | |
81 | ||
82 | /* Protocols supported: | |
83 | * | |
84 | * 0x04 = RTL8365MB DSA protocol | |
85 | */ | |
86 | ||
94793a56 VO |
87 | #define RTL8_4_NAME "rtl8_4" |
88 | #define RTL8_4T_NAME "rtl8_4t" | |
89 | ||
1521d5ad AŠ |
90 | #define RTL8_4_TAG_LEN 8 |
91 | ||
92 | #define RTL8_4_PROTOCOL GENMASK(15, 8) | |
93 | #define RTL8_4_PROTOCOL_RTL8365MB 0x04 | |
94 | #define RTL8_4_REASON GENMASK(7, 0) | |
95 | #define RTL8_4_REASON_FORWARD 0 | |
96 | #define RTL8_4_REASON_TRAP 80 | |
97 | ||
98 | #define RTL8_4_LEARN_DIS BIT(5) | |
99 | ||
100 | #define RTL8_4_TX GENMASK(3, 0) | |
101 | #define RTL8_4_RX GENMASK(10, 0) | |
102 | ||
cd87fecd LADL |
103 | static void rtl8_4_write_tag(struct sk_buff *skb, struct net_device *dev, |
104 | void *tag) | |
1521d5ad AŠ |
105 | { |
106 | struct dsa_port *dp = dsa_slave_to_port(dev); | |
cd87fecd | 107 | __be16 tag16[RTL8_4_TAG_LEN / 2]; |
1521d5ad AŠ |
108 | |
109 | /* Set Realtek EtherType */ | |
cd87fecd | 110 | tag16[0] = htons(ETH_P_REALTEK); |
1521d5ad AŠ |
111 | |
112 | /* Set Protocol; zero REASON */ | |
cd87fecd | 113 | tag16[1] = htons(FIELD_PREP(RTL8_4_PROTOCOL, RTL8_4_PROTOCOL_RTL8365MB)); |
1521d5ad AŠ |
114 | |
115 | /* Zero FID_EN, FID, PRI_EN, PRI, KEEP; set LEARN_DIS */ | |
cd87fecd | 116 | tag16[2] = htons(FIELD_PREP(RTL8_4_LEARN_DIS, 1)); |
1521d5ad AŠ |
117 | |
118 | /* Zero ALLOW; set RX (CPU->switch) forwarding port mask */ | |
cd87fecd LADL |
119 | tag16[3] = htons(FIELD_PREP(RTL8_4_RX, BIT(dp->index))); |
120 | ||
121 | memcpy(tag, tag16, RTL8_4_TAG_LEN); | |
122 | } | |
123 | ||
124 | static struct sk_buff *rtl8_4_tag_xmit(struct sk_buff *skb, | |
125 | struct net_device *dev) | |
126 | { | |
127 | skb_push(skb, RTL8_4_TAG_LEN); | |
128 | ||
129 | dsa_alloc_etype_header(skb, RTL8_4_TAG_LEN); | |
130 | ||
131 | rtl8_4_write_tag(skb, dev, dsa_etype_header_pos_tx(skb)); | |
1521d5ad AŠ |
132 | |
133 | return skb; | |
134 | } | |
135 | ||
cd87fecd LADL |
136 | static struct sk_buff *rtl8_4t_tag_xmit(struct sk_buff *skb, |
137 | struct net_device *dev) | |
138 | { | |
139 | /* Calculate the checksum here if not done yet as trailing tags will | |
140 | * break either software or hardware based checksum | |
141 | */ | |
142 | if (skb->ip_summed == CHECKSUM_PARTIAL && skb_checksum_help(skb)) | |
143 | return NULL; | |
144 | ||
145 | rtl8_4_write_tag(skb, dev, skb_put(skb, RTL8_4_TAG_LEN)); | |
146 | ||
147 | return skb; | |
148 | } | |
149 | ||
150 | static int rtl8_4_read_tag(struct sk_buff *skb, struct net_device *dev, | |
151 | void *tag) | |
1521d5ad | 152 | { |
cd87fecd | 153 | __be16 tag16[RTL8_4_TAG_LEN / 2]; |
1521d5ad AŠ |
154 | u16 etype; |
155 | u8 reason; | |
156 | u8 proto; | |
157 | u8 port; | |
158 | ||
cd87fecd | 159 | memcpy(tag16, tag, RTL8_4_TAG_LEN); |
1521d5ad AŠ |
160 | |
161 | /* Parse Realtek EtherType */ | |
cd87fecd | 162 | etype = ntohs(tag16[0]); |
1521d5ad AŠ |
163 | if (unlikely(etype != ETH_P_REALTEK)) { |
164 | dev_warn_ratelimited(&dev->dev, | |
165 | "non-realtek ethertype 0x%04x\n", etype); | |
cd87fecd | 166 | return -EPROTO; |
1521d5ad AŠ |
167 | } |
168 | ||
169 | /* Parse Protocol */ | |
cd87fecd | 170 | proto = FIELD_GET(RTL8_4_PROTOCOL, ntohs(tag16[1])); |
1521d5ad AŠ |
171 | if (unlikely(proto != RTL8_4_PROTOCOL_RTL8365MB)) { |
172 | dev_warn_ratelimited(&dev->dev, | |
173 | "unknown realtek protocol 0x%02x\n", | |
174 | proto); | |
cd87fecd | 175 | return -EPROTO; |
1521d5ad AŠ |
176 | } |
177 | ||
178 | /* Parse REASON */ | |
cd87fecd | 179 | reason = FIELD_GET(RTL8_4_REASON, ntohs(tag16[1])); |
1521d5ad AŠ |
180 | |
181 | /* Parse TX (switch->CPU) */ | |
cd87fecd | 182 | port = FIELD_GET(RTL8_4_TX, ntohs(tag16[3])); |
1521d5ad AŠ |
183 | skb->dev = dsa_master_find_slave(dev, 0, port); |
184 | if (!skb->dev) { | |
185 | dev_warn_ratelimited(&dev->dev, | |
186 | "could not find slave for port %d\n", | |
187 | port); | |
cd87fecd | 188 | return -ENOENT; |
1521d5ad AŠ |
189 | } |
190 | ||
cd87fecd LADL |
191 | if (reason != RTL8_4_REASON_TRAP) |
192 | dsa_default_offload_fwd_mark(skb); | |
193 | ||
194 | return 0; | |
195 | } | |
196 | ||
197 | static struct sk_buff *rtl8_4_tag_rcv(struct sk_buff *skb, | |
198 | struct net_device *dev) | |
199 | { | |
200 | if (unlikely(!pskb_may_pull(skb, RTL8_4_TAG_LEN))) | |
201 | return NULL; | |
202 | ||
203 | if (unlikely(rtl8_4_read_tag(skb, dev, dsa_etype_header_pos_rx(skb)))) | |
204 | return NULL; | |
205 | ||
1521d5ad AŠ |
206 | /* Remove tag and recalculate checksum */ |
207 | skb_pull_rcsum(skb, RTL8_4_TAG_LEN); | |
208 | ||
209 | dsa_strip_etype_header(skb, RTL8_4_TAG_LEN); | |
210 | ||
cd87fecd LADL |
211 | return skb; |
212 | } | |
213 | ||
214 | static struct sk_buff *rtl8_4t_tag_rcv(struct sk_buff *skb, | |
215 | struct net_device *dev) | |
216 | { | |
217 | if (skb_linearize(skb)) | |
218 | return NULL; | |
219 | ||
220 | if (unlikely(rtl8_4_read_tag(skb, dev, skb_tail_pointer(skb) - RTL8_4_TAG_LEN))) | |
221 | return NULL; | |
222 | ||
223 | if (pskb_trim_rcsum(skb, skb->len - RTL8_4_TAG_LEN)) | |
224 | return NULL; | |
1521d5ad AŠ |
225 | |
226 | return skb; | |
227 | } | |
228 | ||
cd87fecd | 229 | /* Ethertype version */ |
1521d5ad AŠ |
230 | static const struct dsa_device_ops rtl8_4_netdev_ops = { |
231 | .name = "rtl8_4", | |
232 | .proto = DSA_TAG_PROTO_RTL8_4, | |
233 | .xmit = rtl8_4_tag_xmit, | |
234 | .rcv = rtl8_4_tag_rcv, | |
235 | .needed_headroom = RTL8_4_TAG_LEN, | |
236 | }; | |
1521d5ad | 237 | |
cd87fecd LADL |
238 | DSA_TAG_DRIVER(rtl8_4_netdev_ops); |
239 | ||
94793a56 | 240 | MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_RTL8_4, RTL8_4_NAME); |
cd87fecd LADL |
241 | |
242 | /* Tail version */ | |
243 | static const struct dsa_device_ops rtl8_4t_netdev_ops = { | |
244 | .name = "rtl8_4t", | |
245 | .proto = DSA_TAG_PROTO_RTL8_4T, | |
246 | .xmit = rtl8_4t_tag_xmit, | |
247 | .rcv = rtl8_4t_tag_rcv, | |
248 | .needed_tailroom = RTL8_4_TAG_LEN, | |
249 | }; | |
250 | ||
251 | DSA_TAG_DRIVER(rtl8_4t_netdev_ops); | |
252 | ||
94793a56 | 253 | MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_RTL8_4T, RTL8_4T_NAME); |
cd87fecd LADL |
254 | |
255 | static struct dsa_tag_driver *dsa_tag_drivers[] = { | |
256 | &DSA_TAG_DRIVER_NAME(rtl8_4_netdev_ops), | |
257 | &DSA_TAG_DRIVER_NAME(rtl8_4t_netdev_ops), | |
258 | }; | |
259 | module_dsa_tag_drivers(dsa_tag_drivers); | |
260 | ||
261 | MODULE_LICENSE("GPL"); |