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(); MemoryRegion *sysmem = get_system_memory();
dev = qdev_new(TYPE_PLATFORM_BUS_DEVICE); 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, "num_irqs", PLATFORM_BUS_NUM_IRQS);
qdev_prop_set_uint32(dev, "mmio_size", vms->memmap[VIRT_PLATFORM_BUS].size); qdev_prop_set_uint32(dev, "mmio_size", vms->memmap[VIRT_PLATFORM_BUS].size);
sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); 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; 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] = peers[i];
ncs[i]->queue_index = i; ncs[i]->queue_index = i;
} }

View File

@ -28,6 +28,7 @@
#include "qemu/osdep.h" #include "qemu/osdep.h"
#include "qapi/error.h" #include "qapi/error.h"
#include "qapi/qapi-events-qdev.h" #include "qapi/qapi-events-qdev.h"
#include "qapi/qmp/qdict.h"
#include "qapi/qmp/qerror.h" #include "qapi/qmp/qerror.h"
#include "qapi/visitor.h" #include "qapi/visitor.h"
#include "qemu/error-report.h" #include "qemu/error-report.h"
@ -211,14 +212,17 @@ void device_listener_unregister(DeviceListener *listener)
QTAILQ_REMOVE(&device_listeners, listener, link); 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; DeviceListener *listener;
QTAILQ_FOREACH(listener, &device_listeners, link) { QTAILQ_FOREACH(listener, &device_listeners, link) {
if (listener->hide_device) { if (listener->hide_device) {
if (listener->hide_device(listener, opts)) { if (listener->hide_device(listener, opts, from_json, errp)) {
return true; return true;
} else if (*errp) {
return false;
} }
} }
} }
@ -955,7 +959,8 @@ static void device_finalize(Object *obj)
dev->canonical_path = NULL; 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) 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 { typedef struct {
VirtIONet *n; VirtIONet *n;
char *id; DeviceState *dev;
} FailoverId; } FailoverDevice;
/** /**
* Set the id of the failover primary device * Set the failover primary device
* *
* @opaque: FailoverId to setup * @opaque: FailoverId to setup
* @opts: opts for device we are handling * @opts: opts for device we are handling
* @errp: returns an error if this function fails * @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; FailoverDevice *fdev = opaque;
const char *standby_id = qemu_opt_get(opts, "failover_pair_id"); PCIDevice *pci_dev = (PCIDevice *)
object_dynamic_cast(OBJECT(dev), TYPE_PCI_DEVICE);
if (g_strcmp0(standby_id, fid->n->netclient_name) == 0) { if (!pci_dev) {
fid->id = g_strdup(opts->id); return 0;
}
if (!g_strcmp0(pci_dev->failover_pair_id, fdev->n->netclient_name)) {
fdev->dev = dev;
return 1; return 1;
} }
return 0; 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 * 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) static DeviceState *failover_find_primary_device(VirtIONet *n)
{ {
char *id = failover_find_primary_device_id(n); FailoverDevice fdev = {
.n = n,
};
if (!id) { qbus_walk_children(sysbus_get_default(), failover_set_primary, NULL,
return NULL; NULL, NULL, &fdev);
} return fdev.dev;
return qdev_find_recursive(sysbus_get_default(), id);
} }
static void failover_add_primary(VirtIONet *n, Error **errp) static void failover_add_primary(VirtIONet *n, Error **errp)
{ {
Error *err = NULL; Error *err = NULL;
QemuOpts *opts;
char *id;
DeviceState *dev = failover_find_primary_device(n); DeviceState *dev = failover_find_primary_device(n);
if (dev) { if (dev) {
return; return;
} }
id = failover_find_primary_device_id(n); if (!n->primary_opts) {
if (!id) {
error_setg(errp, "Primary device not found"); error_setg(errp, "Primary device not found");
error_append_hint(errp, "Virtio-net failover will not work. Make " error_append_hint(errp, "Virtio-net failover will not work. Make "
"sure primary device has parameter" "sure primary device has parameter"
" failover_pair_id=%s\n", n->netclient_name); " failover_pair_id=%s\n", n->netclient_name);
return; 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_from_qdict(n->primary_opts,
dev = qdev_device_add(opts, &err); n->primary_opts_from_json,
&err);
if (err) { if (err) {
qemu_opts_del(opts); qobject_unref(n->primary_opts);
n->primary_opts = NULL;
} else { } else {
object_unref(OBJECT(dev)); 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, 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); VirtIONet *n = container_of(listener, VirtIONet, primary_listener);
const char *standby_id; const char *standby_id;
@ -3312,11 +3299,20 @@ static bool failover_hide_primary_device(DeviceListener *listener,
if (!device_opts) { if (!device_opts) {
return false; 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) { if (g_strcmp0(standby_id, n->netclient_name) != 0) {
return false; 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 */ /* failover_primary_hidden is set during feature negotiation */
return qatomic_read(&n->failover_primary_hidden); return qatomic_read(&n->failover_primary_hidden);
} }
@ -3506,8 +3502,11 @@ static void virtio_net_device_unrealize(DeviceState *dev)
g_free(n->vlans); g_free(n->vlans);
if (n->failover) { if (n->failover) {
qobject_unref(n->primary_opts);
device_listener_unregister(&n->primary_listener); device_listener_unregister(&n->primary_listener);
remove_migration_state_change_notifier(&n->migration_state); remove_migration_state_change_notifier(&n->migration_state);
} else {
assert(n->primary_opts == NULL);
} }
max_queues = n->multiqueue ? n->max_queues : 1; 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 { } else {
bus = pci_root_bus_new(ds, "pxb-internal", NULL, NULL, 0, TYPE_PXB_BUS); bus = pci_root_bus_new(ds, "pxb-internal", NULL, NULL, 0, TYPE_PXB_BUS);
bds = qdev_new("pci-bridge"); 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_uint8(bds, PCI_BRIDGE_DEV_PROP_CHASSIS_NR, pxb->bus_nr);
qdev_prop_set_bit(bds, PCI_BRIDGE_DEV_PROP_SHPC, false); 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 */ /* Platform Bus Device */
if (pmc->has_platform_bus) { if (pmc->has_platform_bus) {
dev = qdev_new(TYPE_PLATFORM_BUS_DEVICE); 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, "num_irqs", pmc->platform_bus_num_irqs);
qdev_prop_set_uint32(dev, "mmio_size", pmc->platform_bus_size); qdev_prop_set_uint32(dev, "mmio_size", pmc->platform_bus_size);
sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); 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.h"
#include "hw/qdev-properties-system.h" #include "hw/qdev-properties-system.h"
#include "migration/vmstate.h" #include "migration/vmstate.h"
#include "qapi/qmp/qdict.h"
#include "qemu/error-report.h" #include "qemu/error-report.h"
#include "qemu/main-loop.h" #include "qemu/main-loop.h"
#include "qemu/module.h" #include "qemu/module.h"
#include "qemu/option.h"
#include "qemu/range.h" #include "qemu/range.h"
#include "qemu/units.h" #include "qemu/units.h"
#include "sysemu/kvm.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 (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" warn_report("Device at %s is known to cause system instability"
" issues during option rom execution", " issues during option rom execution",
vdev->vbasedev.name); 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); xendev = g_malloc0(ops->size);
object_initialize(&xendev->qdev, ops->size, TYPE_XENBACKEND); object_initialize(&xendev->qdev, ops->size, TYPE_XENBACKEND);
OBJECT(xendev)->free = g_free; 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); qdev_realize(DEVICE(xendev), xen_sysbus, &error_fatal);
object_unref(OBJECT(xendev)); object_unref(OBJECT(xendev));

