]>
Commit | Line | Data |
---|---|---|
a404b612 | 1 | /* |
8d76bfe8 | 2 | * qdev property parsing |
a404b612 EH |
3 | * (parts specific for qemu-system-*) |
4 | * | |
5 | * This file is based on code from hw/qdev-properties.c from | |
6 | * commit 074a86fccd185616469dfcdc0e157f438aebba18, | |
7 | * Copyright (c) Gerd Hoffmann <kraxel@redhat.com> and other contributors. | |
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 | ||
18c86e2b | 13 | #include "qemu/osdep.h" |
88e47b9a | 14 | #include "audio/audio.h" |
501a7ce7 | 15 | #include "net/net.h" |
a27bd6c7 | 16 | #include "hw/qdev-properties.h" |
da34e65c | 17 | #include "qapi/error.h" |
501a7ce7 | 18 | #include "qapi/qmp/qerror.h" |
4be74634 | 19 | #include "sysemu/block-backend.h" |
501a7ce7 | 20 | #include "sysemu/blockdev.h" |
0d09e41a | 21 | #include "hw/block/block.h" |
a404b612 | 22 | #include "net/hub.h" |
501a7ce7 | 23 | #include "qapi/visitor.h" |
4d43a603 | 24 | #include "chardev/char-fe.h" |
6e4a876b | 25 | #include "sysemu/iothread.h" |
493b7830 | 26 | #include "sysemu/tpm_backend.h" |
a404b612 | 27 | |
1bc13336 MA |
28 | static bool check_prop_still_unset(DeviceState *dev, const char *name, |
29 | const void *old_val, const char *new_val, | |
30 | Error **errp) | |
31 | { | |
32 | const GlobalProperty *prop = qdev_find_global_prop(dev, name); | |
33 | ||
34 | if (!old_val) { | |
35 | return true; | |
36 | } | |
37 | ||
38 | if (prop) { | |
39 | error_setg(errp, "-global %s.%s=... conflicts with %s=%s", | |
40 | prop->driver, prop->property, name, new_val); | |
41 | } else { | |
42 | /* Error message is vague, but a better one would be hard */ | |
43 | error_setg(errp, "%s=%s conflicts, and override is not implemented", | |
44 | name, new_val); | |
45 | } | |
46 | return false; | |
47 | } | |
48 | ||
49 | ||
466c2983 MA |
50 | /* --- drive --- */ |
51 | ||
52 | static void get_drive(Object *obj, Visitor *v, const char *name, void *opaque, | |
53 | Error **errp) | |
a404b612 EH |
54 | { |
55 | DeviceState *dev = DEVICE(obj); | |
466c2983 | 56 | Property *prop = opaque; |
a404b612 | 57 | void **ptr = qdev_get_prop_ptr(dev, prop); |
466c2983 | 58 | const char *value; |
a404b612 EH |
59 | char *p; |
60 | ||
466c2983 MA |
61 | if (*ptr) { |
62 | value = blk_name(*ptr); | |
63 | if (!*value) { | |
64 | BlockDriverState *bs = blk_bs(*ptr); | |
65 | if (bs) { | |
66 | value = bdrv_get_node_name(bs); | |
67 | } | |
68 | } | |
69 | } else { | |
70 | value = ""; | |
71 | } | |
72 | ||
73 | p = g_strdup(value); | |
51e72bc1 | 74 | visit_type_str(v, name, &p, errp); |
7d1de464 | 75 | g_free(p); |
a404b612 EH |
76 | } |
77 | ||
466c2983 MA |
78 | static void set_drive_helper(Object *obj, Visitor *v, const char *name, |
79 | void *opaque, bool iothread, Error **errp) | |
a404b612 EH |
80 | { |
81 | DeviceState *dev = DEVICE(obj); | |
466c2983 | 82 | Property *prop = opaque; |
a404b612 EH |
83 | void **ptr = qdev_get_prop_ptr(dev, prop); |
84 | char *str; | |
466c2983 MA |
85 | BlockBackend *blk; |
86 | bool blk_created = false; | |
87 | int ret; | |
a404b612 | 88 | |
7983c8a3 | 89 | if (dev->realized) { |
b000dfbd | 90 | qdev_prop_set_after_realize(dev, name, errp); |
a404b612 EH |
91 | return; |
92 | } | |
93 | ||
668f62ec | 94 | if (!visit_type_str(v, name, &str, errp)) { |
a404b612 EH |
95 | return; |
96 | } | |
466c2983 | 97 | |
84b0475c MA |
98 | /* |
99 | * TODO Should this really be an error? If no, the old value | |
100 | * needs to be released before we store the new one. | |
101 | */ | |
102 | if (!check_prop_still_unset(dev, name, *ptr, str, errp)) { | |
103 | return; | |
104 | } | |
105 | ||
a404b612 EH |
106 | if (!*str) { |
107 | g_free(str); | |
108 | *ptr = NULL; | |
109 | return; | |
110 | } | |
a404b612 | 111 | |
4be74634 | 112 | blk = blk_by_name(str); |
8daea510 KW |
113 | if (!blk) { |
114 | BlockDriverState *bs = bdrv_lookup_bs(NULL, str, NULL); | |
115 | if (bs) { | |
307a5f60 KW |
116 | /* |
117 | * If the device supports iothreads, it will make sure to move the | |
118 | * block node to the right AioContext if necessary (or fail if this | |
119 | * isn't possible because of other users). Devices that are not | |
120 | * aware of iothreads require their BlockBackends to be in the main | |
121 | * AioContext. | |
122 | */ | |
123 | AioContext *ctx = iothread ? bdrv_get_aio_context(bs) : | |
124 | qemu_get_aio_context(); | |
125 | blk = blk_new(ctx, 0, BLK_PERM_ALL); | |
8daea510 | 126 | blk_created = true; |
d7086422 KW |
127 | |
128 | ret = blk_insert_bs(blk, bs, errp); | |
129 | if (ret < 0) { | |
130 | goto fail; | |
131 | } | |
8daea510 KW |
132 | } |
133 | } | |
4be74634 | 134 | if (!blk) { |
f1fb9f0d | 135 | error_setg(errp, "Property '%s.%s' can't find value '%s'", |
466c2983 | 136 | object_get_typename(OBJECT(dev)), prop->name, str); |
8daea510 | 137 | goto fail; |
a404b612 | 138 | } |
4be74634 | 139 | if (blk_attach_dev(blk, dev) < 0) { |
62f7dbde PM |
140 | DriveInfo *dinfo = blk_legacy_dinfo(blk); |
141 | ||
a9d52a75 | 142 | if (dinfo && dinfo->type != IF_NONE) { |
62f7dbde PM |
143 | error_setg(errp, "Drive '%s' is already in use because " |
144 | "it has been automatically connected to another " | |
145 | "device (did you need 'if=none' in the drive options?)", | |
146 | str); | |
147 | } else { | |
148 | error_setg(errp, "Drive '%s' is already in use by another device", | |
149 | str); | |
150 | } | |
8daea510 | 151 | goto fail; |
a404b612 | 152 | } |
8daea510 | 153 | |
4be74634 | 154 | *ptr = blk; |
8daea510 KW |
155 | |
156 | fail: | |
157 | if (blk_created) { | |
158 | /* If we need to keep a reference, blk_attach_dev() took it */ | |
159 | blk_unref(blk); | |
160 | } | |
466c2983 MA |
161 | |
162 | g_free(str); | |
a404b612 EH |
163 | } |
164 | ||
466c2983 MA |
165 | static void set_drive(Object *obj, Visitor *v, const char *name, void *opaque, |
166 | Error **errp) | |
307a5f60 | 167 | { |
466c2983 | 168 | set_drive_helper(obj, v, name, opaque, false, errp); |
307a5f60 KW |
169 | } |
170 | ||
466c2983 MA |
171 | static void set_drive_iothread(Object *obj, Visitor *v, const char *name, |
172 | void *opaque, Error **errp) | |
307a5f60 | 173 | { |
466c2983 | 174 | set_drive_helper(obj, v, name, opaque, true, errp); |
307a5f60 KW |
175 | } |
176 | ||
a404b612 EH |
177 | static void release_drive(Object *obj, const char *name, void *opaque) |
178 | { | |
179 | DeviceState *dev = DEVICE(obj); | |
180 | Property *prop = opaque; | |
4be74634 | 181 | BlockBackend **ptr = qdev_get_prop_ptr(dev, prop); |
a404b612 EH |
182 | |
183 | if (*ptr) { | |
9588c589 DL |
184 | AioContext *ctx = blk_get_aio_context(*ptr); |
185 | ||
186 | aio_context_acquire(ctx); | |
a404b612 | 187 | blockdev_auto_del(*ptr); |
8daea510 | 188 | blk_detach_dev(*ptr, dev); |
9588c589 | 189 | aio_context_release(ctx); |
a404b612 EH |
190 | } |
191 | } | |
192 | ||
1b6b7d10 | 193 | const PropertyInfo qdev_prop_drive = { |
85ca1202 | 194 | .name = "str", |
8daea510 | 195 | .description = "Node name or ID of a block device to use as a backend", |
a404b612 EH |
196 | .get = get_drive, |
197 | .set = set_drive, | |
198 | .release = release_drive, | |
199 | }; | |
200 | ||
307a5f60 KW |
201 | const PropertyInfo qdev_prop_drive_iothread = { |
202 | .name = "str", | |
203 | .description = "Node name or ID of a block device to use as a backend", | |
204 | .get = get_drive, | |
205 | .set = set_drive_iothread, | |
206 | .release = release_drive, | |
207 | }; | |
208 | ||
a404b612 EH |
209 | /* --- character device --- */ |
210 | ||
becdfa00 MAL |
211 | static void get_chr(Object *obj, Visitor *v, const char *name, void *opaque, |
212 | Error **errp) | |
a404b612 | 213 | { |
becdfa00 MAL |
214 | DeviceState *dev = DEVICE(obj); |
215 | CharBackend *be = qdev_get_prop_ptr(dev, opaque); | |
216 | char *p; | |
217 | ||
218 | p = g_strdup(be->chr && be->chr->label ? be->chr->label : ""); | |
219 | visit_type_str(v, name, &p, errp); | |
220 | g_free(p); | |
a404b612 EH |
221 | } |
222 | ||
becdfa00 MAL |
223 | static void set_chr(Object *obj, Visitor *v, const char *name, void *opaque, |
224 | Error **errp) | |
a404b612 EH |
225 | { |
226 | DeviceState *dev = DEVICE(obj); | |
227 | Property *prop = opaque; | |
becdfa00 | 228 | CharBackend *be = qdev_get_prop_ptr(dev, prop); |
0ec7b3e7 | 229 | Chardev *s; |
becdfa00 | 230 | char *str; |
a404b612 | 231 | |
becdfa00 MAL |
232 | if (dev->realized) { |
233 | qdev_prop_set_after_realize(dev, name, errp); | |
234 | return; | |
a404b612 | 235 | } |
a404b612 | 236 | |
668f62ec | 237 | if (!visit_type_str(v, name, &str, errp)) { |
becdfa00 MAL |
238 | return; |
239 | } | |
a404b612 | 240 | |
9572a787 MA |
241 | /* |
242 | * TODO Should this really be an error? If no, the old value | |
243 | * needs to be released before we store the new one. | |
244 | */ | |
245 | if (!check_prop_still_unset(dev, name, be->chr, str, errp)) { | |
246 | return; | |
247 | } | |
248 | ||
becdfa00 MAL |
249 | if (!*str) { |
250 | g_free(str); | |
251 | be->chr = NULL; | |
252 | return; | |
253 | } | |
a404b612 | 254 | |
becdfa00 | 255 | s = qemu_chr_find(str); |
becdfa00 MAL |
256 | if (s == NULL) { |
257 | error_setg(errp, "Property '%s.%s' can't find value '%s'", | |
258 | object_get_typename(obj), prop->name, str); | |
2209401f | 259 | } else if (!qemu_chr_fe_init(be, s, errp)) { |
c39860e6 MAL |
260 | error_prepend(errp, "Property '%s.%s' can't take value '%s': ", |
261 | object_get_typename(obj), prop->name, str); | |
becdfa00 | 262 | } |
2209401f | 263 | g_free(str); |
a404b612 EH |
264 | } |
265 | ||
becdfa00 | 266 | static void release_chr(Object *obj, const char *name, void *opaque) |
a404b612 | 267 | { |
becdfa00 MAL |
268 | DeviceState *dev = DEVICE(obj); |
269 | Property *prop = opaque; | |
270 | CharBackend *be = qdev_get_prop_ptr(dev, prop); | |
271 | ||
1ce2610c | 272 | qemu_chr_fe_deinit(be, false); |
a404b612 EH |
273 | } |
274 | ||
1b6b7d10 | 275 | const PropertyInfo qdev_prop_chr = { |
85ca1202 | 276 | .name = "str", |
51b2e8c3 | 277 | .description = "ID of a chardev to use as a backend", |
a404b612 EH |
278 | .get = get_chr, |
279 | .set = set_chr, | |
280 | .release = release_chr, | |
281 | }; | |
282 | ||
283 | /* --- netdev device --- */ | |
d7bce999 EB |
284 | static void get_netdev(Object *obj, Visitor *v, const char *name, |
285 | void *opaque, Error **errp) | |
23120b13 JW |
286 | { |
287 | DeviceState *dev = DEVICE(obj); | |
288 | Property *prop = opaque; | |
289 | NICPeers *peers_ptr = qdev_get_prop_ptr(dev, prop); | |
290 | char *p = g_strdup(peers_ptr->ncs[0] ? peers_ptr->ncs[0]->name : ""); | |
291 | ||
51e72bc1 | 292 | visit_type_str(v, name, &p, errp); |
23120b13 JW |
293 | g_free(p); |
294 | } | |
a404b612 | 295 | |
d7bce999 EB |
296 | static void set_netdev(Object *obj, Visitor *v, const char *name, |
297 | void *opaque, Error **errp) | |
a404b612 | 298 | { |
23120b13 JW |
299 | DeviceState *dev = DEVICE(obj); |
300 | Property *prop = opaque; | |
301 | NICPeers *peers_ptr = qdev_get_prop_ptr(dev, prop); | |
1ceef9f2 JW |
302 | NetClientState **ncs = peers_ptr->ncs; |
303 | NetClientState *peers[MAX_QUEUE_NUM]; | |
23120b13 JW |
304 | int queues, err = 0, i = 0; |
305 | char *str; | |
306 | ||
307 | if (dev->realized) { | |
308 | qdev_prop_set_after_realize(dev, name, errp); | |
309 | return; | |
310 | } | |
311 | ||
668f62ec | 312 | if (!visit_type_str(v, name, &str, errp)) { |
23120b13 JW |
313 | return; |
314 | } | |
a404b612 | 315 | |
1ceef9f2 | 316 | queues = qemu_find_net_clients_except(str, peers, |
f394b2e2 | 317 | NET_CLIENT_DRIVER_NIC, |
1ceef9f2 JW |
318 | MAX_QUEUE_NUM); |
319 | if (queues == 0) { | |
23120b13 JW |
320 | err = -ENOENT; |
321 | goto out; | |
a404b612 | 322 | } |
1ceef9f2 JW |
323 | |
324 | if (queues > MAX_QUEUE_NUM) { | |
23120b13 JW |
325 | error_setg(errp, "queues of backend '%s'(%d) exceeds QEMU limitation(%d)", |
326 | str, queues, MAX_QUEUE_NUM); | |
327 | goto out; | |
1ceef9f2 JW |
328 | } |
329 | ||
330 | for (i = 0; i < queues; i++) { | |
1ceef9f2 | 331 | if (peers[i]->peer) { |
23120b13 JW |
332 | err = -EEXIST; |
333 | goto out; | |
1ceef9f2 JW |
334 | } |
335 | ||
1bc13336 MA |
336 | /* |
337 | * TODO Should this really be an error? If no, the old value | |
338 | * needs to be released before we store the new one. | |
339 | */ | |
340 | if (!check_prop_still_unset(dev, name, ncs[i], str, errp)) { | |
23120b13 | 341 | goto out; |
30c367ed VY |
342 | } |
343 | ||
1ceef9f2 JW |
344 | ncs[i] = peers[i]; |
345 | ncs[i]->queue_index = i; | |
a404b612 | 346 | } |
1ceef9f2 | 347 | |
575a1c0e | 348 | peers_ptr->queues = queues; |
1ceef9f2 | 349 | |
23120b13 JW |
350 | out: |
351 | error_set_from_qdev_prop_error(errp, err, dev, prop, str); | |
352 | g_free(str); | |
a404b612 EH |
353 | } |
354 | ||
1b6b7d10 | 355 | const PropertyInfo qdev_prop_netdev = { |
85ca1202 | 356 | .name = "str", |
51b2e8c3 | 357 | .description = "ID of a netdev to use as a backend", |
a404b612 EH |
358 | .get = get_netdev, |
359 | .set = set_netdev, | |
360 | }; | |
361 | ||
a404b612 | 362 | |
88e47b9a KZ |
363 | /* --- audiodev --- */ |
364 | static void get_audiodev(Object *obj, Visitor *v, const char* name, | |
365 | void *opaque, Error **errp) | |
366 | { | |
367 | DeviceState *dev = DEVICE(obj); | |
368 | Property *prop = opaque; | |
369 | QEMUSoundCard *card = qdev_get_prop_ptr(dev, prop); | |
370 | char *p = g_strdup(audio_get_id(card)); | |
371 | ||
372 | visit_type_str(v, name, &p, errp); | |
373 | g_free(p); | |
374 | } | |
375 | ||
376 | static void set_audiodev(Object *obj, Visitor *v, const char* name, | |
377 | void *opaque, Error **errp) | |
378 | { | |
379 | DeviceState *dev = DEVICE(obj); | |
380 | Property *prop = opaque; | |
381 | QEMUSoundCard *card = qdev_get_prop_ptr(dev, prop); | |
382 | AudioState *state; | |
88e47b9a KZ |
383 | int err = 0; |
384 | char *str; | |
385 | ||
386 | if (dev->realized) { | |
387 | qdev_prop_set_after_realize(dev, name, errp); | |
388 | return; | |
389 | } | |
390 | ||
668f62ec | 391 | if (!visit_type_str(v, name, &str, errp)) { |
88e47b9a KZ |
392 | return; |
393 | } | |
394 | ||
395 | state = audio_state_by_name(str); | |
396 | ||
397 | if (!state) { | |
398 | err = -ENOENT; | |
399 | goto out; | |
400 | } | |
401 | card->state = state; | |
402 | ||
403 | out: | |
404 | error_set_from_qdev_prop_error(errp, err, dev, prop, str); | |
405 | g_free(str); | |
406 | } | |
407 | ||
408 | const PropertyInfo qdev_prop_audiodev = { | |
409 | .name = "str", | |
410 | .description = "ID of an audiodev to use as a backend", | |
411 | /* release done on shutdown */ | |
412 | .get = get_audiodev, | |
413 | .set = set_audiodev, | |
414 | }; | |
415 | ||
73ac1aac | 416 | bool qdev_prop_set_drive_err(DeviceState *dev, const char *name, |
934df912 | 417 | BlockBackend *value, Error **errp) |
a404b612 | 418 | { |
8daea510 KW |
419 | const char *ref = ""; |
420 | ||
421 | if (value) { | |
422 | ref = blk_name(value); | |
423 | if (!*ref) { | |
be9721f4 | 424 | const BlockDriverState *bs = blk_bs(value); |
8daea510 KW |
425 | if (bs) { |
426 | ref = bdrv_get_node_name(bs); | |
427 | } | |
428 | } | |
429 | } | |
430 | ||
73ac1aac | 431 | return object_property_set_str(OBJECT(dev), name, ref, errp); |
a404b612 EH |
432 | } |
433 | ||
934df912 MA |
434 | void qdev_prop_set_drive(DeviceState *dev, const char *name, |
435 | BlockBackend *value) | |
436 | { | |
437 | qdev_prop_set_drive_err(dev, name, value, &error_abort); | |
438 | } | |
439 | ||
a404b612 | 440 | void qdev_prop_set_chr(DeviceState *dev, const char *name, |
0ec7b3e7 | 441 | Chardev *value) |
a404b612 | 442 | { |
a404b612 | 443 | assert(!value || value->label); |
5325cc34 MA |
444 | object_property_set_str(OBJECT(dev), name, value ? value->label : "", |
445 | &error_abort); | |
a404b612 EH |
446 | } |
447 | ||
448 | void qdev_prop_set_netdev(DeviceState *dev, const char *name, | |
449 | NetClientState *value) | |
450 | { | |
a404b612 | 451 | assert(!value || value->name); |
5325cc34 MA |
452 | object_property_set_str(OBJECT(dev), name, value ? value->name : "", |
453 | &error_abort); | |
a404b612 EH |
454 | } |
455 | ||
456 | void qdev_set_nic_properties(DeviceState *dev, NICInfo *nd) | |
457 | { | |
458 | qdev_prop_set_macaddr(dev, "mac", nd->macaddr.a); | |
459 | if (nd->netdev) { | |
460 | qdev_prop_set_netdev(dev, "netdev", nd->netdev); | |
461 | } | |
462 | if (nd->nvectors != DEV_NVECTORS_UNSPECIFIED && | |
463 | object_property_find(OBJECT(dev), "vectors", NULL)) { | |
464 | qdev_prop_set_uint32(dev, "vectors", nd->nvectors); | |
465 | } | |
466 | nd->instantiated = 1; | |
467 | } |