Merge tag 'drm-fixes-2023-10-13' of git://anongit.freedesktop.org/drm/drm
[platform/kernel/linux-rpi.git] / kernel / bpf / tcx.c
1 // SPDX-License-Identifier: GPL-2.0
2 /* Copyright (c) 2023 Isovalent */
3
4 #include <linux/bpf.h>
5 #include <linux/bpf_mprog.h>
6 #include <linux/netdevice.h>
7
8 #include <net/tcx.h>
9
10 int tcx_prog_attach(const union bpf_attr *attr, struct bpf_prog *prog)
11 {
12         bool created, ingress = attr->attach_type == BPF_TCX_INGRESS;
13         struct net *net = current->nsproxy->net_ns;
14         struct bpf_mprog_entry *entry, *entry_new;
15         struct bpf_prog *replace_prog = NULL;
16         struct net_device *dev;
17         int ret;
18
19         rtnl_lock();
20         dev = __dev_get_by_index(net, attr->target_ifindex);
21         if (!dev) {
22                 ret = -ENODEV;
23                 goto out;
24         }
25         if (attr->attach_flags & BPF_F_REPLACE) {
26                 replace_prog = bpf_prog_get_type(attr->replace_bpf_fd,
27                                                  prog->type);
28                 if (IS_ERR(replace_prog)) {
29                         ret = PTR_ERR(replace_prog);
30                         replace_prog = NULL;
31                         goto out;
32                 }
33         }
34         entry = tcx_entry_fetch_or_create(dev, ingress, &created);
35         if (!entry) {
36                 ret = -ENOMEM;
37                 goto out;
38         }
39         ret = bpf_mprog_attach(entry, &entry_new, prog, NULL, replace_prog,
40                                attr->attach_flags, attr->relative_fd,
41                                attr->expected_revision);
42         if (!ret) {
43                 if (entry != entry_new) {
44                         tcx_entry_update(dev, entry_new, ingress);
45                         tcx_entry_sync();
46                         tcx_skeys_inc(ingress);
47                 }
48                 bpf_mprog_commit(entry);
49         } else if (created) {
50                 tcx_entry_free(entry);
51         }
52 out:
53         if (replace_prog)
54                 bpf_prog_put(replace_prog);
55         rtnl_unlock();
56         return ret;
57 }
58
59 int tcx_prog_detach(const union bpf_attr *attr, struct bpf_prog *prog)
60 {
61         bool ingress = attr->attach_type == BPF_TCX_INGRESS;
62         struct net *net = current->nsproxy->net_ns;
63         struct bpf_mprog_entry *entry, *entry_new;
64         struct net_device *dev;
65         int ret;
66
67         rtnl_lock();
68         dev = __dev_get_by_index(net, attr->target_ifindex);
69         if (!dev) {
70                 ret = -ENODEV;
71                 goto out;
72         }
73         entry = tcx_entry_fetch(dev, ingress);
74         if (!entry) {
75                 ret = -ENOENT;
76                 goto out;
77         }
78         ret = bpf_mprog_detach(entry, &entry_new, prog, NULL, attr->attach_flags,
79                                attr->relative_fd, attr->expected_revision);
80         if (!ret) {
81                 if (!tcx_entry_is_active(entry_new))
82                         entry_new = NULL;
83                 tcx_entry_update(dev, entry_new, ingress);
84                 tcx_entry_sync();
85                 tcx_skeys_dec(ingress);
86                 bpf_mprog_commit(entry);
87                 if (!entry_new)
88                         tcx_entry_free(entry);
89         }
90 out:
91         rtnl_unlock();
92         return ret;
93 }
94
95 void tcx_uninstall(struct net_device *dev, bool ingress)
96 {
97         struct bpf_mprog_entry *entry, *entry_new = NULL;
98         struct bpf_tuple tuple = {};
99         struct bpf_mprog_fp *fp;
100         struct bpf_mprog_cp *cp;
101         bool active;
102
103         entry = tcx_entry_fetch(dev, ingress);
104         if (!entry)
105                 return;
106         active = tcx_entry(entry)->miniq_active;
107         if (active)
108                 bpf_mprog_clear_all(entry, &entry_new);
109         tcx_entry_update(dev, entry_new, ingress);
110         tcx_entry_sync();
111         bpf_mprog_foreach_tuple(entry, fp, cp, tuple) {
112                 if (tuple.link)
113                         tcx_link(tuple.link)->dev = NULL;
114                 else
115                         bpf_prog_put(tuple.prog);
116                 tcx_skeys_dec(ingress);
117         }
118         if (!active)
119                 tcx_entry_free(entry);
120 }
121
122 int tcx_prog_query(const union bpf_attr *attr, union bpf_attr __user *uattr)
123 {
124         bool ingress = attr->query.attach_type == BPF_TCX_INGRESS;
125         struct net *net = current->nsproxy->net_ns;
126         struct net_device *dev;
127         int ret;
128
129         rtnl_lock();
130         dev = __dev_get_by_index(net, attr->query.target_ifindex);
131         if (!dev) {
132                 ret = -ENODEV;
133                 goto out;
134         }
135         ret = bpf_mprog_query(attr, uattr, tcx_entry_fetch(dev, ingress));
136 out:
137         rtnl_unlock();
138         return ret;
139 }
140
141 static int tcx_link_prog_attach(struct bpf_link *link, u32 flags, u32 id_or_fd,
142                                 u64 revision)
143 {
144         struct tcx_link *tcx = tcx_link(link);
145         bool created, ingress = tcx->location == BPF_TCX_INGRESS;
146         struct bpf_mprog_entry *entry, *entry_new;
147         struct net_device *dev = tcx->dev;
148         int ret;
149
150         ASSERT_RTNL();
151         entry = tcx_entry_fetch_or_create(dev, ingress, &created);
152         if (!entry)
153                 return -ENOMEM;
154         ret = bpf_mprog_attach(entry, &entry_new, link->prog, link, NULL, flags,
155                                id_or_fd, revision);
156         if (!ret) {
157                 if (entry != entry_new) {
158                         tcx_entry_update(dev, entry_new, ingress);
159                         tcx_entry_sync();
160                         tcx_skeys_inc(ingress);
161                 }
162                 bpf_mprog_commit(entry);
163         } else if (created) {
164                 tcx_entry_free(entry);
165         }
166         return ret;
167 }
168
169 static void tcx_link_release(struct bpf_link *link)
170 {
171         struct tcx_link *tcx = tcx_link(link);
172         bool ingress = tcx->location == BPF_TCX_INGRESS;
173         struct bpf_mprog_entry *entry, *entry_new;
174         struct net_device *dev;
175         int ret = 0;
176
177         rtnl_lock();
178         dev = tcx->dev;
179         if (!dev)
180                 goto out;
181         entry = tcx_entry_fetch(dev, ingress);
182         if (!entry) {
183                 ret = -ENOENT;
184                 goto out;
185         }
186         ret = bpf_mprog_detach(entry, &entry_new, link->prog, link, 0, 0, 0);
187         if (!ret) {
188                 if (!tcx_entry_is_active(entry_new))
189                         entry_new = NULL;
190                 tcx_entry_update(dev, entry_new, ingress);
191                 tcx_entry_sync();
192                 tcx_skeys_dec(ingress);
193                 bpf_mprog_commit(entry);
194                 if (!entry_new)
195                         tcx_entry_free(entry);
196                 tcx->dev = NULL;
197         }
198 out:
199         WARN_ON_ONCE(ret);
200         rtnl_unlock();
201 }
202
203 static int tcx_link_update(struct bpf_link *link, struct bpf_prog *nprog,
204                            struct bpf_prog *oprog)
205 {
206         struct tcx_link *tcx = tcx_link(link);
207         bool ingress = tcx->location == BPF_TCX_INGRESS;
208         struct bpf_mprog_entry *entry, *entry_new;
209         struct net_device *dev;
210         int ret = 0;
211
212         rtnl_lock();
213         dev = tcx->dev;
214         if (!dev) {
215                 ret = -ENOLINK;
216                 goto out;
217         }
218         if (oprog && link->prog != oprog) {
219                 ret = -EPERM;
220                 goto out;
221         }
222         oprog = link->prog;
223         if (oprog == nprog) {
224                 bpf_prog_put(nprog);
225                 goto out;
226         }
227         entry = tcx_entry_fetch(dev, ingress);
228         if (!entry) {
229                 ret = -ENOENT;
230                 goto out;
231         }
232         ret = bpf_mprog_attach(entry, &entry_new, nprog, link, oprog,
233                                BPF_F_REPLACE | BPF_F_ID,
234                                link->prog->aux->id, 0);
235         if (!ret) {
236                 WARN_ON_ONCE(entry != entry_new);
237                 oprog = xchg(&link->prog, nprog);
238                 bpf_prog_put(oprog);
239                 bpf_mprog_commit(entry);
240         }
241 out:
242         rtnl_unlock();
243         return ret;
244 }
245
246 static void tcx_link_dealloc(struct bpf_link *link)
247 {
248         kfree(tcx_link(link));
249 }
250
251 static void tcx_link_fdinfo(const struct bpf_link *link, struct seq_file *seq)
252 {
253         const struct tcx_link *tcx = tcx_link_const(link);
254         u32 ifindex = 0;
255
256         rtnl_lock();
257         if (tcx->dev)
258                 ifindex = tcx->dev->ifindex;
259         rtnl_unlock();
260
261         seq_printf(seq, "ifindex:\t%u\n", ifindex);
262         seq_printf(seq, "attach_type:\t%u (%s)\n",
263                    tcx->location,
264                    tcx->location == BPF_TCX_INGRESS ? "ingress" : "egress");
265 }
266
267 static int tcx_link_fill_info(const struct bpf_link *link,
268                               struct bpf_link_info *info)
269 {
270         const struct tcx_link *tcx = tcx_link_const(link);
271         u32 ifindex = 0;
272
273         rtnl_lock();
274         if (tcx->dev)
275                 ifindex = tcx->dev->ifindex;
276         rtnl_unlock();
277
278         info->tcx.ifindex = ifindex;
279         info->tcx.attach_type = tcx->location;
280         return 0;
281 }
282
283 static int tcx_link_detach(struct bpf_link *link)
284 {
285         tcx_link_release(link);
286         return 0;
287 }
288
289 static const struct bpf_link_ops tcx_link_lops = {
290         .release        = tcx_link_release,
291         .detach         = tcx_link_detach,
292         .dealloc        = tcx_link_dealloc,
293         .update_prog    = tcx_link_update,
294         .show_fdinfo    = tcx_link_fdinfo,
295         .fill_link_info = tcx_link_fill_info,
296 };
297
298 static int tcx_link_init(struct tcx_link *tcx,
299                          struct bpf_link_primer *link_primer,
300                          const union bpf_attr *attr,
301                          struct net_device *dev,
302                          struct bpf_prog *prog)
303 {
304         bpf_link_init(&tcx->link, BPF_LINK_TYPE_TCX, &tcx_link_lops, prog);
305         tcx->location = attr->link_create.attach_type;
306         tcx->dev = dev;
307         return bpf_link_prime(&tcx->link, link_primer);
308 }
309
310 int tcx_link_attach(const union bpf_attr *attr, struct bpf_prog *prog)
311 {
312         struct net *net = current->nsproxy->net_ns;
313         struct bpf_link_primer link_primer;
314         struct net_device *dev;
315         struct tcx_link *tcx;
316         int ret;
317
318         rtnl_lock();
319         dev = __dev_get_by_index(net, attr->link_create.target_ifindex);
320         if (!dev) {
321                 ret = -ENODEV;
322                 goto out;
323         }
324         tcx = kzalloc(sizeof(*tcx), GFP_USER);
325         if (!tcx) {
326                 ret = -ENOMEM;
327                 goto out;
328         }
329         ret = tcx_link_init(tcx, &link_primer, attr, dev, prog);
330         if (ret) {
331                 kfree(tcx);
332                 goto out;
333         }
334         ret = tcx_link_prog_attach(&tcx->link, attr->link_create.flags,
335                                    attr->link_create.tcx.relative_fd,
336                                    attr->link_create.tcx.expected_revision);
337         if (ret) {
338                 tcx->dev = NULL;
339                 bpf_link_cleanup(&link_primer);
340                 goto out;
341         }
342         ret = bpf_link_settle(&link_primer);
343 out:
344         rtnl_unlock();
345         return ret;
346 }