View File

@ -176,11 +176,11 @@ struct DeviceState {
Object parent_obj; Object parent_obj;
/*< public >*/ /*< public >*/
const char *id; char *id;
char *canonical_path; char *canonical_path;
bool realized; bool realized;
bool pending_deleted_event; bool pending_deleted_event;
QemuOpts *opts; QDict *opts;
int hotplugged; int hotplugged;
bool allow_unplug_during_migration; bool allow_unplug_during_migration;
BusState *parent_bus; BusState *parent_bus;
@ -201,8 +201,12 @@ struct DeviceListener {
* informs qdev if a device should be visible or hidden. We can * informs qdev if a device should be visible or hidden. We can
* hide a failover device depending for example on the device * hide a failover device depending for example on the device
* opts. * 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; QTAILQ_ENTRY(DeviceListener) link;
}; };
@ -831,13 +835,15 @@ void device_listener_unregister(DeviceListener *listener);
/** /**
* @qdev_should_hide_device: * @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. * Check if a device should be added.
* When a device is added via qdev_device_add() this will be called, * When a device is added via qdev_device_add() this will be called,
* and return if the device should be added now or not. * 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 { typedef enum MachineInitPhase {
/* current_machine is NULL. */ /* current_machine is NULL. */

View File

@ -209,6 +209,8 @@ struct VirtIONet {
bool failover_primary_hidden; bool failover_primary_hidden;
bool failover; bool failover;
DeviceListener primary_listener; DeviceListener primary_listener;
QDict *primary_opts;
bool primary_opts_from_json;
Notifier migration_state; Notifier migration_state;
VirtioNetRssData rss_data; VirtioNetRssData rss_data;
struct NetRxPkt *rx_pkt; 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); int qdev_device_help(QemuOpts *opts);
DeviceState *qdev_device_add(QemuOpts *opts, Error **errp); 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 #endif

