]>
Commit | Line | Data |
---|---|---|
54cb65d8 EC |
1 | /* |
2 | * QEMU Plugin Core Loader Code | |
3 | * | |
4 | * This is the code responsible for loading and unloading the plugins. | |
5 | * Aside from the basic housekeeping tasks we also need to ensure any | |
6 | * generated code is flushed when we remove a plugin so we cannot end | |
7 | * up calling and unloaded helper function. | |
8 | * | |
9 | * Copyright (C) 2017, Emilio G. Cota <cota@braap.org> | |
10 | * Copyright (C) 2019, Linaro | |
11 | * | |
12 | * License: GNU GPL, version 2 or later. | |
13 | * See the COPYING file in the top-level directory. | |
14 | * | |
15 | * SPDX-License-Identifier: GPL-2.0-or-later | |
16 | */ | |
17 | ||
18 | #include "qemu/osdep.h" | |
19 | #include "qemu/error-report.h" | |
20 | #include "qemu/config-file.h" | |
21 | #include "qapi/error.h" | |
ac90871c | 22 | #include "qemu/lockable.h" |
54cb65d8 EC |
23 | #include "qemu/option.h" |
24 | #include "qemu/rcu_queue.h" | |
25 | #include "qemu/qht.h" | |
26 | #include "qemu/bitmap.h" | |
27 | #include "qemu/xxhash.h" | |
28 | #include "qemu/plugin.h" | |
29 | #include "hw/core/cpu.h" | |
30 | #include "cpu.h" | |
31 | #include "exec/exec-all.h" | |
5901b2e1 AB |
32 | #ifndef CONFIG_USER_ONLY |
33 | #include "hw/boards.h" | |
34 | #endif | |
35 | ||
54cb65d8 EC |
36 | #include "plugin.h" |
37 | ||
38 | /* | |
39 | * For convenience we use a bitmap for plugin.mask, but really all we need is a | |
40 | * u32, which is what we store in TranslationBlock. | |
41 | */ | |
42 | QEMU_BUILD_BUG_ON(QEMU_PLUGIN_EV_MAX > 32); | |
43 | ||
44 | struct qemu_plugin_desc { | |
45 | char *path; | |
46 | char **argv; | |
47 | QTAILQ_ENTRY(qemu_plugin_desc) entry; | |
48 | int argc; | |
49 | }; | |
50 | ||
51 | struct qemu_plugin_parse_arg { | |
52 | QemuPluginList *head; | |
53 | struct qemu_plugin_desc *curr; | |
54 | }; | |
55 | ||
56 | QemuOptsList qemu_plugin_opts = { | |
57 | .name = "plugin", | |
58 | .implied_opt_name = "file", | |
59 | .head = QTAILQ_HEAD_INITIALIZER(qemu_plugin_opts.head), | |
60 | .desc = { | |
61 | /* do our own parsing to support multiple plugins */ | |
62 | { /* end of list */ } | |
63 | }, | |
64 | }; | |
65 | ||
5901b2e1 | 66 | typedef int (*qemu_plugin_install_func_t)(qemu_plugin_id_t, const qemu_info_t *, int, char **); |
54cb65d8 EC |
67 | |
68 | extern struct qemu_plugin_state plugin; | |
69 | ||
70 | void qemu_plugin_add_dyn_cb_arr(GArray *arr) | |
71 | { | |
72 | uint32_t hash = qemu_xxhash2((uint64_t)(uintptr_t)arr); | |
73 | bool inserted; | |
74 | ||
75 | inserted = qht_insert(&plugin.dyn_cb_arr_ht, arr, hash, NULL); | |
76 | g_assert(inserted); | |
77 | } | |
78 | ||
79 | static struct qemu_plugin_desc *plugin_find_desc(QemuPluginList *head, | |
80 | const char *path) | |
81 | { | |
82 | struct qemu_plugin_desc *desc; | |
83 | ||
84 | QTAILQ_FOREACH(desc, head, entry) { | |
85 | if (strcmp(desc->path, path) == 0) { | |
86 | return desc; | |
87 | } | |
88 | } | |
89 | return NULL; | |
90 | } | |
91 | ||
92 | static int plugin_add(void *opaque, const char *name, const char *value, | |
93 | Error **errp) | |
94 | { | |
95 | struct qemu_plugin_parse_arg *arg = opaque; | |
96 | struct qemu_plugin_desc *p; | |
97 | ||
98 | if (strcmp(name, "file") == 0) { | |
99 | if (strcmp(value, "") == 0) { | |
100 | error_setg(errp, "requires a non-empty argument"); | |
101 | return 1; | |
102 | } | |
103 | p = plugin_find_desc(arg->head, value); | |
104 | if (p == NULL) { | |
105 | p = g_new0(struct qemu_plugin_desc, 1); | |
106 | p->path = g_strdup(value); | |
107 | QTAILQ_INSERT_TAIL(arg->head, p, entry); | |
108 | } | |
109 | arg->curr = p; | |
110 | } else if (strcmp(name, "arg") == 0) { | |
111 | if (arg->curr == NULL) { | |
112 | error_setg(errp, "missing earlier '-plugin file=' option"); | |
113 | return 1; | |
114 | } | |
115 | p = arg->curr; | |
116 | p->argc++; | |
117 | p->argv = g_realloc_n(p->argv, p->argc, sizeof(char *)); | |
118 | p->argv[p->argc - 1] = g_strdup(value); | |
119 | } else { | |
120 | error_setg(errp, "-plugin: unexpected parameter '%s'; ignored", name); | |
121 | } | |
122 | return 0; | |
123 | } | |
124 | ||
125 | void qemu_plugin_opt_parse(const char *optarg, QemuPluginList *head) | |
126 | { | |
127 | struct qemu_plugin_parse_arg arg; | |
128 | QemuOpts *opts; | |
129 | ||
130 | opts = qemu_opts_parse_noisily(qemu_find_opts("plugin"), optarg, true); | |
131 | if (opts == NULL) { | |
132 | exit(1); | |
133 | } | |
134 | arg.head = head; | |
135 | arg.curr = NULL; | |
136 | qemu_opt_foreach(opts, plugin_add, &arg, &error_fatal); | |
137 | qemu_opts_del(opts); | |
138 | } | |
139 | ||
140 | /* | |
141 | * From: https://en.wikipedia.org/wiki/Xorshift | |
142 | * This is faster than rand_r(), and gives us a wider range (RAND_MAX is only | |
143 | * guaranteed to be >= INT_MAX). | |
144 | */ | |
145 | static uint64_t xorshift64star(uint64_t x) | |
146 | { | |
147 | x ^= x >> 12; /* a */ | |
148 | x ^= x << 25; /* b */ | |
149 | x ^= x >> 27; /* c */ | |
150 | return x * UINT64_C(2685821657736338717); | |
151 | } | |
152 | ||
0572f558 | 153 | static int plugin_load(struct qemu_plugin_desc *desc, const qemu_info_t *info, Error **errp) |
54cb65d8 EC |
154 | { |
155 | qemu_plugin_install_func_t install; | |
156 | struct qemu_plugin_ctx *ctx; | |
157 | gpointer sym; | |
158 | int rc; | |
159 | ||
160 | ctx = qemu_memalign(qemu_dcache_linesize, sizeof(*ctx)); | |
161 | memset(ctx, 0, sizeof(*ctx)); | |
162 | ctx->desc = desc; | |
163 | ||
164 | ctx->handle = g_module_open(desc->path, G_MODULE_BIND_LOCAL); | |
165 | if (ctx->handle == NULL) { | |
0572f558 | 166 | error_setg(errp, "Could not load plugin %s: %s", desc->path, g_module_error()); |
54cb65d8 EC |
167 | goto err_dlopen; |
168 | } | |
169 | ||
170 | if (!g_module_symbol(ctx->handle, "qemu_plugin_install", &sym)) { | |
0572f558 | 171 | error_setg(errp, "Could not load plugin %s: %s", desc->path, g_module_error()); |
54cb65d8 EC |
172 | goto err_symbol; |
173 | } | |
174 | install = (qemu_plugin_install_func_t) sym; | |
175 | /* symbol was found; it could be NULL though */ | |
176 | if (install == NULL) { | |
0572f558 PB |
177 | error_setg(errp, "Could not load plugin %s: qemu_plugin_install is NULL", |
178 | desc->path); | |
54cb65d8 EC |
179 | goto err_symbol; |
180 | } | |
181 | ||
3fb356cc | 182 | if (!g_module_symbol(ctx->handle, "qemu_plugin_version", &sym)) { |
0572f558 PB |
183 | error_setg(errp, "Could not load plugin %s: plugin does not declare API version %s", |
184 | desc->path, g_module_error()); | |
3fb356cc AB |
185 | goto err_symbol; |
186 | } else { | |
187 | int version = *(int *)sym; | |
188 | if (version < QEMU_PLUGIN_MIN_VERSION) { | |
0572f558 PB |
189 | error_setg(errp, "Could not load plugin %s: plugin requires API version %d, but " |
190 | "this QEMU supports only a minimum version of %d", | |
191 | desc->path, version, QEMU_PLUGIN_MIN_VERSION); | |
3fb356cc AB |
192 | goto err_symbol; |
193 | } else if (version > QEMU_PLUGIN_VERSION) { | |
0572f558 PB |
194 | error_setg(errp, "Could not load plugin %s: plugin requires API version %d, but " |
195 | "this QEMU supports only up to version %d", | |
196 | desc->path, version, QEMU_PLUGIN_VERSION); | |
3fb356cc AB |
197 | goto err_symbol; |
198 | } | |
199 | } | |
200 | ||
54cb65d8 EC |
201 | qemu_rec_mutex_lock(&plugin.lock); |
202 | ||
203 | /* find an unused random id with &ctx as the seed */ | |
204 | ctx->id = (uint64_t)(uintptr_t)ctx; | |
205 | for (;;) { | |
206 | void *existing; | |
207 | ||
208 | ctx->id = xorshift64star(ctx->id); | |
209 | existing = g_hash_table_lookup(plugin.id_ht, &ctx->id); | |
210 | if (likely(existing == NULL)) { | |
211 | bool success; | |
212 | ||
213 | success = g_hash_table_insert(plugin.id_ht, &ctx->id, &ctx->id); | |
214 | g_assert(success); | |
215 | break; | |
216 | } | |
217 | } | |
218 | QTAILQ_INSERT_TAIL(&plugin.ctxs, ctx, entry); | |
219 | ctx->installing = true; | |
5901b2e1 | 220 | rc = install(ctx->id, info, desc->argc, desc->argv); |
54cb65d8 EC |
221 | ctx->installing = false; |
222 | if (rc) { | |
0572f558 PB |
223 | error_setg(errp, "Could not load plugin %s: qemu_plugin_install returned error code %d", |
224 | desc->path, rc); | |
54cb65d8 EC |
225 | /* |
226 | * we cannot rely on the plugin doing its own cleanup, so | |
227 | * call a full uninstall if the plugin did not yet call it. | |
228 | */ | |
229 | if (!ctx->uninstalling) { | |
230 | plugin_reset_uninstall(ctx->id, NULL, false); | |
231 | } | |
232 | } | |
233 | ||
234 | qemu_rec_mutex_unlock(&plugin.lock); | |
235 | return rc; | |
236 | ||
237 | err_symbol: | |
b3137100 | 238 | g_module_close(ctx->handle); |
54cb65d8 EC |
239 | err_dlopen: |
240 | qemu_vfree(ctx); | |
241 | return 1; | |
242 | } | |
243 | ||
244 | /* call after having removed @desc from the list */ | |
245 | static void plugin_desc_free(struct qemu_plugin_desc *desc) | |
246 | { | |
247 | int i; | |
248 | ||
249 | for (i = 0; i < desc->argc; i++) { | |
250 | g_free(desc->argv[i]); | |
251 | } | |
252 | g_free(desc->argv); | |
253 | g_free(desc->path); | |
254 | g_free(desc); | |
255 | } | |
256 | ||
257 | /** | |
258 | * qemu_plugin_load_list - load a list of plugins | |
259 | * @head: head of the list of descriptors of the plugins to be loaded | |
260 | * | |
261 | * Returns 0 if all plugins in the list are installed, !0 otherwise. | |
262 | * | |
263 | * Note: the descriptor of each successfully installed plugin is removed | |
264 | * from the list given by @head. | |
265 | */ | |
0572f558 | 266 | int qemu_plugin_load_list(QemuPluginList *head, Error **errp) |
54cb65d8 EC |
267 | { |
268 | struct qemu_plugin_desc *desc, *next; | |
5901b2e1 AB |
269 | g_autofree qemu_info_t *info = g_new0(qemu_info_t, 1); |
270 | ||
271 | info->target_name = TARGET_NAME; | |
3fb356cc AB |
272 | info->version.min = QEMU_PLUGIN_MIN_VERSION; |
273 | info->version.cur = QEMU_PLUGIN_VERSION; | |
5901b2e1 AB |
274 | #ifndef CONFIG_USER_ONLY |
275 | MachineState *ms = MACHINE(qdev_get_machine()); | |
276 | info->system_emulation = true; | |
277 | info->system.smp_vcpus = ms->smp.cpus; | |
278 | info->system.max_vcpus = ms->smp.max_cpus; | |
279 | #else | |
280 | info->system_emulation = false; | |
281 | #endif | |
54cb65d8 EC |
282 | |
283 | QTAILQ_FOREACH_SAFE(desc, head, entry, next) { | |
284 | int err; | |
285 | ||
0572f558 | 286 | err = plugin_load(desc, info, errp); |
54cb65d8 EC |
287 | if (err) { |
288 | return err; | |
289 | } | |
290 | QTAILQ_REMOVE(head, desc, entry); | |
291 | } | |
292 | return 0; | |
293 | } | |
294 | ||
295 | struct qemu_plugin_reset_data { | |
296 | struct qemu_plugin_ctx *ctx; | |
297 | qemu_plugin_simple_cb_t cb; | |
298 | bool reset; | |
299 | }; | |
300 | ||
301 | static void plugin_reset_destroy__locked(struct qemu_plugin_reset_data *data) | |
302 | { | |
303 | struct qemu_plugin_ctx *ctx = data->ctx; | |
304 | enum qemu_plugin_event ev; | |
305 | bool success; | |
306 | ||
307 | /* | |
308 | * After updating the subscription lists there is no need to wait for an RCU | |
309 | * grace period to elapse, because right now we either are in a "safe async" | |
310 | * work environment (i.e. all vCPUs are asleep), or no vCPUs have yet been | |
311 | * created. | |
312 | */ | |
313 | for (ev = 0; ev < QEMU_PLUGIN_EV_MAX; ev++) { | |
314 | plugin_unregister_cb__locked(ctx, ev); | |
315 | } | |
316 | ||
317 | if (data->reset) { | |
318 | g_assert(ctx->resetting); | |
319 | if (data->cb) { | |
320 | data->cb(ctx->id); | |
321 | } | |
322 | ctx->resetting = false; | |
323 | g_free(data); | |
324 | return; | |
325 | } | |
326 | ||
327 | g_assert(ctx->uninstalling); | |
328 | /* we cannot dlclose if we are going to return to plugin code */ | |
329 | if (ctx->installing) { | |
330 | error_report("Calling qemu_plugin_uninstall from the install function " | |
331 | "is a bug. Instead, return !0 from the install function."); | |
332 | abort(); | |
333 | } | |
334 | ||
335 | success = g_hash_table_remove(plugin.id_ht, &ctx->id); | |
336 | g_assert(success); | |
337 | QTAILQ_REMOVE(&plugin.ctxs, ctx, entry); | |
338 | if (data->cb) { | |
339 | data->cb(ctx->id); | |
340 | } | |
341 | if (!g_module_close(ctx->handle)) { | |
342 | warn_report("%s: %s", __func__, g_module_error()); | |
343 | } | |
344 | plugin_desc_free(ctx->desc); | |
345 | qemu_vfree(ctx); | |
346 | g_free(data); | |
347 | } | |
348 | ||
349 | static void plugin_reset_destroy(struct qemu_plugin_reset_data *data) | |
350 | { | |
351 | qemu_rec_mutex_lock(&plugin.lock); | |
352 | plugin_reset_destroy__locked(data); | |
353 | qemu_rec_mutex_lock(&plugin.lock); | |
354 | } | |
355 | ||
356 | static void plugin_flush_destroy(CPUState *cpu, run_on_cpu_data arg) | |
357 | { | |
358 | struct qemu_plugin_reset_data *data = arg.host_ptr; | |
359 | ||
360 | g_assert(cpu_in_exclusive_context(cpu)); | |
361 | tb_flush(cpu); | |
362 | plugin_reset_destroy(data); | |
363 | } | |
364 | ||
365 | void plugin_reset_uninstall(qemu_plugin_id_t id, | |
366 | qemu_plugin_simple_cb_t cb, | |
367 | bool reset) | |
368 | { | |
369 | struct qemu_plugin_reset_data *data; | |
370 | struct qemu_plugin_ctx *ctx; | |
371 | ||
ac90871c SH |
372 | WITH_QEMU_LOCK_GUARD(&plugin.lock) { |
373 | ctx = plugin_id_to_ctx_locked(id); | |
374 | if (ctx->uninstalling || (reset && ctx->resetting)) { | |
375 | return; | |
376 | } | |
377 | ctx->resetting = reset; | |
378 | ctx->uninstalling = !reset; | |
54cb65d8 | 379 | } |
54cb65d8 EC |
380 | |
381 | data = g_new(struct qemu_plugin_reset_data, 1); | |
382 | data->ctx = ctx; | |
383 | data->cb = cb; | |
384 | data->reset = reset; | |
385 | /* | |
386 | * Only flush the code cache if the vCPUs have been created. If so, | |
387 | * current_cpu must be non-NULL. | |
388 | */ | |
389 | if (current_cpu) { | |
390 | async_safe_run_on_cpu(current_cpu, plugin_flush_destroy, | |
391 | RUN_ON_CPU_HOST_PTR(data)); | |
392 | } else { | |
393 | /* | |
394 | * If current_cpu isn't set, then we don't have yet any vCPU threads | |
395 | * and we therefore can remove the callbacks synchronously. | |
396 | */ | |
397 | plugin_reset_destroy(data); | |
398 | } | |
399 | } |