projects/linux·blob master · refs

173 lines · 4.5 KiB

1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Motorcomm YT921x Switch Extended CPU Port Tagging
4 *
5 * Copyright (c) 2025 David Yang <[email protected]>
6 *
7 * +----+----+-------+-----+----+---------
8 * | DA | SA | TagET | Tag | ET | Payload ...
9 * +----+----+-------+-----+----+---------
10 * 6 6 2 6 2 N
11 *
12 * Tag Ethertype: CPU_TAG_TPID_TPID (default: ETH_P_YT921X = 0x9988)
13 * * Hardcoded for the moment, but still configurable. Discuss it if there
14 * are conflicts somewhere and/or you want to change it for some reason.
15 * Tag:
16 * 2: VLAN Tag
17 * 2:
18 * 15b: Rx Port Valid
19 * 14b-11b: Rx Port
20 * 10b-8b: Tx/Rx Priority
21 * 7b: Tx/Rx Code Valid
22 * 6b-1b: Tx/Rx Code
23 * 0b: ? (unset)
24 * 2:
25 * 15b: Tx Port(s) Valid
26 * 10b-0b: Tx Port(s) Mask
27 */
28
29#include <linux/etherdevice.h>
30
31#include "tag.h"
32
33#define YT921X_TAG_NAME "yt921x"
34
35#define YT921X_TAG_LEN 8
36
37#define YT921X_TAG_PORT_EN BIT(15)
38#define YT921X_TAG_RX_PORT_M GENMASK(14, 11)
39#define YT921X_TAG_PRIO_M GENMASK(10, 8)
40#define YT921X_TAG_PRIO(x) FIELD_PREP(YT921X_TAG_PRIO_M, (x))
41#define YT921X_TAG_CODE_EN BIT(7)
42#define YT921X_TAG_CODE_M GENMASK(6, 1)
43#define YT921X_TAG_CODE(x) FIELD_PREP(YT921X_TAG_CODE_M, (x))
44#define YT921X_TAG_TX_PORTS_M GENMASK(10, 0)
45#define YT921X_TAG_TX_PORTS(x) FIELD_PREP(YT921X_TAG_TX_PORTS_M, (x))
46
47/* Incomplete. Some are configurable via RMA_CTRL_CPU_CODE, the meaning of
48 * others remains unknown.
49 */
50enum yt921x_tag_code {
51 YT921X_TAG_CODE_FORWARD = 0,
52 YT921X_TAG_CODE_ACL = 0x17,
53 YT921X_TAG_CODE_UNK_UCAST = 0x19,
54 YT921X_TAG_CODE_UNK_MCAST = 0x1a,
55 YT921X_TAG_CODE_PORT_COPY = 0x1b,
56 YT921X_TAG_CODE_FDB_COPY = 0x1c,
57};
58
59static struct sk_buff *
60yt921x_tag_xmit(struct sk_buff *skb, struct net_device *netdev)
61{
62 __be16 *tag;
63 u16 ctrl;
64
65 skb_push(skb, YT921X_TAG_LEN);
66 dsa_alloc_etype_header(skb, YT921X_TAG_LEN);
67
68 tag = dsa_etype_header_pos_tx(skb);
69
70 tag[0] = htons(ETH_P_YT921X);
71 /* VLAN tag unrelated when TX */
72 tag[1] = 0;
73 ctrl = YT921X_TAG_CODE(YT921X_TAG_CODE_FORWARD) | YT921X_TAG_CODE_EN |
74 YT921X_TAG_PRIO(skb->priority);
75 tag[2] = htons(ctrl);
76 ctrl = YT921X_TAG_TX_PORTS(dsa_xmit_port_mask(skb, netdev)) |
77 YT921X_TAG_PORT_EN;
78 tag[3] = htons(ctrl);
79
80 return skb;
81}
82
83static struct sk_buff *
84yt921x_tag_rcv(struct sk_buff *skb, struct net_device *netdev)
85{
86 unsigned int port;
87 __be16 *tag;
88 u16 rx;
89
90 if (unlikely(!pskb_may_pull(skb, YT921X_TAG_LEN))) {
91 kfree_skb(skb);
92 return NULL;
93 }
94
95 tag = dsa_etype_header_pos_rx(skb);
96
97 if (unlikely(tag[0] != htons(ETH_P_YT921X))) {
98 dev_warn_ratelimited(&netdev->dev,
99 "Unexpected EtherType 0x%04x\n",
100 ntohs(tag[0]));
101 kfree_skb(skb);
102 return NULL;
103 }
104
105 /* Locate which port this is coming from */
106 rx = ntohs(tag[2]);
107 if (unlikely((rx & YT921X_TAG_PORT_EN) == 0)) {
108 dev_warn_ratelimited(&netdev->dev,
109 "Unexpected rx tag 0x%04x\n", rx);
110 kfree_skb(skb);
111 return NULL;
112 }
113
114 port = FIELD_GET(YT921X_TAG_RX_PORT_M, rx);
115 skb->dev = dsa_conduit_find_user(netdev, 0, port);
116 if (unlikely(!skb->dev)) {
117 dev_warn_ratelimited(&netdev->dev,
118 "Couldn't decode source port %u\n", port);
119 kfree_skb(skb);
120 return NULL;
121 }
122
123 skb->priority = FIELD_GET(YT921X_TAG_PRIO_M, rx);
124
125 if (!(rx & YT921X_TAG_CODE_EN)) {
126 dev_warn_ratelimited(&netdev->dev,
127 "Tag code not enabled in rx packet\n");
128 } else {
129 u16 code = FIELD_GET(YT921X_TAG_CODE_M, rx);
130
131 switch (code) {
132 case YT921X_TAG_CODE_FORWARD:
133 case YT921X_TAG_CODE_PORT_COPY:
134 case YT921X_TAG_CODE_FDB_COPY:
135 /* Already forwarded by hardware */
136 dsa_default_offload_fwd_mark(skb);
137 break;
138 case YT921X_TAG_CODE_ACL:
139 case YT921X_TAG_CODE_UNK_UCAST:
140 case YT921X_TAG_CODE_UNK_MCAST:
141 /* NOTE: hardware doesn't distinguish between TRAP (copy
142 * to CPU only) and COPY (forward and copy to CPU). In
143 * order to perform a soft switch, NEVER use COPY action
144 * in the switch driver.
145 */
146 break;
147 default:
148 dev_warn_ratelimited(&netdev->dev,
149 "Unknown code 0x%02x\n", code);
150 break;
151 }
152 }
153
154 /* Remove YT921x tag and update checksum */
155 skb_pull_rcsum(skb, YT921X_TAG_LEN);
156 dsa_strip_etype_header(skb, YT921X_TAG_LEN);
157
158 return skb;
159}
160
161static const struct dsa_device_ops yt921x_netdev_ops = {
162 .name = YT921X_TAG_NAME,
163 .proto = DSA_TAG_PROTO_YT921X,
164 .xmit = yt921x_tag_xmit,
165 .rcv = yt921x_tag_rcv,
166 .needed_headroom = YT921X_TAG_LEN,
167};
168
169MODULE_DESCRIPTION("DSA tag driver for Motorcomm YT921x switches");
170MODULE_LICENSE("GPL");
171MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_YT921X, YT921X_TAG_NAME);
172
173module_dsa_tag_driver(yt921x_netdev_ops);