| /* |
| * 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" |
| |
| #ifdef CONFIG_NUMA |
| #include <numa.h> |
| #endif |
| |
| 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; |
| |
| if (tc->init_cpu_bitmap) { |
| error_setg(errp, "Mixing CPU and node affinity not supported"); |
| return; |
| } |
| |
| 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_set_node_affinity(Object *obj, Visitor *v, |
| const char *name, void *opaque, |
| Error **errp) |
| { |
| #ifdef CONFIG_NUMA |
| const int nbits = numa_num_possible_cpus(); |
| ThreadContext *tc = THREAD_CONTEXT(obj); |
| uint16List *l, *host_nodes = NULL; |
| unsigned long *bitmap = NULL; |
| struct bitmask *tmp_cpus; |
| Error *err = NULL; |
| int ret, i; |
| |
| if (tc->init_cpu_bitmap) { |
| error_setg(errp, "Mixing CPU and node affinity not supported"); |
| return; |
| } |
| |
| visit_type_uint16List(v, name, &host_nodes, &err); |
| if (err) { |
| error_propagate(errp, err); |
| return; |
| } |
| |
| if (!host_nodes) { |
| error_setg(errp, "Node list is empty"); |
| goto out; |
| } |
| |
| bitmap = bitmap_new(nbits); |
| tmp_cpus = numa_allocate_cpumask(); |
| for (l = host_nodes; l; l = l->next) { |
| numa_bitmask_clearall(tmp_cpus); |
| ret = numa_node_to_cpus(l->value, tmp_cpus); |
| if (ret) { |
| /* We ignore any errors, such as impossible nodes. */ |
| continue; |
| } |
| for (i = 0; i < nbits; i++) { |
| if (numa_bitmask_isbitset(tmp_cpus, i)) { |
| set_bit(i, bitmap); |
| } |
| } |
| } |
| numa_free_cpumask(tmp_cpus); |
| |
| if (bitmap_empty(bitmap, nbits)) { |
| error_setg(errp, "The nodes select no CPUs"); |
| goto out; |
| } |
| |
| if (tc->thread_id != -1) { |
| /* |
| * Note: we won't be adjusting the affinity of any thread that is still |
| * around for now, 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_nodes); |
| #else |
| error_setg(errp, "NUMA node affinity is not supported by this QEMU"); |
| #endif |
| } |
| |
| 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); |
| object_class_property_add(oc, "node-affinity", "int", NULL, |
| thread_context_set_node_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); |
| } |