new file mode 100644
@@ -0,0 +1,57 @@
+/*
+ * QEMU Thread Context
+ *
+ * Copyright Red Hat Inc., 2022
+ *
+ * Authors:
+ * David Hildenbrand <david@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef SYSEMU_THREAD_CONTEXT_H
+#define SYSEMU_THREAD_CONTEXT_H
+
+#include "qapi/qapi-types-machine.h"
+#include "qemu/thread.h"
+#include "qom/object.h"
+
+#define TYPE_THREAD_CONTEXT "thread-context"
+OBJECT_DECLARE_TYPE(ThreadContext, ThreadContextClass,
+ THREAD_CONTEXT)
+
+struct ThreadContextClass {
+ ObjectClass parent_class;
+};
+
+struct ThreadContext {
+ /* private */
+ Object parent;
+
+ /* private */
+ unsigned int thread_id;
+ QemuThread thread;
+
+ /* Semaphore to wait for context thread action. */
+ QemuSemaphore sem;
+ /* Semaphore to wait for action in context thread. */
+ QemuSemaphore sem_thread;
+ /* Mutex to synchronize requests. */
+ QemuMutex mutex;
+
+ /* Commands for the thread to execute. */
+ int thread_cmd;
+ void *thread_cmd_data;
+
+ /* CPU affinity bitmap used for initialization. */
+ unsigned long *init_cpu_bitmap;
+ int init_cpu_nbits;
+};
+
+void thread_context_create_thread(ThreadContext *tc, QemuThread *thread,
+ const char *name,
+ void *(*start_routine)(void *), void *arg,
+ int mode);
+
+#endif /* SYSEMU_THREAD_CONTEXT_H */
@@ -830,6 +830,21 @@
'reduced-phys-bits': 'uint32',
'*kernel-hashes': 'bool' } }
+##
+# @ThreadContextProperties:
+#
+# Properties for thread context objects.
+#
+# @cpu-affinity: the list of host CPU numbers used as CPU affinity for all
+# threads created in the thread context (default: QEMU main
+# thread CPU affinity)
+#
+# Since: 7.2
+##
+{ 'struct': 'ThreadContextProperties',
+ 'data': { '*cpu-affinity': ['uint16'] } }
+
+
##
# @ObjectType:
#
@@ -882,6 +897,7 @@
{ 'name': 'secret_keyring',
'if': 'CONFIG_SECRET_KEYRING' },
'sev-guest',
+ 'thread-context',
's390-pv-guest',
'throttle-group',
'tls-creds-anon',
@@ -948,6 +964,7 @@
'secret_keyring': { 'type': 'SecretKeyringProperties',
'if': 'CONFIG_SECRET_KEYRING' },
'sev-guest': 'SevGuestProperties',
+ 'thread-context': 'ThreadContextProperties',
'throttle-group': 'ThrottleGroupProperties',
'tls-creds-anon': 'TlsCredsAnonProperties',
'tls-creds-psk': 'TlsCredsPskProperties',
@@ -1,4 +1,5 @@
util_ss.add(files('osdep.c', 'cutils.c', 'unicode.c', 'qemu-timer-common.c'))
+util_ss.add(files('thread-context.c'))
if not config_host_data.get('CONFIG_ATOMIC64')
util_ss.add(files('atomic64.c'))
endif
@@ -42,6 +42,7 @@
#include "qemu/cutils.h"
#include "qemu/compiler.h"
#include "qemu/units.h"
+#include "qemu/thread-context.h"
#ifdef CONFIG_LINUX
#include <sys/syscall.h>
new file mode 100644
@@ -0,0 +1,278 @@
+/*
+ * QEMU Thread Context
+ *
+ * Copyright Red Hat Inc., 2022
+ *
+ * Authors:
+ * David Hildenbrand <david@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/thread-context.h"
+#include "qapi/error.h"
+#include "qapi/qapi-builtin-visit.h"
+#include "qapi/visitor.h"
+#include "qemu/config-file.h"
+#include "qapi/qapi-builtin-visit.h"
+#include "qom/object_interfaces.h"
+#include "qemu/module.h"
+#include "qemu/bitmap.h"
+
+enum {
+ TC_CMD_NONE = 0,
+ TC_CMD_STOP,
+ TC_CMD_NEW,
+};
+
+typedef struct ThreadContextCmdNew {
+ QemuThread *thread;
+ const char *name;
+ void *(*start_routine)(void *);
+ void *arg;
+ int mode;
+} ThreadContextCmdNew;
+
+static void *thread_context_run(void *opaque)
+{
+ ThreadContext *tc = opaque;
+
+ tc->thread_id = qemu_get_thread_id();
+ qemu_sem_post(&tc->sem);
+
+ while (true) {
+ /*
+ * Threads inherit the CPU affinity of the creating thread. For this
+ * reason, we create new (especially short-lived) threads from our
+ * persistent context thread.
+ *
+ * Especially when QEMU is not allowed to set the affinity itself,
+ * management tools can simply set the affinity of the context thread
+ * after creating the context, to have new threads created via
+ * the context inherit the CPU affinity automatically.
+ */
+ switch (tc->thread_cmd) {
+ case TC_CMD_NONE:
+ break;
+ case TC_CMD_STOP:
+ tc->thread_cmd = TC_CMD_NONE;
+ qemu_sem_post(&tc->sem);
+ return NULL;
+ case TC_CMD_NEW: {
+ ThreadContextCmdNew *cmd_new = tc->thread_cmd_data;
+
+ qemu_thread_create(cmd_new->thread, cmd_new->name,
+ cmd_new->start_routine, cmd_new->arg,
+ cmd_new->mode);
+ tc->thread_cmd = TC_CMD_NONE;
+ tc->thread_cmd_data = NULL;
+ qemu_sem_post(&tc->sem);
+ break;
+ }
+ default:
+ g_assert_not_reached();
+ }
+ qemu_sem_wait(&tc->sem_thread);
+ }
+}
+
+static void thread_context_set_cpu_affinity(Object *obj, Visitor *v,
+ const char *name, void *opaque,
+ Error **errp)
+{
+ ThreadContext *tc = THREAD_CONTEXT(obj);
+ uint16List *l, *host_cpus = NULL;
+ unsigned long *bitmap = NULL;
+ int nbits = 0, ret;
+ Error *err = NULL;
+
+ visit_type_uint16List(v, name, &host_cpus, &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ if (!host_cpus) {
+ error_setg(errp, "CPU list is empty");
+ goto out;
+ }
+
+ for (l = host_cpus; l; l = l->next) {
+ nbits = MAX(nbits, l->value + 1);
+ }
+ bitmap = bitmap_new(nbits);
+ for (l = host_cpus; l; l = l->next) {
+ set_bit(l->value, bitmap);
+ }
+
+ if (tc->thread_id != -1) {
+ /*
+ * Note: we won't be adjusting the affinity of any thread that is still
+ * around, but only the affinity of the context thread.
+ */
+ ret = qemu_thread_set_affinity(&tc->thread, bitmap, nbits);
+ if (ret) {
+ error_setg(errp, "Setting CPU affinity failed: %s", strerror(ret));
+ }
+ } else {
+ tc->init_cpu_bitmap = bitmap;
+ bitmap = NULL;
+ tc->init_cpu_nbits = nbits;
+ }
+out:
+ g_free(bitmap);
+ qapi_free_uint16List(host_cpus);
+}
+
+static void thread_context_get_cpu_affinity(Object *obj, Visitor *v,
+ const char *name, void *opaque,
+ Error **errp)
+{
+ unsigned long *bitmap, nbits, value;
+ ThreadContext *tc = THREAD_CONTEXT(obj);
+ uint16List *host_cpus = NULL;
+ uint16List **tail = &host_cpus;
+ int ret;
+
+ if (tc->thread_id == -1) {
+ error_setg(errp, "Object not initialized yet");
+ return;
+ }
+
+ ret = qemu_thread_get_affinity(&tc->thread, &bitmap, &nbits);
+ if (ret) {
+ error_setg(errp, "Getting CPU affinity failed: %s", strerror(ret));
+ return;
+ }
+
+ value = find_first_bit(bitmap, nbits);
+ while (value < nbits) {
+ QAPI_LIST_APPEND(tail, value);
+
+ value = find_next_bit(bitmap, nbits, value + 1);
+ }
+ g_free(bitmap);
+
+ visit_type_uint16List(v, name, &host_cpus, errp);
+ qapi_free_uint16List(host_cpus);
+}
+
+static void thread_context_get_thread_id(Object *obj, Visitor *v,
+ const char *name, void *opaque,
+ Error **errp)
+{
+ ThreadContext *tc = THREAD_CONTEXT(obj);
+ uint64_t value = tc->thread_id;
+
+ visit_type_uint64(v, name, &value, errp);
+}
+
+static void thread_context_instance_complete(UserCreatable *uc, Error **errp)
+{
+ ThreadContext *tc = THREAD_CONTEXT(uc);
+ char *thread_name;
+ int ret;
+
+ thread_name = g_strdup_printf("TC %s",
+ object_get_canonical_path_component(OBJECT(uc)));
+ qemu_thread_create(&tc->thread, thread_name, thread_context_run, tc,
+ QEMU_THREAD_JOINABLE);
+ g_free(thread_name);
+
+ /* Wait until initialization of the thread is done. */
+ while (tc->thread_id == -1) {
+ qemu_sem_wait(&tc->sem);
+ }
+
+ if (tc->init_cpu_bitmap) {
+ ret = qemu_thread_set_affinity(&tc->thread, tc->init_cpu_bitmap,
+ tc->init_cpu_nbits);
+ if (ret) {
+ error_setg(errp, "Setting CPU affinity failed: %s", strerror(ret));
+ }
+ g_free(tc->init_cpu_bitmap);
+ tc->init_cpu_bitmap = NULL;
+ }
+}
+
+static void thread_context_class_init(ObjectClass *oc, void *data)
+{
+ UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
+
+ ucc->complete = thread_context_instance_complete;
+ object_class_property_add(oc, "thread-id", "int",
+ thread_context_get_thread_id, NULL, NULL,
+ NULL);
+ object_class_property_add(oc, "cpu-affinity", "int",
+ thread_context_get_cpu_affinity,
+ thread_context_set_cpu_affinity, NULL, NULL);
+}
+
+static void thread_context_instance_init(Object *obj)
+{
+ ThreadContext *tc = THREAD_CONTEXT(obj);
+
+ tc->thread_id = -1;
+ qemu_sem_init(&tc->sem, 0);
+ qemu_sem_init(&tc->sem_thread, 0);
+ qemu_mutex_init(&tc->mutex);
+}
+
+static void thread_context_instance_finalize(Object *obj)
+{
+ ThreadContext *tc = THREAD_CONTEXT(obj);
+
+ if (tc->thread_id != -1) {
+ tc->thread_cmd = TC_CMD_STOP;
+ qemu_sem_post(&tc->sem_thread);
+ qemu_thread_join(&tc->thread);
+ }
+ qemu_sem_destroy(&tc->sem);
+ qemu_sem_destroy(&tc->sem_thread);
+ qemu_mutex_destroy(&tc->mutex);
+}
+
+static const TypeInfo thread_context_info = {
+ .name = TYPE_THREAD_CONTEXT,
+ .parent = TYPE_OBJECT,
+ .class_init = thread_context_class_init,
+ .instance_size = sizeof(ThreadContext),
+ .instance_init = thread_context_instance_init,
+ .instance_finalize = thread_context_instance_finalize,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_USER_CREATABLE },
+ { }
+ }
+};
+
+static void thread_context_register_types(void)
+{
+ type_register_static(&thread_context_info);
+}
+type_init(thread_context_register_types)
+
+void thread_context_create_thread(ThreadContext *tc, QemuThread *thread,
+ const char *name,
+ void *(*start_routine)(void *), void *arg,
+ int mode)
+{
+ ThreadContextCmdNew data = {
+ .thread = thread,
+ .name = name,
+ .start_routine = start_routine,
+ .arg = arg,
+ .mode = mode,
+ };
+
+ qemu_mutex_lock(&tc->mutex);
+ tc->thread_cmd = TC_CMD_NEW;
+ tc->thread_cmd_data = &data;
+ qemu_sem_post(&tc->sem_thread);
+
+ while (tc->thread_cmd != TC_CMD_NONE) {
+ qemu_sem_wait(&tc->sem);
+ }
+ qemu_mutex_unlock(&tc->mutex);
+}