View File

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

View File

@ -198,6 +198,19 @@ static bool vhost_user_has_ufo(NetClientState *nc)
return true; 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 = { static NetClientInfo net_vhost_user_info = {
.type = NET_CLIENT_DRIVER_VHOST_USER, .type = NET_CLIENT_DRIVER_VHOST_USER,
.size = sizeof(NetVhostUserState), .size = sizeof(NetVhostUserState),
@ -207,6 +220,7 @@ static NetClientInfo net_vhost_user_info = {
.has_ufo = vhost_user_has_ufo, .has_ufo = vhost_user_has_ufo,
.set_vnet_be = vhost_user_set_vnet_endianness, .set_vnet_be = vhost_user_set_vnet_endianness,
.set_vnet_le = 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, static gboolean net_vhost_user_watch(void *do_not_use, GIOCondition cond,
@ -397,27 +411,6 @@ static Chardev *net_vhost_claim_chardev(
return chr; 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, int net_init_vhost_user(const Netdev *netdev, const char *name,
NetClientState *peer, Error **errp) NetClientState *peer, Error **errp)
{ {
@ -433,12 +426,6 @@ int net_init_vhost_user(const Netdev *netdev, const char *name,
return -1; 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; queues = vhost_user_opts->has_queues ? vhost_user_opts->queues : 1;
if (queues < 1 || queues > MAX_QUEUE_NUM) { if (queues < 1 || queues > MAX_QUEUE_NUM) {
error_setg(errp, 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 = { static NetClientInfo net_vhost_vdpa_info = {
.type = NET_CLIENT_DRIVER_VHOST_VDPA, .type = NET_CLIENT_DRIVER_VHOST_VDPA,
.size = sizeof(VhostVDPAState), .size = sizeof(VhostVDPAState),
.cleanup = vhost_vdpa_cleanup, .cleanup = vhost_vdpa_cleanup,
.has_vnet_hdr = vhost_vdpa_has_vnet_hdr, .has_vnet_hdr = vhost_vdpa_has_vnet_hdr,
.has_ufo = vhost_vdpa_has_ufo, .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, 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; 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, int net_init_vhost_vdpa(const Netdev *netdev, const char *name,
NetClientState *peer, Error **errp) 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); assert(netdev->type == NET_CLIENT_DRIVER_VHOST_VDPA);
opts = &netdev->u.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); return net_vhost_vdpa_init(peer, TYPE_VHOST_VDPA, name, opts->vhostdev);
} }

View File

@ -32,17 +32,23 @@
## ##
# @device_add: # @device_add:
# #
# Add a device.
#
# @driver: the name of the new device's driver # @driver: the name of the new device's driver
# #
# @bus: the device's parent bus (device tree path) # @bus: the device's parent bus (device tree path)
# #
# @id: the device's ID, must be unique # @id: the device's ID, must be unique
# #
# Additional arguments depend on the type. # Features:
# # @json-cli: If present, the "-device" command line option supports JSON
# Add a device. # syntax with a structure identical to the arguments of this
# command.
# #
# Notes: # Notes:
#
# Additional arguments depend on the type.
#
# 1. For detailed information about this command, please refer to the # 1. For detailed information about this command, please refer to the
# 'docs/qdev-device-use.txt' file. # 'docs/qdev-device-use.txt' file.
# #
@ -67,7 +73,8 @@
## ##
{ 'command': 'device_add', { 'command': 'device_add',
'data': {'driver': 'str', '*bus': 'str', '*id': 'str'}, '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: # @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, bool object_property_set(Object *obj, const char *name, Visitor *v,
Error **errp) Error **errp)
{ {
Error *err = NULL; ERRP_GUARD();
ObjectProperty *prop = object_property_find_err(obj, name, errp); ObjectProperty *prop = object_property_find_err(obj, name, errp);
if (prop == NULL) { if (prop == NULL) {
@ -1400,9 +1400,8 @@ bool object_property_set(Object *obj, const char *name, Visitor *v,
error_setg(errp, QERR_PERMISSION_DENIED); error_setg(errp, QERR_PERMISSION_DENIED);
return false; return false;
} }
prop->set(obj, v, name, prop->opaque, &err); prop->set(obj, v, name, prop->opaque, errp);
error_propagate(errp, err); return !*errp;
return !err;
} }
bool object_property_set_str(Object *obj, const char *name, 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) Visitor *v, Error **errp)
{ {
const QDictEntry *e; const QDictEntry *e;
Error *local_err = NULL;
if (!visit_start_struct(v, NULL, NULL, 0, &local_err)) { if (!visit_start_struct(v, NULL, NULL, 0, errp)) {
goto out; return;
} }
for (e = qdict_first(qdict); e; e = qdict_next(qdict, e)) { for (e = qdict_first(qdict); e; e = qdict_next(qdict, e)) {
if (!object_property_set(obj, e->key, v, &local_err)) { if (!object_property_set(obj, e->key, v, errp)) {
break; goto out;
} }
} }
if (!local_err) { visit_check_struct(v, errp);
visit_check_struct(v, &local_err);
}
visit_end_struct(v, NULL);
out: out:
if (local_err) { visit_end_struct(v, NULL);
error_propagate(errp, local_err);
}
} }
void object_set_properties_from_keyval(Object *obj, const QDict *qdict, 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/dispatch.h"
#include "qapi/qmp/qdict.h" #include "qapi/qmp/qdict.h"
#include "qapi/qmp/qerror.h" #include "qapi/qmp/qerror.h"
#include "qapi/qmp/qstring.h"
#include "qapi/qobject-input-visitor.h"
#include "qemu/config-file.h" #include "qemu/config-file.h"
#include "qemu/error-report.h" #include "qemu/error-report.h"
#include "qemu/help_option.h" #include "qemu/help_option.h"
@ -194,22 +196,6 @@ static void qdev_print_devinfos(bool show_no_user)
g_slist_free(list); 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) static const char *find_typename_by_alias(const char *alias)
{ {
int i; int i;
@ -578,32 +564,49 @@ static BusState *qbus_find(const char *path, Error **errp)
return bus; 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) { ObjectProperty *prop;
dev->id = id;
}
if (dev->id) { assert(!dev->id && !dev->realized);
object_property_add_child(qdev_get_peripheral(), dev->id,
OBJECT(dev)); /*
* 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 { } else {
static int anon_count; static int anon_count;
gchar *name = g_strdup_printf("device[%d]", anon_count++); gchar *name = g_strdup_printf("device[%d]", anon_count++);
object_property_add_child(qdev_get_peripheral_anon(), name, prop = object_property_add_child(qdev_get_peripheral_anon(), name,
OBJECT(dev)); OBJECT(dev));
g_free(name); 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; DeviceClass *dc;
const char *driver, *path; const char *driver, *path;
char *id;
DeviceState *dev = NULL; DeviceState *dev = NULL;
BusState *bus = NULL; BusState *bus = NULL;
driver = qemu_opt_get(opts, "driver"); driver = qdict_get_try_str(opts, "driver");
if (!driver) { if (!driver) {
error_setg(errp, QERR_MISSING_PARAMETER, "driver"); error_setg(errp, QERR_MISSING_PARAMETER, "driver");
return NULL; return NULL;
@ -616,7 +619,7 @@ DeviceState *qdev_device_add(QemuOpts *opts, Error **errp)
} }
/* find bus */ /* find bus */
path = qemu_opt_get(opts, "bus"); path = qdict_get_try_str(opts, "bus");
if (path != NULL) { if (path != NULL) {
bus = qbus_find(path, errp); bus = qbus_find(path, errp);
if (!bus) { if (!bus) {
@ -636,16 +639,18 @@ DeviceState *qdev_device_add(QemuOpts *opts, Error **errp)
} }
} }
if (qemu_opt_get(opts, "failover_pair_id")) { if (qdict_haskey(opts, "failover_pair_id")) {
if (!opts->id) { if (!qdict_haskey(opts, "id")) {
error_setg(errp, "Device with failover_pair_id don't have id"); error_setg(errp, "Device with failover_pair_id don't have id");
return NULL; return NULL;
} }
if (qdev_should_hide_device(opts)) { if (qdev_should_hide_device(opts, from_json, errp)) {
if (bus && !qbus_is_hotpluggable(bus)) { if (bus && !qbus_is_hotpluggable(bus)) {
error_setg(errp, QERR_BUS_NO_HOTPLUG, bus->name); error_setg(errp, QERR_BUS_NO_HOTPLUG, bus->name);
} }
return NULL; 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 dev's parent and register its id.
/* set properties */ * If it fails it means the id is already taken.
if (qemu_opt_foreach(opts, set_property, dev, errp)) { */
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; goto err_del_dev;
} }
dev->opts = opts;
if (!qdev_realize(DEVICE(dev), bus, errp)) { if (!qdev_realize(DEVICE(dev), bus, errp)) {
dev->opts = NULL;
goto err_del_dev; goto err_del_dev;
} }
return dev; return dev;
@ -698,6 +715,19 @@ err_del_dev:
return NULL; 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__) #define qdev_printf(fmt, ...) monitor_printf(mon, "%*s" fmt, indent, "", ## __VA_ARGS__)
static void qbus_print(Monitor *mon, BusState *bus, int indent); static void qbus_print(Monitor *mon, BusState *bus, int indent);

View File

@ -144,6 +144,12 @@ typedef struct ObjectOption {
QTAILQ_ENTRY(ObjectOption) next; QTAILQ_ENTRY(ObjectOption) next;
} ObjectOption; } ObjectOption;
typedef struct DeviceOption {
QDict *opts;
Location loc;
QTAILQ_ENTRY(DeviceOption) next;
} DeviceOption;
static const char *cpu_option; static const char *cpu_option;
static const char *mem_path; static const char *mem_path;
static const char *incoming; static const char *incoming;
@ -151,6 +157,7 @@ static const char *loadvm;
static const char *accelerators; static const char *accelerators;
static QDict *machine_opts_dict; static QDict *machine_opts_dict;
static QTAILQ_HEAD(, ObjectOption) object_opts = QTAILQ_HEAD_INITIALIZER(object_opts); 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 ram_addr_t maxram_size;
static uint64_t ram_slots; static uint64_t ram_slots;
static int display_remote; static int display_remote;
@ -494,21 +501,39 @@ const char *qemu_get_vm_name(void)
return qemu_name; 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; int i;
if (!driver) if (!driver) {
return 0; return;
}
for (i = 0; i < ARRAY_SIZE(default_list); i++) { for (i = 0; i < ARRAY_SIZE(default_list); i++) {
if (strcmp(default_list[i].driver, driver) != 0) if (strcmp(default_list[i].driver, driver) != 0)
continue; continue;
*(default_list[i].flag) = 0; *(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; 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) static int parse_name(void *opaque, QemuOpts *opts, Error **errp)
{ {
const char *proc_name; const char *proc_name;
@ -1311,6 +1336,7 @@ static void qemu_disable_default_devices(void)
{ {
MachineClass *machine_class = MACHINE_GET_CLASS(current_machine); MachineClass *machine_class = MACHINE_GET_CLASS(current_machine);
default_driver_check_json();
qemu_opts_foreach(qemu_find_opts("device"), qemu_opts_foreach(qemu_find_opts("device"),
default_driver_check, NULL, NULL); default_driver_check, NULL, NULL);
qemu_opts_foreach(qemu_find_opts("global"), qemu_opts_foreach(qemu_find_opts("global"),
@ -2637,6 +2663,8 @@ static void qemu_init_board(void)
static void qemu_create_cli_devices(void) static void qemu_create_cli_devices(void)
{ {
DeviceOption *opt;
soundhw_init(); soundhw_init();
qemu_opts_foreach(qemu_find_opts("fw_cfg"), 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); rom_set_order_override(FW_CFG_ORDER_OVERRIDE_DEVICE);
qemu_opts_foreach(qemu_find_opts("device"), qemu_opts_foreach(qemu_find_opts("device"),
device_init_func, NULL, &error_fatal); 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(); rom_reset_order_override();
} }
@ -3352,9 +3392,18 @@ void qemu_init(int argc, char **argv, char **envp)
add_device_config(DEV_USB, optarg); add_device_config(DEV_USB, optarg);
break; break;
case QEMU_OPTION_device: case QEMU_OPTION_device:
if (!qemu_opts_parse_noisily(qemu_find_opts("device"), if (optarg[0] == '{') {
optarg, true)) { QObject *obj = qobject_from_json(optarg, &error_fatal);
exit(1); 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; break;
case QEMU_OPTION_smp: 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 # 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 # device, so a second virtio-blk device can't be added even with the
# same iothread. virtio-scsi allows this. # 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 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 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 (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 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 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 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') self.run_test_iothreads('iothread0', 'iothread0')
def test_iothreads_switch_backing(self): def test_iothreads_switch_backing(self):
self.run_test_iothreads('iothread0', None) self.run_test_iothreads('iothread0', '')
def test_iothreads_switch_overlay(self): def test_iothreads_switch_overlay(self):
self.run_test_iothreads(None, 'iothread0') self.run_test_iothreads('', 'iothread0')
if __name__ == '__main__': if __name__ == '__main__':
iotests.activate_logging() iotests.activate_logging()

View File

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