qdev: Add JSON -device

- Add a JSON mode to the -device command line option
 - net/vhost-{user,vdpa}: Fix device compatibility check
 - Minor iotests fixes
 -----BEGIN PGP SIGNATURE-----
 
 iQJFBAABCAAvFiEE3D3rFZqa+V09dFb+fwmycsiPL9YFAmFpkxIRHGt3b2xmQHJl
 ZGhhdC5jb20ACgkQfwmycsiPL9awKA//YWdkh/81lTczk+l7zwRI5YS4efbOvMxc
 LoS6tUV4gh0mAEBxw/ZmSwMMnGWcZkjIGnhROSeLPDtzi0YN2gwaVYJ2RMcICTyD
 9LAIMRfR5tUu63qwXXH9VpfhkS2hJt/uq9+oQnD1okRRxBTIcRBi8vIj6X1EPii/
 w0cDWsMlq4m8R6+cVcgapmXCej7zSXaHPyyat0JiNstnREW/QQkR8rZRbJKYQc03
 6nfsznB2wdAWCT02dchhtaZK/0IN3EZ5kin8kn/luxjoAPV6UimvtJmaqZ0zl86j
 dwXGJbAbnUnXhkBludGYYKiJDtKh2w1Zfltsoac5diigaSnKik2bavmnOn8N95NE
 U8+kxXFXBxJiHGn7PgWYtkmiRqdY46/t6r0U/bQAa3QpCLRtdqpddC5dBw92iPml
 aX0q7LVIGmesbDgMEVjgdwCYGxUOefEL05THeJSmZ82cZS0YvAzdQhN0nL7L2yX/
 t9jscqLiUnXS6ierqvLMH+Y5P7o5x07mR1DRgQr2JYaOz5lBMTASX/DmehfJcLkO
 SmsgBtlzg4Y1BMCC07q2LkUvPHMA4PillAWxeW4TPWAr19geEafzhjoWJyIktZ3h
 9ND6A4W6OoOL/fjr/v0J2z6svBcXwya/W8rCpyeMndEatRKINPzbNE2iTi1zE8Ki
 fMhJHwsjus0=
 =1WQc
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/kwolf/tags/for-upstream' into staging

qdev: Add JSON -device

- Add a JSON mode to the -device command line option
- net/vhost-{user,vdpa}: Fix device compatibility check
- Minor iotests fixes

# gpg: Signature made Fri 15 Oct 2021 07:41:22 AM PDT
# gpg:                using RSA key DC3DEB159A9AF95D3D7456FE7F09B272C88F2FD6
# gpg:                issuer "kwolf@redhat.com"
# gpg: Good signature from "Kevin Wolf <kwolf@redhat.com>" [full]

* remotes/kwolf/tags/for-upstream:
  vl: Enable JSON syntax for -device
  qdev: Base object creation on QDict rather than QemuOpts
  virtio-net: Avoid QemuOpts in failover_find_primary_device()
  virtio-net: Store failover primary opts pointer locally
  qdev: Add Error parameter to hide_device() callbacks
  qemu-option: Allow deleting opts during qemu_opts_foreach()
  softmmu/qdev-monitor: add error handling in qdev_set_id
  qdev: Make DeviceState.id independent of QemuOpts
  qdev: Avoid using string visitor for properties
  iotests/051: Fix typo
  iotests/245: Fix type for iothread property
  qom: Reduce use of error_propagate()
  net/vhost-vdpa: Fix device compatibility check
  net/vhost-user: Fix device compatibility check
  net: Introduce NetClientInfo.check_peer_type()

Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
This commit is contained in:
Richard Henderson 2021-10-15 12:08:54 -07:00
commit 253e399bab
23 changed files with 282 additions and 180 deletions

View File

@ -1459,7 +1459,7 @@ static void create_platform_bus(VirtMachineState *vms)
MemoryRegion *sysmem = get_system_memory();
dev = qdev_new(TYPE_PLATFORM_BUS_DEVICE);
dev->id = TYPE_PLATFORM_BUS_DEVICE;
dev->id = g_strdup(TYPE_PLATFORM_BUS_DEVICE);
qdev_prop_set_uint32(dev, "num_irqs", PLATFORM_BUS_NUM_IRQS);
qdev_prop_set_uint32(dev, "mmio_size", vms->memmap[VIRT_PLATFORM_BUS].size);
sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);

View File

@ -431,6 +431,12 @@ static void set_netdev(Object *obj, Visitor *v, const char *name,
goto out;
}
if (peers[i]->info->check_peer_type) {
if (!peers[i]->info->check_peer_type(peers[i], obj->class, errp)) {
goto out;
}
}
ncs[i] = peers[i];
ncs[i]->queue_index = i;
}

