xref: /openbmc/qemu/util/thread-context.c (revision d1c81c34)
1 /*
2  * QEMU Thread Context
3  *
4  * Copyright Red Hat Inc., 2022
5  *
6  * Authors:
7  *  David Hildenbrand <david@redhat.com>
8  *
9  * This work is licensed under the terms of the GNU GPL, version 2 or later.
10  * See the COPYING file in the top-level directory.
11  */
12 
13 #include "qemu/osdep.h"
14 #include "qemu/thread-context.h"
15 #include "qapi/error.h"
16 #include "qapi/qapi-builtin-visit.h"
17 #include "qapi/visitor.h"
18 #include "qemu/config-file.h"
19 #include "qapi/qapi-builtin-visit.h"
20 #include "qom/object_interfaces.h"
21 #include "qemu/module.h"
22 #include "qemu/bitmap.h"
23 
24 #ifdef CONFIG_NUMA
25 #include <numa.h>
26 #endif
27 
28 enum {
29     TC_CMD_NONE = 0,
30     TC_CMD_STOP,
31     TC_CMD_NEW,
32 };
33 
34 typedef struct ThreadContextCmdNew {
35     QemuThread *thread;
36     const char *name;
37     void *(*start_routine)(void *);
38     void *arg;
39     int mode;
40 } ThreadContextCmdNew;
41 
thread_context_run(void * opaque)42 static void *thread_context_run(void *opaque)
43 {
44     ThreadContext *tc = opaque;
45 
46     tc->thread_id = qemu_get_thread_id();
47     qemu_sem_post(&tc->sem);
48 
49     while (true) {
50         /*
51          * Threads inherit the CPU affinity of the creating thread. For this
52          * reason, we create new (especially short-lived) threads from our
53          * persistent context thread.
54          *
55          * Especially when QEMU is not allowed to set the affinity itself,
56          * management tools can simply set the affinity of the context thread
57          * after creating the context, to have new threads created via
58          * the context inherit the CPU affinity automatically.
59          */
60         switch (tc->thread_cmd) {
61         case TC_CMD_NONE:
62             break;
63         case TC_CMD_STOP:
64             tc->thread_cmd = TC_CMD_NONE;
65             qemu_sem_post(&tc->sem);
66             return NULL;
67         case TC_CMD_NEW: {
68             ThreadContextCmdNew *cmd_new = tc->thread_cmd_data;
69 
70             qemu_thread_create(cmd_new->thread, cmd_new->name,
71                                cmd_new->start_routine, cmd_new->arg,
72                                cmd_new->mode);
73             tc->thread_cmd = TC_CMD_NONE;
74             tc->thread_cmd_data = NULL;
75             qemu_sem_post(&tc->sem);
76             break;
77         }
78         default:
79             g_assert_not_reached();
80         }
81         qemu_sem_wait(&tc->sem_thread);
82     }
83 }
84 
thread_context_set_cpu_affinity(Object * obj,Visitor * v,const char * name,void * opaque,Error ** errp)85 static void thread_context_set_cpu_affinity(Object *obj, Visitor *v,
86                                             const char *name, void *opaque,
87                                             Error **errp)
88 {
89     ThreadContext *tc = THREAD_CONTEXT(obj);
90     uint16List *l, *host_cpus = NULL;
91     unsigned long *bitmap = NULL;
92     int nbits = 0, ret;
93 
94     if (tc->init_cpu_bitmap) {
95         error_setg(errp, "Mixing CPU and node affinity not supported");
96         return;
97     }
98 
99     if (!visit_type_uint16List(v, name, &host_cpus, errp)) {
100         return;
101     }
102 
103     if (!host_cpus) {
104         error_setg(errp, "CPU list is empty");
105         goto out;
106     }
107 
108     for (l = host_cpus; l; l = l->next) {
109         nbits = MAX(nbits, l->value + 1);
110     }
111     bitmap = bitmap_new(nbits);
112     for (l = host_cpus; l; l = l->next) {
113         set_bit(l->value, bitmap);
114     }
115 
116     if (tc->thread_id != -1) {
117         /*
118          * Note: we won't be adjusting the affinity of any thread that is still
119          * around, but only the affinity of the context thread.
120          */
121         ret = qemu_thread_set_affinity(&tc->thread, bitmap, nbits);
122         if (ret) {
123             error_setg(errp, "Setting CPU affinity failed: %s", strerror(ret));
124         }
125     } else {
126         tc->init_cpu_bitmap = bitmap;
127         bitmap = NULL;
128         tc->init_cpu_nbits = nbits;
129     }
130 out:
131     g_free(bitmap);
132     qapi_free_uint16List(host_cpus);
133 }
134 
thread_context_get_cpu_affinity(Object * obj,Visitor * v,const char * name,void * opaque,Error ** errp)135 static void thread_context_get_cpu_affinity(Object *obj, Visitor *v,
136                                             const char *name, void *opaque,
137                                             Error **errp)
138 {
139     unsigned long *bitmap, nbits, value;
140     ThreadContext *tc = THREAD_CONTEXT(obj);
141     uint16List *host_cpus = NULL;
142     uint16List **tail = &host_cpus;
143     int ret;
144 
145     if (tc->thread_id == -1) {
146         error_setg(errp, "Object not initialized yet");
147         return;
148     }
149 
150     ret = qemu_thread_get_affinity(&tc->thread, &bitmap, &nbits);
151     if (ret) {
152         error_setg(errp, "Getting CPU affinity failed: %s", strerror(ret));
153         return;
154     }
155 
156     value = find_first_bit(bitmap, nbits);
157     while (value < nbits) {
158         QAPI_LIST_APPEND(tail, value);
159 
160         value = find_next_bit(bitmap, nbits, value + 1);
161     }
162     g_free(bitmap);
163 
164     visit_type_uint16List(v, name, &host_cpus, errp);
165     qapi_free_uint16List(host_cpus);
166 }
167 
thread_context_set_node_affinity(Object * obj,Visitor * v,const char * name,void * opaque,Error ** errp)168 static void thread_context_set_node_affinity(Object *obj, Visitor *v,
169                                              const char *name, void *opaque,
170                                              Error **errp)
171 {
172 #ifdef CONFIG_NUMA
173     const int nbits = numa_num_possible_cpus();
174     ThreadContext *tc = THREAD_CONTEXT(obj);
175     uint16List *l, *host_nodes = NULL;
176     unsigned long *bitmap = NULL;
177     struct bitmask *tmp_cpus;
178     int ret, i;
179 
180     if (tc->init_cpu_bitmap) {
181         error_setg(errp, "Mixing CPU and node affinity not supported");
182         return;
183     }
184 
185     if (!visit_type_uint16List(v, name, &host_nodes, errp)) {
186         return;
187     }
188 
189     if (!host_nodes) {
190         error_setg(errp, "Node list is empty");
191         goto out;
192     }
193 
194     bitmap = bitmap_new(nbits);
195     tmp_cpus = numa_allocate_cpumask();
196     for (l = host_nodes; l; l = l->next) {
197         numa_bitmask_clearall(tmp_cpus);
198         ret = numa_node_to_cpus(l->value, tmp_cpus);
199         if (ret) {
200             /* We ignore any errors, such as impossible nodes. */
201             continue;
202         }
203         for (i = 0; i < nbits; i++) {
204             if (numa_bitmask_isbitset(tmp_cpus, i)) {
205                 set_bit(i, bitmap);
206             }
207         }
208     }
209     numa_free_cpumask(tmp_cpus);
210 
211     if (bitmap_empty(bitmap, nbits)) {
212         error_setg(errp, "The nodes select no CPUs");
213         goto out;
214     }
215 
216     if (tc->thread_id != -1) {
217         /*
218          * Note: we won't be adjusting the affinity of any thread that is still
219          * around for now, but only the affinity of the context thread.
220          */
221         ret = qemu_thread_set_affinity(&tc->thread, bitmap, nbits);
222         if (ret) {
223             error_setg(errp, "Setting CPU affinity failed: %s", strerror(ret));
224         }
225     } else {
226         tc->init_cpu_bitmap = bitmap;
227         bitmap = NULL;
228         tc->init_cpu_nbits = nbits;
229     }
230 out:
231     g_free(bitmap);
232     qapi_free_uint16List(host_nodes);
233 #else
234     error_setg(errp, "NUMA node affinity is not supported by this QEMU");
235 #endif
236 }
237 
thread_context_get_thread_id(Object * obj,Visitor * v,const char * name,void * opaque,Error ** errp)238 static void thread_context_get_thread_id(Object *obj, Visitor *v,
239                                          const char *name, void *opaque,
240                                          Error **errp)
241 {
242     ThreadContext *tc = THREAD_CONTEXT(obj);
243     uint64_t value = tc->thread_id;
244 
245     visit_type_uint64(v, name, &value, errp);
246 }
247 
thread_context_instance_complete(UserCreatable * uc,Error ** errp)248 static void thread_context_instance_complete(UserCreatable *uc, Error **errp)
249 {
250     ThreadContext *tc = THREAD_CONTEXT(uc);
251     char *thread_name;
252     int ret;
253 
254     thread_name = g_strdup_printf("TC %s",
255                                object_get_canonical_path_component(OBJECT(uc)));
256     qemu_thread_create(&tc->thread, thread_name, thread_context_run, tc,
257                        QEMU_THREAD_JOINABLE);
258     g_free(thread_name);
259 
260     /* Wait until initialization of the thread is done. */
261     while (tc->thread_id == -1) {
262         qemu_sem_wait(&tc->sem);
263     }
264 
265     if (tc->init_cpu_bitmap) {
266         ret = qemu_thread_set_affinity(&tc->thread, tc->init_cpu_bitmap,
267                                        tc->init_cpu_nbits);
268         if (ret) {
269             error_setg(errp, "Setting CPU affinity failed: %s", strerror(ret));
270         }
271         g_free(tc->init_cpu_bitmap);
272         tc->init_cpu_bitmap = NULL;
273     }
274 }
275 
thread_context_class_init(ObjectClass * oc,void * data)276 static void thread_context_class_init(ObjectClass *oc, void *data)
277 {
278     UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
279 
280     ucc->complete = thread_context_instance_complete;
281     object_class_property_add(oc, "thread-id", "int",
282                               thread_context_get_thread_id, NULL, NULL,
283                               NULL);
284     object_class_property_add(oc, "cpu-affinity", "int",
285                               thread_context_get_cpu_affinity,
286                               thread_context_set_cpu_affinity, NULL, NULL);
287     object_class_property_add(oc, "node-affinity", "int", NULL,
288                               thread_context_set_node_affinity, NULL, NULL);
289 }
290 
thread_context_instance_init(Object * obj)291 static void thread_context_instance_init(Object *obj)
292 {
293     ThreadContext *tc = THREAD_CONTEXT(obj);
294 
295     tc->thread_id = -1;
296     qemu_sem_init(&tc->sem, 0);
297     qemu_sem_init(&tc->sem_thread, 0);
298     qemu_mutex_init(&tc->mutex);
299 }
300 
thread_context_instance_finalize(Object * obj)301 static void thread_context_instance_finalize(Object *obj)
302 {
303     ThreadContext *tc = THREAD_CONTEXT(obj);
304 
305     if (tc->thread_id != -1) {
306         tc->thread_cmd = TC_CMD_STOP;
307         qemu_sem_post(&tc->sem_thread);
308         qemu_thread_join(&tc->thread);
309     }
310     qemu_sem_destroy(&tc->sem);
311     qemu_sem_destroy(&tc->sem_thread);
312     qemu_mutex_destroy(&tc->mutex);
313 }
314 
315 static const TypeInfo thread_context_info = {
316     .name = TYPE_THREAD_CONTEXT,
317     .parent = TYPE_OBJECT,
318     .class_init = thread_context_class_init,
319     .instance_size = sizeof(ThreadContext),
320     .instance_init = thread_context_instance_init,
321     .instance_finalize = thread_context_instance_finalize,
322     .interfaces = (InterfaceInfo[]) {
323         { TYPE_USER_CREATABLE },
324         { }
325     }
326 };
327 
thread_context_register_types(void)328 static void thread_context_register_types(void)
329 {
330     type_register_static(&thread_context_info);
331 }
type_init(thread_context_register_types)332 type_init(thread_context_register_types)
333 
334 void thread_context_create_thread(ThreadContext *tc, QemuThread *thread,
335                                   const char *name,
336                                   void *(*start_routine)(void *), void *arg,
337                                   int mode)
338 {
339     ThreadContextCmdNew data = {
340         .thread = thread,
341         .name = name,
342         .start_routine = start_routine,
343         .arg = arg,
344         .mode = mode,
345     };
346 
347     qemu_mutex_lock(&tc->mutex);
348     tc->thread_cmd = TC_CMD_NEW;
349     tc->thread_cmd_data = &data;
350     qemu_sem_post(&tc->sem_thread);
351 
352     while (tc->thread_cmd != TC_CMD_NONE) {
353         qemu_sem_wait(&tc->sem);
354     }
355     qemu_mutex_unlock(&tc->mutex);
356 }
357