]>
Commit | Line | Data |
---|---|---|
e2de2c49 DH |
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 | ||
10218ae6 DH |
24 | #ifdef CONFIG_NUMA |
25 | #include <numa.h> | |
26 | #endif | |
27 | ||
e2de2c49 DH |
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 | ||
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 | ||
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; | |
e2de2c49 | 93 | |
10218ae6 DH |
94 | if (tc->init_cpu_bitmap) { |
95 | error_setg(errp, "Mixing CPU and node affinity not supported"); | |
96 | return; | |
97 | } | |
98 | ||
d1c81c34 | 99 | if (!visit_type_uint16List(v, name, &host_cpus, errp)) { |
e2de2c49 DH |
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 | ||
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 | ||
10218ae6 DH |
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; | |
10218ae6 DH |
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 | ||
d1c81c34 | 185 | if (!visit_type_uint16List(v, name, &host_nodes, errp)) { |
10218ae6 DH |
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 | ||
e2de2c49 DH |
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 | ||
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 | ||
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); | |
10218ae6 DH |
287 | object_class_property_add(oc, "node-affinity", "int", NULL, |
288 | thread_context_set_node_affinity, NULL, NULL); | |
e2de2c49 DH |
289 | } |
290 | ||
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 | ||
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 | ||
328 | static void thread_context_register_types(void) | |
329 | { | |
330 | type_register_static(&thread_context_info); | |
331 | } | |
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 | } |