View File

@ -28,6 +28,7 @@
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qapi/qapi-events-qdev.h"
#include "qapi/qmp/qdict.h"
#include "qapi/qmp/qerror.h"
#include "qapi/visitor.h"
#include "qemu/error-report.h"
@ -211,14 +212,17 @@ void device_listener_unregister(DeviceListener *listener)
QTAILQ_REMOVE(&device_listeners, listener, link);
}
bool qdev_should_hide_device(QemuOpts *opts)
bool qdev_should_hide_device(const QDict *opts, bool from_json, Error **errp)
{
ERRP_GUARD();
DeviceListener *listener;
QTAILQ_FOREACH(listener, &device_listeners, link) {
if (listener->hide_device) {
if (listener->hide_device(listener, opts)) {
if (listener->hide_device(listener, opts, from_json, errp)) {
return true;
} else if (*errp) {
return false;
}
}
}
@ -955,7 +959,8 @@ static void device_finalize(Object *obj)
dev->canonical_path = NULL;
}
qemu_opts_del(dev->opts);
qobject_unref(dev->opts);
g_free(dev->id);
}
static void device_class_base_init(ObjectClass *class, void *data)

View File

@ -796,48 +796,34 @@ static inline uint64_t virtio_net_supported_guest_offloads(VirtIONet *n)
typedef struct {
VirtIONet *n;
char *id;
} FailoverId;
DeviceState *dev;
} FailoverDevice;
/**
* Set the id of the failover primary device
* Set the failover primary device
*
* @opaque: FailoverId to setup
* @opts: opts for device we are handling
* @errp: returns an error if this function fails
*/
static int failover_set_primary(void *opaque, QemuOpts *opts, Error **errp)
static int failover_set_primary(DeviceState *dev, void *opaque)
{
FailoverId *fid = opaque;
const char *standby_id = qemu_opt_get(opts, "failover_pair_id");
FailoverDevice *fdev = opaque;
PCIDevice *pci_dev = (PCIDevice *)
object_dynamic_cast(OBJECT(dev), TYPE_PCI_DEVICE);
if (g_strcmp0(standby_id, fid->n->netclient_name) == 0) {
fid->id = g_strdup(opts->id);
if (!pci_dev) {
return 0;
}
if (!g_strcmp0(pci_dev->failover_pair_id, fdev->n->netclient_name)) {
fdev->dev = dev;
return 1;
}
return 0;
}
/**
* Find the primary device id for this failover virtio-net
*
* @n: VirtIONet device
* @errp: returns an error if this function fails
*/
static char *failover_find_primary_device_id(VirtIONet *n)
{
Error *err = NULL;
FailoverId fid;
fid.n = n;
if (!qemu_opts_foreach(qemu_find_opts("device"),
failover_set_primary, &fid, &err)) {
return NULL;
}
return fid.id;
}
/**
* Find the primary device for this failover virtio-net
*
@ -846,39 +832,38 @@ static char *failover_find_primary_device_id(VirtIONet *n)
*/
static DeviceState *failover_find_primary_device(VirtIONet *n)
{
char *id = failover_find_primary_device_id(n);
FailoverDevice fdev = {
.n = n,
};
if (!id) {
return NULL;
}
return qdev_find_recursive(sysbus_get_default(), id);
qbus_walk_children(sysbus_get_default(), failover_set_primary, NULL,
NULL, NULL, &fdev);
return fdev.dev;
}
static void failover_add_primary(VirtIONet *n, Error **errp)
{
Error *err = NULL;
QemuOpts *opts;
char *id;
DeviceState *dev = failover_find_primary_device(n);
if (dev) {
return;
}
id = failover_find_primary_device_id(n);
if (!id) {
if (!n->primary_opts) {
error_setg(errp, "Primary device not found");
error_append_hint(errp, "Virtio-net failover will not work. Make "
"sure primary device has parameter"
" failover_pair_id=%s\n", n->netclient_name);
return;
}
opts = qemu_opts_find(qemu_find_opts("device"), id);
g_assert(opts); /* cannot be NULL because id was found using opts list */
dev = qdev_device_add(opts, &err);
dev = qdev_device_add_from_qdict(n->primary_opts,
n->primary_opts_from_json,
&err);
if (err) {
qemu_opts_del(opts);
qobject_unref(n->primary_opts);
n->primary_opts = NULL;
} else {
object_unref(OBJECT(dev));
}
@ -3304,7 +3289,9 @@ static void virtio_net_migration_state_notifier(Notifier *notifier, void *data)
}
static bool failover_hide_primary_device(DeviceListener *listener,
QemuOpts *device_opts)
const QDict *device_opts,
bool from_json,
Error **errp)
{
VirtIONet *n = container_of(listener, VirtIONet, primary_listener);
const char *standby_id;
@ -3312,11 +3299,20 @@ static bool failover_hide_primary_device(DeviceListener *listener,
if (!device_opts) {
return false;
}
standby_id = qemu_opt_get(device_opts, "failover_pair_id");
standby_id = qdict_get_try_str(device_opts, "failover_pair_id");
if (g_strcmp0(standby_id, n->netclient_name) != 0) {
return false;
}
if (n->primary_opts) {
error_setg(errp, "Cannot attach more than one primary device to '%s'",
n->netclient_name);
return false;
}
n->primary_opts = qdict_clone_shallow(device_opts);
n->primary_opts_from_json = from_json;
/* failover_primary_hidden is set during feature negotiation */
return qatomic_read(&n->failover_primary_hidden);
}
@ -3506,8 +3502,11 @@ static void virtio_net_device_unrealize(DeviceState *dev)
g_free(n->vlans);
if (n->failover) {
qobject_unref(n->primary_opts);
device_listener_unregister(&n->primary_listener);
remove_migration_state_change_notifier(&n->migration_state);
} else {
assert(n->primary_opts == NULL);
}
max_queues = n->multiqueue ? n->max_queues : 1;

View File

@ -245,7 +245,7 @@ static void pxb_dev_realize_common(PCIDevice *dev, bool pcie, Error **errp)
} else {
bus = pci_root_bus_new(ds, "pxb-internal", NULL, NULL, 0, TYPE_PXB_BUS);
bds = qdev_new("pci-bridge");
bds->id = dev_name;
bds->id = g_strdup(dev_name);
qdev_prop_set_uint8(bds, PCI_BRIDGE_DEV_PROP_CHASSIS_NR, pxb->bus_nr);
qdev_prop_set_bit(bds, PCI_BRIDGE_DEV_PROP_SHPC, false);
}

View File

@ -1006,7 +1006,7 @@ void ppce500_init(MachineState *machine)
/* Platform Bus Device */
if (pmc->has_platform_bus) {
dev = qdev_new(TYPE_PLATFORM_BUS_DEVICE);
dev->id = TYPE_PLATFORM_BUS_DEVICE;
dev->id = g_strdup(TYPE_PLATFORM_BUS_DEVICE);
qdev_prop_set_uint32(dev, "num_irqs", pmc->platform_bus_num_irqs);
qdev_prop_set_uint32(dev, "mmio_size", pmc->platform_bus_size);
sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);

View File

@ -29,10 +29,10 @@
#include "hw/qdev-properties.h"
#include "hw/qdev-properties-system.h"
#include "migration/vmstate.h"
#include "qapi/qmp/qdict.h"
#include "qemu/error-report.h"
#include "qemu/main-loop.h"
#include "qemu/module.h"
#include "qemu/option.h"
#include "qemu/range.h"
#include "qemu/units.h"
#include "sysemu/kvm.h"
@ -941,7 +941,7 @@ static void vfio_pci_size_rom(VFIOPCIDevice *vdev)
}
if (vfio_opt_rom_in_denylist(vdev)) {
if (dev->opts && qemu_opt_get(dev->opts, "rombar")) {
if (dev->opts && qdict_haskey(dev->opts, "rombar")) {
warn_report("Device at %s is known to cause system instability"
" issues during option rom execution",
vdev->vbasedev.name);

View File

@ -276,7 +276,8 @@ static struct XenLegacyDevice *xen_be_get_xendev(const char *type, int dom,
xendev = g_malloc0(ops->size);
object_initialize(&xendev->qdev, ops->size, TYPE_XENBACKEND);
OBJECT(xendev)->free = g_free;
qdev_set_id(DEVICE(xendev), g_strdup_printf("xen-%s-%d", type, dev));
qdev_set_id(DEVICE(xendev), g_strdup_printf("xen-%s-%d", type, dev),
&error_fatal);
qdev_realize(DEVICE(xendev), xen_sysbus, &error_fatal);
object_unref(OBJECT(xendev));

View File

@ -176,11 +176,11 @@ struct DeviceState {
Object parent_obj;
/*< public >*/
const char *id;
char *id;
char *canonical_path;
bool realized;
bool pending_deleted_event;
QemuOpts *opts;
QDict *opts;
int hotplugged;
bool allow_unplug_during_migration;
BusState *parent_bus;
@ -201,8 +201,12 @@ struct DeviceListener {
* informs qdev if a device should be visible or hidden. We can
* hide a failover device depending for example on the device
* opts.
*
* On errors, it returns false and errp is set. Device creation
* should fail in this case.
*/
bool (*hide_device)(DeviceListener *listener, QemuOpts *device_opts);
bool (*hide_device)(DeviceListener *listener, const QDict *device_opts,
bool from_json, Error **errp);
QTAILQ_ENTRY(DeviceListener) link;
};
@ -831,13 +835,15 @@ void device_listener_unregister(DeviceListener *listener);
/**
* @qdev_should_hide_device:
* @opts: QemuOpts as passed on cmdline.
* @opts: options QDict
* @from_json: true if @opts entries are typed, false for all strings
* @errp: pointer to error object
*
* Check if a device should be added.
* When a device is added via qdev_device_add() this will be called,
* and return if the device should be added now or not.
*/
bool qdev_should_hide_device(QemuOpts *opts);
bool qdev_should_hide_device(const QDict *opts, bool from_json, Error **errp);
typedef enum MachineInitPhase {
/* current_machine is NULL. */

View File

@ -209,6 +209,8 @@ struct VirtIONet {
bool failover_primary_hidden;
bool failover;
DeviceListener primary_listener;
QDict *primary_opts;
bool primary_opts_from_json;
Notifier migration_state;
VirtioNetRssData rss_data;
struct NetRxPkt *rx_pkt;

View File

@ -9,6 +9,31 @@ void qmp_device_add(QDict *qdict, QObject **ret_data, Error **errp);
int qdev_device_help(QemuOpts *opts);
DeviceState *qdev_device_add(QemuOpts *opts, Error **errp);
void qdev_set_id(DeviceState *dev, const char *id);
DeviceState *qdev_device_add_from_qdict(const QDict *opts,
bool from_json, Error **errp);
/**
* qdev_set_id: parent the device and set its id if provided.
* @dev: device to handle
* @id: id to be given to the device, or NULL.
*
* Returns: the id of the device in case of success; otherwise NULL.
*
* @dev must be unrealized, unparented and must not have an id.
*
* If @id is non-NULL, this function tries to setup @dev qom path as
* "/peripheral/id". If @id is already taken, it fails. If it succeeds,
* the id field of @dev is set to @id (@dev now owns the given @id
* parameter).
*
* If @id is NULL, this function generates a unique name and setups @dev
* qom path as "/peripheral-anon/name". This name is not set as the id
* of @dev.
*
* Upon success, it returns the id/name (generated or provided). The
* returned string is owned by the corresponding child property and must
* not be freed by the caller.
*/
const char *qdev_set_id(DeviceState *dev, char *id, Error **errp);
#endif

View File

@ -62,6 +62,7 @@ typedef struct SocketReadState SocketReadState;
typedef void (SocketReadStateFinalize)(SocketReadState *rs);
typedef void (NetAnnounce)(NetClientState *);
typedef bool (SetSteeringEBPF)(NetClientState *, int);
typedef bool (NetCheckPeerType)(NetClientState *, ObjectClass *, Error **);
typedef struct NetClientInfo {
NetClientDriver type;
@ -84,6 +85,7 @@ typedef struct NetClientInfo {
SetVnetBE *set_vnet_be;
NetAnnounce *announce;
SetSteeringEBPF *set_steering_ebpf;
NetCheckPeerType *check_peer_type;
} NetClientInfo;
struct NetClientState {

View File

@ -198,6 +198,19 @@ static bool vhost_user_has_ufo(NetClientState *nc)
return true;
}
static bool vhost_user_check_peer_type(NetClientState *nc, ObjectClass *oc,
Error **errp)
{
const char *driver = object_class_get_name(oc);
if (!g_str_has_prefix(driver, "virtio-net-")) {
error_setg(errp, "vhost-user requires frontend driver virtio-net-*");
return false;
}
return true;
}
static NetClientInfo net_vhost_user_info = {
.type = NET_CLIENT_DRIVER_VHOST_USER,
.size = sizeof(NetVhostUserState),
@ -207,6 +220,7 @@ static NetClientInfo net_vhost_user_info = {
.has_ufo = vhost_user_has_ufo,
.set_vnet_be = vhost_user_set_vnet_endianness,
.set_vnet_le = vhost_user_set_vnet_endianness,
.check_peer_type = vhost_user_check_peer_type,
};
static gboolean net_vhost_user_watch(void *do_not_use, GIOCondition cond,
@ -397,27 +411,6 @@ static Chardev *net_vhost_claim_chardev(
return chr;
}
static int net_vhost_check_net(void *opaque, QemuOpts *opts, Error **errp)
{
const char *name = opaque;
const char *driver, *netdev;
driver = qemu_opt_get(opts, "driver");
netdev = qemu_opt_get(opts, "netdev");
if (!driver || !netdev) {
return 0;
}
if (strcmp(netdev, name) == 0 &&
!g_str_has_prefix(driver, "virtio-net-")) {
error_setg(errp, "vhost-user requires frontend driver virtio-net-*");
return -1;
}
return 0;
}
int net_init_vhost_user(const Netdev *netdev, const char *name,
NetClientState *peer, Error **errp)
{
@ -433,12 +426,6 @@ int net_init_vhost_user(const Netdev *netdev, const char *name,
return -1;
}
/* verify net frontend */
if (qemu_opts_foreach(qemu_find_opts("device"), net_vhost_check_net,
(char *)name, errp)) {
return -1;
}
queues = vhost_user_opts->has_queues ? vhost_user_opts->queues : 1;
if (queues < 1 || queues > MAX_QUEUE_NUM) {
error_setg(errp,

View File

@ -147,12 +147,26 @@ static bool vhost_vdpa_has_ufo(NetClientState *nc)
}
static bool vhost_vdpa_check_peer_type(NetClientState *nc, ObjectClass *oc,
Error **errp)
{
const char *driver = object_class_get_name(oc);
if (!g_str_has_prefix(driver, "virtio-net-")) {
error_setg(errp, "vhost-vdpa requires frontend driver virtio-net-*");
return false;
}
return true;
}
static NetClientInfo net_vhost_vdpa_info = {
.type = NET_CLIENT_DRIVER_VHOST_VDPA,
.size = sizeof(VhostVDPAState),
.cleanup = vhost_vdpa_cleanup,
.has_vnet_hdr = vhost_vdpa_has_vnet_hdr,
.has_ufo = vhost_vdpa_has_ufo,
.check_peer_type = vhost_vdpa_check_peer_type,
};
static int net_vhost_vdpa_init(NetClientState *peer, const char *device,
@ -179,24 +193,6 @@ static int net_vhost_vdpa_init(NetClientState *peer, const char *device,
return ret;
}
static int net_vhost_check_net(void *opaque, QemuOpts *opts, Error **errp)
{
const char *name = opaque;
const char *driver, *netdev;
driver = qemu_opt_get(opts, "driver");
netdev = qemu_opt_get(opts, "netdev");
if (!driver || !netdev) {
return 0;
}
if (strcmp(netdev, name) == 0 &&
!g_str_has_prefix(driver, "virtio-net-")) {
error_setg(errp, "vhost-vdpa requires frontend driver virtio-net-*");
return -1;
}
return 0;
}
int net_init_vhost_vdpa(const Netdev *netdev, const char *name,
NetClientState *peer, Error **errp)
{
@ -204,10 +200,5 @@ int net_init_vhost_vdpa(const Netdev *netdev, const char *name,
assert(netdev->type == NET_CLIENT_DRIVER_VHOST_VDPA);
opts = &netdev->u.vhost_vdpa;
/* verify net frontend */
if (qemu_opts_foreach(qemu_find_opts("device"), net_vhost_check_net,
(char *)name, errp)) {
return -1;
}
return net_vhost_vdpa_init(peer, TYPE_VHOST_VDPA, name, opts->vhostdev);
}

View File

@ -32,17 +32,23 @@
##
# @device_add:
#
# Add a device.
#
# @driver: the name of the new device's driver
#
# @bus: the device's parent bus (device tree path)
#
# @id: the device's ID, must be unique
#
# Additional arguments depend on the type.
#
# Add a device.
# Features:
# @json-cli: If present, the "-device" command line option supports JSON
# syntax with a structure identical to the arguments of this
# command.
#
# Notes:
#
# Additional arguments depend on the type.
#
# 1. For detailed information about this command, please refer to the
# 'docs/qdev-device-use.txt' file.
#
@ -67,7 +73,8 @@
##
{ 'command': 'device_add',
'data': {'driver': 'str', '*bus': 'str', '*id': 'str'},
'gen': false } # so we can get the additional arguments
'gen': false, # so we can get the additional arguments
'features': ['json-cli'] }
##
# @device_del:

View File

@ -1389,7 +1389,7 @@ bool object_property_get(Object *obj, const char *name, Visitor *v,
bool object_property_set(Object *obj, const char *name, Visitor *v,
Error **errp)
{
Error *err = NULL;
ERRP_GUARD();
ObjectProperty *prop = object_property_find_err(obj, name, errp);
if (prop == NULL) {
@ -1400,9 +1400,8 @@ bool object_property_set(Object *obj, const char *name, Visitor *v,
error_setg(errp, QERR_PERMISSION_DENIED);
return false;
}
prop->set(obj, v, name, prop->opaque, &err);
error_propagate(errp, err);
return !err;
prop->set(obj, v, name, prop->opaque, errp);
return !*errp;
}
bool object_property_set_str(Object *obj, const char *name,

View File

@ -46,25 +46,18 @@ static void object_set_properties_from_qdict(Object *obj, const QDict *qdict,
Visitor *v, Error **errp)
{
const QDictEntry *e;
Error *local_err = NULL;
if (!visit_start_struct(v, NULL, NULL, 0, &local_err)) {
goto out;
if (!visit_start_struct(v, NULL, NULL, 0, errp)) {
return;
}
for (e = qdict_first(qdict); e; e = qdict_next(qdict, e)) {
if (!object_property_set(obj, e->key, v, &local_err)) {
break;
if (!object_property_set(obj, e->key, v, errp)) {
goto out;
}
}
if (!local_err) {
visit_check_struct(v, &local_err);
}
visit_end_struct(v, NULL);
visit_check_struct(v, errp);
out:
if (local_err) {
error_propagate(errp, local_err);
}
visit_end_struct(v, NULL);
}
void object_set_properties_from_keyval(Object *obj, const QDict *qdict,

View File

@ -28,6 +28,8 @@
#include "qapi/qmp/dispatch.h"
#include "qapi/qmp/qdict.h"
#include "qapi/qmp/qerror.h"
#include "qapi/qmp/qstring.h"
#include "qapi/qobject-input-visitor.h"
#include "qemu/config-file.h"
#include "qemu/error-report.h"
#include "qemu/help_option.h"
@ -194,22 +196,6 @@ static void qdev_print_devinfos(bool show_no_user)
g_slist_free(list);
}
static int set_property(void *opaque, const char *name, const char *value,
Error **errp)
{
Object *obj = opaque;
if (strcmp(name, "driver") == 0)
return 0;
if (strcmp(name, "bus") == 0)
return 0;
if (!object_property_parse(obj, name, value, errp)) {
return -1;
}
return 0;
}
static const char *find_typename_by_alias(const char *alias)
{
int i;
@ -578,32 +564,49 @@ static BusState *qbus_find(const char *path, Error **errp)
return bus;
}
void qdev_set_id(DeviceState *dev, const char *id)
/* Takes ownership of @id, will be freed when deleting the device */
const char *qdev_set_id(DeviceState *dev, char *id, Error **errp)
{
if (id) {
dev->id = id;
}
ObjectProperty *prop;
if (dev->id) {
object_property_add_child(qdev_get_peripheral(), dev->id,
OBJECT(dev));
assert(!dev->id && !dev->realized);
/*
* object_property_[try_]add_child() below will assert the device
* has no parent
*/
if (id) {
prop = object_property_try_add_child(qdev_get_peripheral(), id,
OBJECT(dev), NULL);
if (prop) {
dev->id = id;
} else {
g_free(id);
error_setg(errp, "Duplicate device ID '%s'", id);
return NULL;
}
} else {
static int anon_count;
gchar *name = g_strdup_printf("device[%d]", anon_count++);
object_property_add_child(qdev_get_peripheral_anon(), name,
OBJECT(dev));
prop = object_property_add_child(qdev_get_peripheral_anon(), name,
OBJECT(dev));
g_free(name);
}
return prop->name;
}
DeviceState *qdev_device_add(QemuOpts *opts, Error **errp)
DeviceState *qdev_device_add_from_qdict(const QDict *opts,
bool from_json, Error **errp)
{
ERRP_GUARD();
DeviceClass *dc;
const char *driver, *path;
char *id;
DeviceState *dev = NULL;
BusState *bus = NULL;
driver = qemu_opt_get(opts, "driver");
driver = qdict_get_try_str(opts, "driver");
if (!driver) {
error_setg(errp, QERR_MISSING_PARAMETER, "driver");
return NULL;
@ -616,7 +619,7 @@ DeviceState *qdev_device_add(QemuOpts *opts, Error **errp)
}
/* find bus */
path = qemu_opt_get(opts, "bus");
path = qdict_get_try_str(opts, "bus");
if (path != NULL) {
bus = qbus_find(path, errp);
if (!bus) {
@ -636,16 +639,18 @@ DeviceState *qdev_device_add(QemuOpts *opts, Error **errp)
}
}
if (qemu_opt_get(opts, "failover_pair_id")) {
if (!opts->id) {
if (qdict_haskey(opts, "failover_pair_id")) {
if (!qdict_haskey(opts, "id")) {
error_setg(errp, "Device with failover_pair_id don't have id");
return NULL;
}
if (qdev_should_hide_device(opts)) {
if (qdev_should_hide_device(opts, from_json, errp)) {
if (bus && !qbus_is_hotpluggable(bus)) {
error_setg(errp, QERR_BUS_NO_HOTPLUG, bus->name);
}
return NULL;
} else if (*errp) {
return NULL;
}
}
@ -676,16 +681,28 @@ DeviceState *qdev_device_add(QemuOpts *opts, Error **errp)
}
}
qdev_set_id(dev, qemu_opts_id(opts));
/* set properties */
if (qemu_opt_foreach(opts, set_property, dev, errp)) {
/*
* set dev's parent and register its id.
* If it fails it means the id is already taken.
*/
id = g_strdup(qdict_get_try_str(opts, "id"));
if (!qdev_set_id(dev, id, errp)) {
goto err_del_dev;
}
/* set properties */
dev->opts = qdict_clone_shallow(opts);
qdict_del(dev->opts, "driver");
qdict_del(dev->opts, "bus");
qdict_del(dev->opts, "id");
object_set_properties_from_keyval(&dev->parent_obj, dev->opts, from_json,
errp);
if (*errp) {
goto err_del_dev;
}
dev->opts = opts;
if (!qdev_realize(DEVICE(dev), bus, errp)) {
dev->opts = NULL;
goto err_del_dev;
}
return dev;
@ -698,6 +715,19 @@ err_del_dev:
return NULL;
}
/* Takes ownership of @opts on success */
DeviceState *qdev_device_add(QemuOpts *opts, Error **errp)
{
QDict *qdict = qemu_opts_to_qdict(opts, NULL);
DeviceState *ret;
ret = qdev_device_add_from_qdict(qdict, false, errp);
if (ret) {
qemu_opts_del(opts);
}
qobject_unref(qdict);
return ret;
}
#define qdev_printf(fmt, ...) monitor_printf(mon, "%*s" fmt, indent, "", ## __VA_ARGS__)
static void qbus_print(Monitor *mon, BusState *bus, int indent);

View File

@ -144,6 +144,12 @@ typedef struct ObjectOption {
QTAILQ_ENTRY(ObjectOption) next;
} ObjectOption;
typedef struct DeviceOption {
QDict *opts;
Location loc;
QTAILQ_ENTRY(DeviceOption) next;
} DeviceOption;
static const char *cpu_option;
static const char *mem_path;
static const char *incoming;
@ -151,6 +157,7 @@ static const char *loadvm;
static const char *accelerators;
static QDict *machine_opts_dict;
static QTAILQ_HEAD(, ObjectOption) object_opts = QTAILQ_HEAD_INITIALIZER(object_opts);
static QTAILQ_HEAD(, DeviceOption) device_opts = QTAILQ_HEAD_INITIALIZER(device_opts);
static ram_addr_t maxram_size;
static uint64_t ram_slots;
static int display_remote;
@ -494,21 +501,39 @@ const char *qemu_get_vm_name(void)
return qemu_name;
}
static int default_driver_check(void *opaque, QemuOpts *opts, Error **errp)
static void default_driver_disable(const char *driver)
{
const char *driver = qemu_opt_get(opts, "driver");
int i;
if (!driver)
return 0;
if (!driver) {
return;
}
for (i = 0; i < ARRAY_SIZE(default_list); i++) {
if (strcmp(default_list[i].driver, driver) != 0)
continue;
*(default_list[i].flag) = 0;
}
}
static int default_driver_check(void *opaque, QemuOpts *opts, Error **errp)
{
const char *driver = qemu_opt_get(opts, "driver");
default_driver_disable(driver);
return 0;
}
static void default_driver_check_json(void)
{
DeviceOption *opt;
QTAILQ_FOREACH(opt, &device_opts, next) {
const char *driver = qdict_get_try_str(opt->opts, "driver");
default_driver_disable(driver);
}
}
static int parse_name(void *opaque, QemuOpts *opts, Error **errp)
{
const char *proc_name;
@ -1311,6 +1336,7 @@ static void qemu_disable_default_devices(void)
{
MachineClass *machine_class = MACHINE_GET_CLASS(current_machine);
default_driver_check_json();
qemu_opts_foreach(qemu_find_opts("device"),
default_driver_check, NULL, NULL);
qemu_opts_foreach(qemu_find_opts("global"),
@ -2637,6 +2663,8 @@ static void qemu_init_board(void)
static void qemu_create_cli_devices(void)
{
DeviceOption *opt;
soundhw_init();
qemu_opts_foreach(qemu_find_opts("fw_cfg"),
@ -2652,6 +2680,18 @@ static void qemu_create_cli_devices(void)
rom_set_order_override(FW_CFG_ORDER_OVERRIDE_DEVICE);
qemu_opts_foreach(qemu_find_opts("device"),
device_init_func, NULL, &error_fatal);
QTAILQ_FOREACH(opt, &device_opts, next) {
loc_push_restore(&opt->loc);
/*
* TODO Eventually we should call qmp_device_add() here to make sure it
* behaves the same, but QMP still has to accept incorrectly typed
* options until libvirt is fixed and we want to be strict on the CLI
* from the start, so call qdev_device_add_from_qdict() directly for
* now.
*/
qdev_device_add_from_qdict(opt->opts, true, &error_fatal);
loc_pop(&opt->loc);
}
rom_reset_order_override();
}
@ -3352,9 +3392,18 @@ void qemu_init(int argc, char **argv, char **envp)
add_device_config(DEV_USB, optarg);
break;
case QEMU_OPTION_device:
if (!qemu_opts_parse_noisily(qemu_find_opts("device"),
optarg, true)) {
exit(1);
if (optarg[0] == '{') {
QObject *obj = qobject_from_json(optarg, &error_fatal);
DeviceOption *opt = g_new0(DeviceOption, 1);
opt->opts = qobject_to(QDict, obj);
loc_save(&opt->loc);
assert(opt->opts != NULL);
QTAILQ_INSERT_TAIL(&device_opts, opt, next);
} else {
if (!qemu_opts_parse_noisily(qemu_find_opts("device"),
optarg, true)) {
exit(1);
}
}
break;
case QEMU_OPTION_smp:

View File

@ -199,7 +199,7 @@ case "$QEMU_DEFAULT_MACHINE" in
# virtio-blk enables the iothread only when the driver initialises the
# device, so a second virtio-blk device can't be added even with the
# same iothread. virtio-scsi allows this.
run_qemu $iothread -device virtio-blk-pci,drive=disk,iothread=iothread0,share-rw=on
run_qemu $iothread -device virtio-blk-pci,drive=disk,iothread=thread0,share-rw=on
run_qemu $iothread -device virtio-scsi,id=virtio-scsi1,iothread=thread0 -device scsi-hd,bus=virtio-scsi1.0,drive=disk,share-rw=on
;;
*)

View File

@ -183,9 +183,9 @@ Testing: -drive file=TEST_DIR/t.qcow2,if=none,node-name=disk -object iothread,id
QEMU X.Y.Z monitor - type 'help' for more information
(qemu) QEMU_PROG: -device scsi-hd,bus=virtio-scsi1.0,drive=disk,share-rw=on: Cannot change iothread of active block backend
Testing: -drive file=TEST_DIR/t.qcow2,if=none,node-name=disk -object iothread,id=thread0 -device virtio-scsi,iothread=thread0,id=virtio-scsi0 -device scsi-hd,bus=virtio-scsi0.0,drive=disk,share-rw=on -device virtio-blk-pci,drive=disk,iothread=iothread0,share-rw=on
Testing: -drive file=TEST_DIR/t.qcow2,if=none,node-name=disk -object iothread,id=thread0 -device virtio-scsi,iothread=thread0,id=virtio-scsi0 -device scsi-hd,bus=virtio-scsi0.0,drive=disk,share-rw=on -device virtio-blk-pci,drive=disk,iothread=thread0,share-rw=on
QEMU X.Y.Z monitor - type 'help' for more information
(qemu) QEMU_PROG: -device virtio-blk-pci,drive=disk,iothread=iothread0,share-rw=on: Cannot change iothread of active block backend
(qemu) QEMU_PROG: -device virtio-blk-pci,drive=disk,iothread=thread0,share-rw=on: Cannot change iothread of active block backend
Testing: -drive file=TEST_DIR/t.qcow2,if=none,node-name=disk -object iothread,id=thread0 -device virtio-scsi,iothread=thread0,id=virtio-scsi0 -device scsi-hd,bus=virtio-scsi0.0,drive=disk,share-rw=on -device virtio-scsi,id=virtio-scsi1,iothread=thread0 -device scsi-hd,bus=virtio-scsi1.0,drive=disk,share-rw=on
QEMU X.Y.Z monitor - type 'help' for more information

View File

@ -1189,10 +1189,10 @@ class TestBlockdevReopen(iotests.QMPTestCase):
self.run_test_iothreads('iothread0', 'iothread0')
def test_iothreads_switch_backing(self):
self.run_test_iothreads('iothread0', None)
self.run_test_iothreads('iothread0', '')
def test_iothreads_switch_overlay(self):
self.run_test_iothreads(None, 'iothread0')
self.run_test_iothreads('', 'iothread0')
if __name__ == '__main__':
iotests.activate_logging()

View File

@ -1126,11 +1126,11 @@ int qemu_opts_foreach(QemuOptsList *list, qemu_opts_loopfunc func,
void *opaque, Error **errp)
{
Location loc;
QemuOpts *opts;
QemuOpts *opts, *next;
int rc = 0;
loc_push_none(&loc);
QTAILQ_FOREACH(opts, &list->head, next) {
QTAILQ_FOREACH_SAFE(opts, &list->head, next, next) {
loc_restore(&opts->loc);
rc = func(opaque, opts, errp);
if (rc) {