mtd: fix build with GCC 14
[openwrt/openwrt.git] / target / linux / generic / backport-5.15 / 700-v5.17-net-dsa-introduce-tagger-owned-storage-for-private.patch
1 From dc452a471dbae8aca8257c565174212620880093 Mon Sep 17 00:00:00 2001
2 From: Vladimir Oltean <vladimir.oltean@nxp.com>
3 Date: Fri, 10 Dec 2021 01:34:37 +0200
4 Subject: net: dsa: introduce tagger-owned storage for private and shared data
5
6 Ansuel is working on register access over Ethernet for the qca8k switch
7 family. This requires the qca8k tagging protocol driver to receive
8 frames which aren't intended for the network stack, but instead for the
9 qca8k switch driver itself.
10
11 The dp->priv is currently the prevailing method for passing data back
12 and forth between the tagging protocol driver and the switch driver.
13 However, this method is riddled with caveats.
14
15 The DSA design allows in principle for any switch driver to return any
16 protocol it desires in ->get_tag_protocol(). The dsa_loop driver can be
17 modified to do just that. But in the current design, the memory behind
18 dp->priv has to be allocated by the switch driver, so if the tagging
19 protocol is paired to an unexpected switch driver, we may end up in NULL
20 pointer dereferences inside the kernel, or worse (a switch driver may
21 allocate dp->priv according to the expectations of a different tagger).
22
23 The latter possibility is even more plausible considering that DSA
24 switches can dynamically change tagging protocols in certain cases
25 (dsa <-> edsa, ocelot <-> ocelot-8021q), and the current design lends
26 itself to mistakes that are all too easy to make.
27
28 This patch proposes that the tagging protocol driver should manage its
29 own memory, instead of relying on the switch driver to do so.
30 After analyzing the different in-tree needs, it can be observed that the
31 required tagger storage is per switch, therefore a ds->tagger_data
32 pointer is introduced. In principle, per-port storage could also be
33 introduced, although there is no need for it at the moment. Future
34 changes will replace the current usage of dp->priv with ds->tagger_data.
35
36 We define a "binding" event between the DSA switch tree and the tagging
37 protocol. During this binding event, the tagging protocol's ->connect()
38 method is called first, and this may allocate some memory for each
39 switch of the tree. Then a cross-chip notifier is emitted for the
40 switches within that tree, and they are given the opportunity to fix up
41 the tagger's memory (for example, they might set up some function
42 pointers that represent virtual methods for consuming packets).
43 Because the memory is owned by the tagger, there exists a ->disconnect()
44 method for the tagger (which is the place to free the resources), but
45 there doesn't exist a ->disconnect() method for the switch driver.
46 This is part of the design. The switch driver should make minimal use of
47 the public part of the tagger data, and only after type-checking it
48 using the supplied "proto" argument.
49
50 In the code there are in fact two binding events, one is the initial
51 event in dsa_switch_setup_tag_protocol(). At this stage, the cross chip
52 notifier chains aren't initialized, so we call each switch's connect()
53 method by hand. Then there is dsa_tree_bind_tag_proto() during
54 dsa_tree_change_tag_proto(), and here we have an old protocol and a new
55 one. We first connect to the new one before disconnecting from the old
56 one, to simplify error handling a bit and to ensure we remain in a valid
57 state at all times.
58
59 Co-developed-by: Ansuel Smith <ansuelsmth@gmail.com>
60 Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
61 Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
62 Signed-off-by: David S. Miller <davem@davemloft.net>
63 ---
64 include/net/dsa.h | 12 +++++++++
65 net/dsa/dsa2.c | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++---
66 net/dsa/dsa_priv.h | 1 +
67 net/dsa/switch.c | 14 +++++++++++
68 4 files changed, 96 insertions(+), 4 deletions(-)
69
70 --- a/include/net/dsa.h
71 +++ b/include/net/dsa.h
72 @@ -80,12 +80,15 @@ enum dsa_tag_protocol {
73 };
74
75 struct dsa_switch;
76 +struct dsa_switch_tree;
77
78 struct dsa_device_ops {
79 struct sk_buff *(*xmit)(struct sk_buff *skb, struct net_device *dev);
80 struct sk_buff *(*rcv)(struct sk_buff *skb, struct net_device *dev);
81 void (*flow_dissect)(const struct sk_buff *skb, __be16 *proto,
82 int *offset);
83 + int (*connect)(struct dsa_switch_tree *dst);
84 + void (*disconnect)(struct dsa_switch_tree *dst);
85 unsigned int needed_headroom;
86 unsigned int needed_tailroom;
87 const char *name;
88 @@ -329,6 +332,8 @@ struct dsa_switch {
89 */
90 void *priv;
91
92 + void *tagger_data;
93 +
94 /*
95 * Configuration data for this switch.
96 */
97 @@ -612,6 +617,13 @@ struct dsa_switch_ops {
98 enum dsa_tag_protocol mprot);
99 int (*change_tag_protocol)(struct dsa_switch *ds, int port,
100 enum dsa_tag_protocol proto);
101 + /*
102 + * Method for switch drivers to connect to the tagging protocol driver
103 + * in current use. The switch driver can provide handlers for certain
104 + * types of packets for switch management.
105 + */
106 + int (*connect_tag_protocol)(struct dsa_switch *ds,
107 + enum dsa_tag_protocol proto);
108
109 /* Optional switch-wide initialization and destruction methods */
110 int (*setup)(struct dsa_switch *ds);
111 --- a/net/dsa/dsa2.c
112 +++ b/net/dsa/dsa2.c
113 @@ -230,8 +230,12 @@ static struct dsa_switch_tree *dsa_tree_
114
115 static void dsa_tree_free(struct dsa_switch_tree *dst)
116 {
117 - if (dst->tag_ops)
118 + if (dst->tag_ops) {
119 + if (dst->tag_ops->disconnect)
120 + dst->tag_ops->disconnect(dst);
121 +
122 dsa_tag_driver_put(dst->tag_ops);
123 + }
124 list_del(&dst->list);
125 kfree(dst);
126 }
127 @@ -805,7 +809,7 @@ static int dsa_switch_setup_tag_protocol
128 int port, err;
129
130 if (tag_ops->proto == dst->default_proto)
131 - return 0;
132 + goto connect;
133
134 for (port = 0; port < ds->num_ports; port++) {
135 if (!dsa_is_cpu_port(ds, port))
136 @@ -821,6 +825,17 @@ static int dsa_switch_setup_tag_protocol
137 }
138 }
139
140 +connect:
141 + if (ds->ops->connect_tag_protocol) {
142 + err = ds->ops->connect_tag_protocol(ds, tag_ops->proto);
143 + if (err) {
144 + dev_err(ds->dev,
145 + "Unable to connect to tag protocol \"%s\": %pe\n",
146 + tag_ops->name, ERR_PTR(err));
147 + return err;
148 + }
149 + }
150 +
151 return 0;
152 }
153
154 @@ -1132,6 +1147,46 @@ static void dsa_tree_teardown(struct dsa
155 dst->setup = false;
156 }
157
158 +static int dsa_tree_bind_tag_proto(struct dsa_switch_tree *dst,
159 + const struct dsa_device_ops *tag_ops)
160 +{
161 + const struct dsa_device_ops *old_tag_ops = dst->tag_ops;
162 + struct dsa_notifier_tag_proto_info info;
163 + int err;
164 +
165 + dst->tag_ops = tag_ops;
166 +
167 + /* Notify the new tagger about the connection to this tree */
168 + if (tag_ops->connect) {
169 + err = tag_ops->connect(dst);
170 + if (err)
171 + goto out_revert;
172 + }
173 +
174 + /* Notify the switches from this tree about the connection
175 + * to the new tagger
176 + */
177 + info.tag_ops = tag_ops;
178 + err = dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO_CONNECT, &info);
179 + if (err && err != -EOPNOTSUPP)
180 + goto out_disconnect;
181 +
182 + /* Notify the old tagger about the disconnection from this tree */
183 + if (old_tag_ops->disconnect)
184 + old_tag_ops->disconnect(dst);
185 +
186 + return 0;
187 +
188 +out_disconnect:
189 + /* Revert the new tagger's connection to this tree */
190 + if (tag_ops->disconnect)
191 + tag_ops->disconnect(dst);
192 +out_revert:
193 + dst->tag_ops = old_tag_ops;
194 +
195 + return err;
196 +}
197 +
198 /* Since the dsa/tagging sysfs device attribute is per master, the assumption
199 * is that all DSA switches within a tree share the same tagger, otherwise
200 * they would have formed disjoint trees (different "dsa,member" values).
201 @@ -1164,12 +1219,15 @@ int dsa_tree_change_tag_proto(struct dsa
202 goto out_unlock;
203 }
204
205 + /* Notify the tag protocol change */
206 info.tag_ops = tag_ops;
207 err = dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO, &info);
208 if (err)
209 - goto out_unwind_tagger;
210 + return err;
211
212 - dst->tag_ops = tag_ops;
213 + err = dsa_tree_bind_tag_proto(dst, tag_ops);
214 + if (err)
215 + goto out_unwind_tagger;
216
217 rtnl_unlock();
218
219 @@ -1257,6 +1315,7 @@ static int dsa_port_parse_cpu(struct dsa
220 struct dsa_switch *ds = dp->ds;
221 struct dsa_switch_tree *dst = ds->dst;
222 enum dsa_tag_protocol default_proto;
223 + int err;
224
225 /* Find out which protocol the switch would prefer. */
226 default_proto = dsa_get_tag_protocol(dp, master);
227 @@ -1311,6 +1370,12 @@ static int dsa_port_parse_cpu(struct dsa
228 */
229 dsa_tag_driver_put(tag_ops);
230 } else {
231 + if (tag_ops->connect) {
232 + err = tag_ops->connect(dst);
233 + if (err)
234 + return err;
235 + }
236 +
237 dst->tag_ops = tag_ops;
238 }
239
240 --- a/net/dsa/dsa_priv.h
241 +++ b/net/dsa/dsa_priv.h
242 @@ -37,6 +37,7 @@ enum {
243 DSA_NOTIFIER_VLAN_DEL,
244 DSA_NOTIFIER_MTU,
245 DSA_NOTIFIER_TAG_PROTO,
246 + DSA_NOTIFIER_TAG_PROTO_CONNECT,
247 DSA_NOTIFIER_MRP_ADD,
248 DSA_NOTIFIER_MRP_DEL,
249 DSA_NOTIFIER_MRP_ADD_RING_ROLE,
250 --- a/net/dsa/switch.c
251 +++ b/net/dsa/switch.c
252 @@ -616,6 +616,17 @@ static int dsa_switch_change_tag_proto(s
253 return 0;
254 }
255
256 +static int dsa_switch_connect_tag_proto(struct dsa_switch *ds,
257 + struct dsa_notifier_tag_proto_info *info)
258 +{
259 + const struct dsa_device_ops *tag_ops = info->tag_ops;
260 +
261 + if (!ds->ops->connect_tag_protocol)
262 + return -EOPNOTSUPP;
263 +
264 + return ds->ops->connect_tag_protocol(ds, tag_ops->proto);
265 +}
266 +
267 static int dsa_switch_mrp_add(struct dsa_switch *ds,
268 struct dsa_notifier_mrp_info *info)
269 {
270 @@ -735,6 +746,9 @@ static int dsa_switch_event(struct notif
271 case DSA_NOTIFIER_TAG_PROTO:
272 err = dsa_switch_change_tag_proto(ds, info);
273 break;
274 + case DSA_NOTIFIER_TAG_PROTO_CONNECT:
275 + err = dsa_switch_connect_tag_proto(ds, info);
276 + break;
277 case DSA_NOTIFIER_MRP_ADD:
278 err = dsa_switch_mrp_add(ds, info);
279 break;