| // SPDX-License-Identifier: GPL-2.0-only | 
 | /* Copyright(c) 2019 Intel Corporation. */ | 
 |  | 
 | #include <linux/hash.h> | 
 | #include <linux/bpf.h> | 
 | #include <linux/filter.h> | 
 |  | 
 | /* The BPF dispatcher is a multiway branch code generator. The | 
 |  * dispatcher is a mechanism to avoid the performance penalty of an | 
 |  * indirect call, which is expensive when retpolines are enabled. A | 
 |  * dispatch client registers a BPF program into the dispatcher, and if | 
 |  * there is available room in the dispatcher a direct call to the BPF | 
 |  * program will be generated. All calls to the BPF programs called via | 
 |  * the dispatcher will then be a direct call, instead of an | 
 |  * indirect. The dispatcher hijacks a trampoline function it via the | 
 |  * __fentry__ of the trampoline. The trampoline function has the | 
 |  * following signature: | 
 |  * | 
 |  * unsigned int trampoline(const void *ctx, const struct bpf_insn *insnsi, | 
 |  *                         unsigned int (*bpf_func)(const void *, | 
 |  *                                                  const struct bpf_insn *)); | 
 |  */ | 
 |  | 
 | static struct bpf_dispatcher_prog *bpf_dispatcher_find_prog( | 
 | 	struct bpf_dispatcher *d, struct bpf_prog *prog) | 
 | { | 
 | 	int i; | 
 |  | 
 | 	for (i = 0; i < BPF_DISPATCHER_MAX; i++) { | 
 | 		if (prog == d->progs[i].prog) | 
 | 			return &d->progs[i]; | 
 | 	} | 
 | 	return NULL; | 
 | } | 
 |  | 
 | static struct bpf_dispatcher_prog *bpf_dispatcher_find_free( | 
 | 	struct bpf_dispatcher *d) | 
 | { | 
 | 	return bpf_dispatcher_find_prog(d, NULL); | 
 | } | 
 |  | 
 | static bool bpf_dispatcher_add_prog(struct bpf_dispatcher *d, | 
 | 				    struct bpf_prog *prog) | 
 | { | 
 | 	struct bpf_dispatcher_prog *entry; | 
 |  | 
 | 	if (!prog) | 
 | 		return false; | 
 |  | 
 | 	entry = bpf_dispatcher_find_prog(d, prog); | 
 | 	if (entry) { | 
 | 		refcount_inc(&entry->users); | 
 | 		return false; | 
 | 	} | 
 |  | 
 | 	entry = bpf_dispatcher_find_free(d); | 
 | 	if (!entry) | 
 | 		return false; | 
 |  | 
 | 	bpf_prog_inc(prog); | 
 | 	entry->prog = prog; | 
 | 	refcount_set(&entry->users, 1); | 
 | 	d->num_progs++; | 
 | 	return true; | 
 | } | 
 |  | 
 | static bool bpf_dispatcher_remove_prog(struct bpf_dispatcher *d, | 
 | 				       struct bpf_prog *prog) | 
 | { | 
 | 	struct bpf_dispatcher_prog *entry; | 
 |  | 
 | 	if (!prog) | 
 | 		return false; | 
 |  | 
 | 	entry = bpf_dispatcher_find_prog(d, prog); | 
 | 	if (!entry) | 
 | 		return false; | 
 |  | 
 | 	if (refcount_dec_and_test(&entry->users)) { | 
 | 		entry->prog = NULL; | 
 | 		bpf_prog_put(prog); | 
 | 		d->num_progs--; | 
 | 		return true; | 
 | 	} | 
 | 	return false; | 
 | } | 
 |  | 
 | int __weak arch_prepare_bpf_dispatcher(void *image, s64 *funcs, int num_funcs) | 
 | { | 
 | 	return -ENOTSUPP; | 
 | } | 
 |  | 
 | static int bpf_dispatcher_prepare(struct bpf_dispatcher *d, void *image) | 
 | { | 
 | 	s64 ips[BPF_DISPATCHER_MAX] = {}, *ipsp = &ips[0]; | 
 | 	int i; | 
 |  | 
 | 	for (i = 0; i < BPF_DISPATCHER_MAX; i++) { | 
 | 		if (d->progs[i].prog) | 
 | 			*ipsp++ = (s64)(uintptr_t)d->progs[i].prog->bpf_func; | 
 | 	} | 
 | 	return arch_prepare_bpf_dispatcher(image, &ips[0], d->num_progs); | 
 | } | 
 |  | 
 | static void bpf_dispatcher_update(struct bpf_dispatcher *d, int prev_num_progs) | 
 | { | 
 | 	void *old, *new; | 
 | 	u32 noff; | 
 | 	int err; | 
 |  | 
 | 	if (!prev_num_progs) { | 
 | 		old = NULL; | 
 | 		noff = 0; | 
 | 	} else { | 
 | 		old = d->image + d->image_off; | 
 | 		noff = d->image_off ^ (PAGE_SIZE / 2); | 
 | 	} | 
 |  | 
 | 	new = d->num_progs ? d->image + noff : NULL; | 
 | 	if (new) { | 
 | 		if (bpf_dispatcher_prepare(d, new)) | 
 | 			return; | 
 | 	} | 
 |  | 
 | 	err = bpf_arch_text_poke(d->func, BPF_MOD_JUMP, old, new); | 
 | 	if (err || !new) | 
 | 		return; | 
 |  | 
 | 	d->image_off = noff; | 
 | } | 
 |  | 
 | void bpf_dispatcher_change_prog(struct bpf_dispatcher *d, struct bpf_prog *from, | 
 | 				struct bpf_prog *to) | 
 | { | 
 | 	bool changed = false; | 
 | 	int prev_num_progs; | 
 |  | 
 | 	if (from == to) | 
 | 		return; | 
 |  | 
 | 	mutex_lock(&d->mutex); | 
 | 	if (!d->image) { | 
 | 		d->image = bpf_jit_alloc_exec_page(); | 
 | 		if (!d->image) | 
 | 			goto out; | 
 | 		bpf_image_ksym_add(d->image, &d->ksym); | 
 | 	} | 
 |  | 
 | 	prev_num_progs = d->num_progs; | 
 | 	changed |= bpf_dispatcher_remove_prog(d, from); | 
 | 	changed |= bpf_dispatcher_add_prog(d, to); | 
 |  | 
 | 	if (!changed) | 
 | 		goto out; | 
 |  | 
 | 	bpf_dispatcher_update(d, prev_num_progs); | 
 | out: | 
 | 	mutex_unlock(&d->mutex); | 
 | } |