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