]>
Commit | Line | Data |
---|---|---|
75ccbef6 BT |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* Copyright(c) 2019 Intel Corporation. */ | |
3 | ||
4 | #include <linux/hash.h> | |
5 | #include <linux/bpf.h> | |
6 | #include <linux/filter.h> | |
c86df29d | 7 | #include <linux/static_call.h> |
75ccbef6 BT |
8 | |
9 | /* The BPF dispatcher is a multiway branch code generator. The | |
10 | * dispatcher is a mechanism to avoid the performance penalty of an | |
11 | * indirect call, which is expensive when retpolines are enabled. A | |
12 | * dispatch client registers a BPF program into the dispatcher, and if | |
13 | * there is available room in the dispatcher a direct call to the BPF | |
14 | * program will be generated. All calls to the BPF programs called via | |
15 | * the dispatcher will then be a direct call, instead of an | |
16 | * indirect. The dispatcher hijacks a trampoline function it via the | |
17 | * __fentry__ of the trampoline. The trampoline function has the | |
18 | * following signature: | |
19 | * | |
20 | * unsigned int trampoline(const void *ctx, const struct bpf_insn *insnsi, | |
21 | * unsigned int (*bpf_func)(const void *, | |
22 | * const struct bpf_insn *)); | |
23 | */ | |
24 | ||
25 | static struct bpf_dispatcher_prog *bpf_dispatcher_find_prog( | |
26 | struct bpf_dispatcher *d, struct bpf_prog *prog) | |
27 | { | |
28 | int i; | |
29 | ||
30 | for (i = 0; i < BPF_DISPATCHER_MAX; i++) { | |
31 | if (prog == d->progs[i].prog) | |
32 | return &d->progs[i]; | |
33 | } | |
34 | return NULL; | |
35 | } | |
36 | ||
37 | static struct bpf_dispatcher_prog *bpf_dispatcher_find_free( | |
38 | struct bpf_dispatcher *d) | |
39 | { | |
40 | return bpf_dispatcher_find_prog(d, NULL); | |
41 | } | |
42 | ||
43 | static bool bpf_dispatcher_add_prog(struct bpf_dispatcher *d, | |
44 | struct bpf_prog *prog) | |
45 | { | |
46 | struct bpf_dispatcher_prog *entry; | |
47 | ||
48 | if (!prog) | |
49 | return false; | |
50 | ||
51 | entry = bpf_dispatcher_find_prog(d, prog); | |
52 | if (entry) { | |
53 | refcount_inc(&entry->users); | |
54 | return false; | |
55 | } | |
56 | ||
57 | entry = bpf_dispatcher_find_free(d); | |
58 | if (!entry) | |
59 | return false; | |
60 | ||
61 | bpf_prog_inc(prog); | |
62 | entry->prog = prog; | |
63 | refcount_set(&entry->users, 1); | |
64 | d->num_progs++; | |
65 | return true; | |
66 | } | |
67 | ||
68 | static bool bpf_dispatcher_remove_prog(struct bpf_dispatcher *d, | |
69 | struct bpf_prog *prog) | |
70 | { | |
71 | struct bpf_dispatcher_prog *entry; | |
72 | ||
73 | if (!prog) | |
74 | return false; | |
75 | ||
76 | entry = bpf_dispatcher_find_prog(d, prog); | |
77 | if (!entry) | |
78 | return false; | |
79 | ||
80 | if (refcount_dec_and_test(&entry->users)) { | |
81 | entry->prog = NULL; | |
82 | bpf_prog_put(prog); | |
83 | d->num_progs--; | |
84 | return true; | |
85 | } | |
86 | return false; | |
87 | } | |
88 | ||
19c02415 | 89 | int __weak arch_prepare_bpf_dispatcher(void *image, void *buf, s64 *funcs, int num_funcs) |
75ccbef6 BT |
90 | { |
91 | return -ENOTSUPP; | |
92 | } | |
93 | ||
19c02415 | 94 | static int bpf_dispatcher_prepare(struct bpf_dispatcher *d, void *image, void *buf) |
75ccbef6 BT |
95 | { |
96 | s64 ips[BPF_DISPATCHER_MAX] = {}, *ipsp = &ips[0]; | |
97 | int i; | |
98 | ||
99 | for (i = 0; i < BPF_DISPATCHER_MAX; i++) { | |
100 | if (d->progs[i].prog) | |
101 | *ipsp++ = (s64)(uintptr_t)d->progs[i].prog->bpf_func; | |
102 | } | |
19c02415 | 103 | return arch_prepare_bpf_dispatcher(image, buf, &ips[0], d->num_progs); |
75ccbef6 BT |
104 | } |
105 | ||
106 | static void bpf_dispatcher_update(struct bpf_dispatcher *d, int prev_num_progs) | |
107 | { | |
c86df29d PZ |
108 | void *new, *tmp; |
109 | u32 noff = 0; | |
110 | ||
111 | if (prev_num_progs) | |
7ac88eba | 112 | noff = d->image_off ^ (PAGE_SIZE / 2); |
75ccbef6 BT |
113 | |
114 | new = d->num_progs ? d->image + noff : NULL; | |
19c02415 | 115 | tmp = d->num_progs ? d->rw_image + noff : NULL; |
75ccbef6 | 116 | if (new) { |
19c02415 SL |
117 | /* Prepare the dispatcher in d->rw_image. Then use |
118 | * bpf_arch_text_copy to update d->image, which is RO+X. | |
119 | */ | |
120 | if (bpf_dispatcher_prepare(d, new, tmp)) | |
121 | return; | |
122 | if (IS_ERR(bpf_arch_text_copy(new, tmp, PAGE_SIZE / 2))) | |
75ccbef6 BT |
123 | return; |
124 | } | |
125 | ||
a679120e | 126 | __BPF_DISPATCHER_UPDATE(d, new ?: (void *)&bpf_dispatcher_nop_func); |
75ccbef6 | 127 | |
c86df29d PZ |
128 | if (new) |
129 | d->image_off = noff; | |
75ccbef6 BT |
130 | } |
131 | ||
132 | void bpf_dispatcher_change_prog(struct bpf_dispatcher *d, struct bpf_prog *from, | |
133 | struct bpf_prog *to) | |
134 | { | |
135 | bool changed = false; | |
136 | int prev_num_progs; | |
137 | ||
138 | if (from == to) | |
139 | return; | |
140 | ||
141 | mutex_lock(&d->mutex); | |
142 | if (!d->image) { | |
19c02415 | 143 | d->image = bpf_prog_pack_alloc(PAGE_SIZE, bpf_jit_fill_hole_with_zero); |
75ccbef6 BT |
144 | if (!d->image) |
145 | goto out; | |
19c02415 SL |
146 | d->rw_image = bpf_jit_alloc_exec(PAGE_SIZE); |
147 | if (!d->rw_image) { | |
148 | u32 size = PAGE_SIZE; | |
149 | ||
150 | bpf_arch_text_copy(d->image, &size, sizeof(size)); | |
151 | bpf_prog_pack_free((struct bpf_binary_header *)d->image); | |
152 | d->image = NULL; | |
153 | goto out; | |
154 | } | |
517b75e4 | 155 | bpf_image_ksym_add(d->image, &d->ksym); |
75ccbef6 BT |
156 | } |
157 | ||
158 | prev_num_progs = d->num_progs; | |
159 | changed |= bpf_dispatcher_remove_prog(d, from); | |
160 | changed |= bpf_dispatcher_add_prog(d, to); | |
161 | ||
162 | if (!changed) | |
163 | goto out; | |
164 | ||
165 | bpf_dispatcher_update(d, prev_num_progs); | |
166 | out: | |
167 | mutex_unlock(&d->mutex); | |
168 | } |