| // 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); |
| } |