xref: /openbmc/linux/kernel/bpf/dispatcher.c (revision 7ae9fb1b7ecbb5d85d07857943f677fd1a559b18)
175ccbef6SBjörn Töpel // SPDX-License-Identifier: GPL-2.0-only
275ccbef6SBjörn Töpel /* Copyright(c) 2019 Intel Corporation. */
375ccbef6SBjörn Töpel 
475ccbef6SBjörn Töpel #include <linux/hash.h>
575ccbef6SBjörn Töpel #include <linux/bpf.h>
675ccbef6SBjörn Töpel #include <linux/filter.h>
7c86df29dSPeter Zijlstra #include <linux/static_call.h>
875ccbef6SBjörn Töpel 
975ccbef6SBjörn Töpel /* The BPF dispatcher is a multiway branch code generator. The
1075ccbef6SBjörn Töpel  * dispatcher is a mechanism to avoid the performance penalty of an
1175ccbef6SBjörn Töpel  * indirect call, which is expensive when retpolines are enabled. A
1275ccbef6SBjörn Töpel  * dispatch client registers a BPF program into the dispatcher, and if
1375ccbef6SBjörn Töpel  * there is available room in the dispatcher a direct call to the BPF
1475ccbef6SBjörn Töpel  * program will be generated. All calls to the BPF programs called via
1575ccbef6SBjörn Töpel  * the dispatcher will then be a direct call, instead of an
1675ccbef6SBjörn Töpel  * indirect. The dispatcher hijacks a trampoline function it via the
1775ccbef6SBjörn Töpel  * __fentry__ of the trampoline. The trampoline function has the
1875ccbef6SBjörn Töpel  * following signature:
1975ccbef6SBjörn Töpel  *
2075ccbef6SBjörn Töpel  * unsigned int trampoline(const void *ctx, const struct bpf_insn *insnsi,
2175ccbef6SBjörn Töpel  *                         unsigned int (*bpf_func)(const void *,
2275ccbef6SBjörn Töpel  *                                                  const struct bpf_insn *));
2375ccbef6SBjörn Töpel  */
2475ccbef6SBjörn Töpel 
bpf_dispatcher_find_prog(struct bpf_dispatcher * d,struct bpf_prog * prog)2575ccbef6SBjörn Töpel static struct bpf_dispatcher_prog *bpf_dispatcher_find_prog(
2675ccbef6SBjörn Töpel 	struct bpf_dispatcher *d, struct bpf_prog *prog)
2775ccbef6SBjörn Töpel {
2875ccbef6SBjörn Töpel 	int i;
2975ccbef6SBjörn Töpel 
3075ccbef6SBjörn Töpel 	for (i = 0; i < BPF_DISPATCHER_MAX; i++) {
3175ccbef6SBjörn Töpel 		if (prog == d->progs[i].prog)
3275ccbef6SBjörn Töpel 			return &d->progs[i];
3375ccbef6SBjörn Töpel 	}
3475ccbef6SBjörn Töpel 	return NULL;
3575ccbef6SBjörn Töpel }
3675ccbef6SBjörn Töpel 
bpf_dispatcher_find_free(struct bpf_dispatcher * d)3775ccbef6SBjörn Töpel static struct bpf_dispatcher_prog *bpf_dispatcher_find_free(
3875ccbef6SBjörn Töpel 	struct bpf_dispatcher *d)
3975ccbef6SBjörn Töpel {
4075ccbef6SBjörn Töpel 	return bpf_dispatcher_find_prog(d, NULL);
4175ccbef6SBjörn Töpel }
4275ccbef6SBjörn Töpel 
bpf_dispatcher_add_prog(struct bpf_dispatcher * d,struct bpf_prog * prog)4375ccbef6SBjörn Töpel static bool bpf_dispatcher_add_prog(struct bpf_dispatcher *d,
4475ccbef6SBjörn Töpel 				    struct bpf_prog *prog)
4575ccbef6SBjörn Töpel {
4675ccbef6SBjörn Töpel 	struct bpf_dispatcher_prog *entry;
4775ccbef6SBjörn Töpel 
4875ccbef6SBjörn Töpel 	if (!prog)
4975ccbef6SBjörn Töpel 		return false;
5075ccbef6SBjörn Töpel 
5175ccbef6SBjörn Töpel 	entry = bpf_dispatcher_find_prog(d, prog);
5275ccbef6SBjörn Töpel 	if (entry) {
5375ccbef6SBjörn Töpel 		refcount_inc(&entry->users);
5475ccbef6SBjörn Töpel 		return false;
5575ccbef6SBjörn Töpel 	}
5675ccbef6SBjörn Töpel 
5775ccbef6SBjörn Töpel 	entry = bpf_dispatcher_find_free(d);
5875ccbef6SBjörn Töpel 	if (!entry)
5975ccbef6SBjörn Töpel 		return false;
6075ccbef6SBjörn Töpel 
6175ccbef6SBjörn Töpel 	bpf_prog_inc(prog);
6275ccbef6SBjörn Töpel 	entry->prog = prog;
6375ccbef6SBjörn Töpel 	refcount_set(&entry->users, 1);
6475ccbef6SBjörn Töpel 	d->num_progs++;
6575ccbef6SBjörn Töpel 	return true;
6675ccbef6SBjörn Töpel }
6775ccbef6SBjörn Töpel 
bpf_dispatcher_remove_prog(struct bpf_dispatcher * d,struct bpf_prog * prog)6875ccbef6SBjörn Töpel static bool bpf_dispatcher_remove_prog(struct bpf_dispatcher *d,
6975ccbef6SBjörn Töpel 				       struct bpf_prog *prog)
7075ccbef6SBjörn Töpel {
7175ccbef6SBjörn Töpel 	struct bpf_dispatcher_prog *entry;
7275ccbef6SBjörn Töpel 
7375ccbef6SBjörn Töpel 	if (!prog)
7475ccbef6SBjörn Töpel 		return false;
7575ccbef6SBjörn Töpel 
7675ccbef6SBjörn Töpel 	entry = bpf_dispatcher_find_prog(d, prog);
7775ccbef6SBjörn Töpel 	if (!entry)
7875ccbef6SBjörn Töpel 		return false;
7975ccbef6SBjörn Töpel 
8075ccbef6SBjörn Töpel 	if (refcount_dec_and_test(&entry->users)) {
8175ccbef6SBjörn Töpel 		entry->prog = NULL;
8275ccbef6SBjörn Töpel 		bpf_prog_put(prog);
8375ccbef6SBjörn Töpel 		d->num_progs--;
8475ccbef6SBjörn Töpel 		return true;
8575ccbef6SBjörn Töpel 	}
8675ccbef6SBjörn Töpel 	return false;
8775ccbef6SBjörn Töpel }
8875ccbef6SBjörn Töpel 
arch_prepare_bpf_dispatcher(void * image,void * buf,s64 * funcs,int num_funcs)8919c02415SSong Liu int __weak arch_prepare_bpf_dispatcher(void *image, void *buf, s64 *funcs, int num_funcs)
9075ccbef6SBjörn Töpel {
9175ccbef6SBjörn Töpel 	return -ENOTSUPP;
9275ccbef6SBjörn Töpel }
9375ccbef6SBjörn Töpel 
bpf_dispatcher_prepare(struct bpf_dispatcher * d,void * image,void * buf)9419c02415SSong Liu static int bpf_dispatcher_prepare(struct bpf_dispatcher *d, void *image, void *buf)
9575ccbef6SBjörn Töpel {
9675ccbef6SBjörn Töpel 	s64 ips[BPF_DISPATCHER_MAX] = {}, *ipsp = &ips[0];
9775ccbef6SBjörn Töpel 	int i;
9875ccbef6SBjörn Töpel 
9975ccbef6SBjörn Töpel 	for (i = 0; i < BPF_DISPATCHER_MAX; i++) {
10075ccbef6SBjörn Töpel 		if (d->progs[i].prog)
10175ccbef6SBjörn Töpel 			*ipsp++ = (s64)(uintptr_t)d->progs[i].prog->bpf_func;
10275ccbef6SBjörn Töpel 	}
10319c02415SSong Liu 	return arch_prepare_bpf_dispatcher(image, buf, &ips[0], d->num_progs);
10475ccbef6SBjörn Töpel }
10575ccbef6SBjörn Töpel 
bpf_dispatcher_update(struct bpf_dispatcher * d,int prev_num_progs)10675ccbef6SBjörn Töpel static void bpf_dispatcher_update(struct bpf_dispatcher *d, int prev_num_progs)
10775ccbef6SBjörn Töpel {
108c86df29dSPeter Zijlstra 	void *new, *tmp;
109c86df29dSPeter Zijlstra 	u32 noff = 0;
11075ccbef6SBjörn Töpel 
111c86df29dSPeter Zijlstra 	if (prev_num_progs)
1127ac88ebaSJiri Olsa 		noff = d->image_off ^ (PAGE_SIZE / 2);
11375ccbef6SBjörn Töpel 
11475ccbef6SBjörn Töpel 	new = d->num_progs ? d->image + noff : NULL;
11519c02415SSong Liu 	tmp = d->num_progs ? d->rw_image + noff : NULL;
11675ccbef6SBjörn Töpel 	if (new) {
11719c02415SSong Liu 		/* Prepare the dispatcher in d->rw_image. Then use
11819c02415SSong Liu 		 * bpf_arch_text_copy to update d->image, which is RO+X.
11919c02415SSong Liu 		 */
12019c02415SSong Liu 		if (bpf_dispatcher_prepare(d, new, tmp))
12119c02415SSong Liu 			return;
12219c02415SSong Liu 		if (IS_ERR(bpf_arch_text_copy(new, tmp, PAGE_SIZE / 2)))
12375ccbef6SBjörn Töpel 			return;
12475ccbef6SBjörn Töpel 	}
12575ccbef6SBjörn Töpel 
126a679120eSNathan Chancellor 	__BPF_DISPATCHER_UPDATE(d, new ?: (void *)&bpf_dispatcher_nop_func);
12775ccbef6SBjörn Töpel 
128*4121d448SJiri Olsa 	/* Make sure all the callers executing the previous/old half of the
129*4121d448SJiri Olsa 	 * image leave it, so following update call can modify it safely.
130*4121d448SJiri Olsa 	 */
131*4121d448SJiri Olsa 	synchronize_rcu();
132*4121d448SJiri Olsa 
133c86df29dSPeter Zijlstra 	if (new)
13475ccbef6SBjörn Töpel 		d->image_off = noff;
13575ccbef6SBjörn Töpel }
13675ccbef6SBjörn Töpel 
bpf_dispatcher_change_prog(struct bpf_dispatcher * d,struct bpf_prog * from,struct bpf_prog * to)13775ccbef6SBjörn Töpel void bpf_dispatcher_change_prog(struct bpf_dispatcher *d, struct bpf_prog *from,
13875ccbef6SBjörn Töpel 				struct bpf_prog *to)
13975ccbef6SBjörn Töpel {
14075ccbef6SBjörn Töpel 	bool changed = false;
14175ccbef6SBjörn Töpel 	int prev_num_progs;
14275ccbef6SBjörn Töpel 
14375ccbef6SBjörn Töpel 	if (from == to)
14475ccbef6SBjörn Töpel 		return;
14575ccbef6SBjörn Töpel 
14675ccbef6SBjörn Töpel 	mutex_lock(&d->mutex);
14775ccbef6SBjörn Töpel 	if (!d->image) {
14819c02415SSong Liu 		d->image = bpf_prog_pack_alloc(PAGE_SIZE, bpf_jit_fill_hole_with_zero);
14975ccbef6SBjörn Töpel 		if (!d->image)
15075ccbef6SBjörn Töpel 			goto out;
15119c02415SSong Liu 		d->rw_image = bpf_jit_alloc_exec(PAGE_SIZE);
15219c02415SSong Liu 		if (!d->rw_image) {
15319c02415SSong Liu 			u32 size = PAGE_SIZE;
15419c02415SSong Liu 
15519c02415SSong Liu 			bpf_arch_text_copy(d->image, &size, sizeof(size));
15619c02415SSong Liu 			bpf_prog_pack_free((struct bpf_binary_header *)d->image);
15719c02415SSong Liu 			d->image = NULL;
15819c02415SSong Liu 			goto out;
15919c02415SSong Liu 		}
160517b75e4SJiri Olsa 		bpf_image_ksym_add(d->image, &d->ksym);
16175ccbef6SBjörn Töpel 	}
16275ccbef6SBjörn Töpel 
16375ccbef6SBjörn Töpel 	prev_num_progs = d->num_progs;
16475ccbef6SBjörn Töpel 	changed |= bpf_dispatcher_remove_prog(d, from);
16575ccbef6SBjörn Töpel 	changed |= bpf_dispatcher_add_prog(d, to);
16675ccbef6SBjörn Töpel 
16775ccbef6SBjörn Töpel 	if (!changed)
16875ccbef6SBjörn Töpel 		goto out;
16975ccbef6SBjörn Töpel 
17075ccbef6SBjörn Töpel 	bpf_dispatcher_update(d, prev_num_progs);
17175ccbef6SBjörn Töpel out:
17275ccbef6SBjörn Töpel 	mutex_unlock(&d->mutex);
17375ccbef6SBjörn Töpel }
174