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