* Jens Freimann (jfreim...@redhat.com) wrote: > This adds support for hiding a device to the qbus and qdev APIs. The > first user of this will be the virtio-net failover feature but the API > introduced with this patch could be used to implement other features as > well, for example hiding pci devices when a pci bus is powered off. > > qdev_device_add() is modified to check for a failover_pair_id > argument in the option string. A DeviceListener callback > should_be_hidden() is added. It can be used by a standby device to > inform qdev that this device should not be added now. The standby device > handler can store the device options to plug the device in at a later > point in time. > > One reason for hiding the device is that we don't want to expose both > devices to the guest kernel until the respective virtio feature bit > VIRTIO_NET_F_STANDBY was negotiated and we know that the devices will be > handled correctly by the guest. > > More information on the kernel feature this is using: > https://www.kernel.org/doc/html/latest/networking/net_failover.html > > An example where the primary device is a vfio-pci device and the standby > device is a virtio-net device: > > A device is hidden when it has an "failover_pair_id" option, e.g. > > -device virtio-net-pci,...,failover=on,... > -device vfio-pci,...,failover_pair_id=net1,... > > Signed-off-by: Jens Freimann <jfreim...@redhat.com> > Reviewed-by: Cornelia Huck <coh...@redhat.com>
I think I see why you've done this, but I'd lay odds on that we're going to find some odd corners of other things in qemu prodding hidden devices. We'll see! Dave > --- > hw/core/qdev.c | 24 ++++++++++++++++++++++++ > include/hw/qdev-core.h | 29 +++++++++++++++++++++++++++++ > qdev-monitor.c | 41 +++++++++++++++++++++++++++++++++++++---- > vl.c | 6 ++++-- > 4 files changed, 94 insertions(+), 6 deletions(-) > > diff --git a/hw/core/qdev.c b/hw/core/qdev.c > index cbad6c1d55..3b8d43d0fd 100644 > --- a/hw/core/qdev.c > +++ b/hw/core/qdev.c > @@ -212,6 +212,30 @@ void device_listener_unregister(DeviceListener *listener) > QTAILQ_REMOVE(&device_listeners, listener, link); > } > > +bool qdev_should_hide_device(QemuOpts *opts) > +{ > + int rc = -1; > + DeviceListener *listener; > + > + QTAILQ_FOREACH(listener, &device_listeners, link) { > + if (listener->should_be_hidden) { > + /* > + * should_be_hidden_will return > + * 1 if device matches opts and it should be hidden > + * 0 if device matches opts and should not be hidden > + * -1 if device doesn't match ops > + */ > + rc = listener->should_be_hidden(listener, opts); > + } > + > + if (rc > 0) { > + break; > + } > + } > + > + return rc > 0; > +} > + > void qdev_set_legacy_instance_id(DeviceState *dev, int alias_id, > int required_for_version) > { > diff --git a/include/hw/qdev-core.h b/include/hw/qdev-core.h > index aa123f88cb..710981af36 100644 > --- a/include/hw/qdev-core.h > +++ b/include/hw/qdev-core.h > @@ -78,6 +78,19 @@ typedef void (*BusUnrealize)(BusState *bus, Error **errp); > * respective parent types. > * </para> > * </note> > + * > + * # Hiding a device # > + * To hide a device, a DeviceListener function should_be_hidden() needs to > + * be registered. > + * It can be used to defer adding a device and therefore hide it from the > + * guest. The handler registering to this DeviceListener can save the QOpts > + * passed to it for re-using it later and must return that it wants the > device > + * to be/remain hidden or not. When the handler function decides the device > + * shall not be hidden it will be added in qdev_device_add() and > + * realized as any other device. Otherwise qdev_device_add() will return > early > + * without adding the device. The guest will not see a "hidden" device > + * until it was marked don't hide and qdev_device_add called again. > + * > */ > typedef struct DeviceClass { > /*< private >*/ > @@ -154,6 +167,12 @@ struct DeviceState { > struct DeviceListener { > void (*realize)(DeviceListener *listener, DeviceState *dev); > void (*unrealize)(DeviceListener *listener, DeviceState *dev); > + /* > + * This callback is called upon init of the DeviceState and allows to > + * inform qdev that a device should be hidden, depending on the device > + * opts, for example, to hide a standby device. > + */ > + int (*should_be_hidden)(DeviceListener *listener, QemuOpts *device_opts); > QTAILQ_ENTRY(DeviceListener) link; > }; > > @@ -451,4 +470,14 @@ static inline bool qbus_is_hotpluggable(BusState *bus) > void device_listener_register(DeviceListener *listener); > void device_listener_unregister(DeviceListener *listener); > > +/** > + * @qdev_should_hide_device: > + * @opts: QemuOpts as passed on cmdline. > + * > + * 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); > + > #endif > diff --git a/qdev-monitor.c b/qdev-monitor.c > index 148df9cacf..ffa08c670f 100644 > --- a/qdev-monitor.c > +++ b/qdev-monitor.c > @@ -32,9 +32,11 @@ > #include "qemu/help_option.h" > #include "qemu/option.h" > #include "qemu/qemu-print.h" > +#include "qemu/option_int.h" > #include "sysemu/block-backend.h" > #include "sysemu/sysemu.h" > #include "migration/misc.h" > +#include "migration/migration.h" > > /* > * Aliases were a bad idea from the start. Let's keep them > @@ -562,13 +564,36 @@ void qdev_set_id(DeviceState *dev, const char *id) > } > } > > +static int is_failover_device(void *opaque, const char *name, const char > *value, > + Error **errp) > +{ > + if (strcmp(name, "failover_pair_id") == 0) { > + QemuOpts *opts = (QemuOpts *)opaque; > + > + if (qdev_should_hide_device(opts)) { > + return 1; > + } > + } > + > + return 0; > +} > + > +static bool should_hide_device(QemuOpts *opts) > +{ > + if (qemu_opt_foreach(opts, is_failover_device, opts, NULL) == 0) { > + return false; > + } > + return true; > +} > + > DeviceState *qdev_device_add(QemuOpts *opts, Error **errp) > { > DeviceClass *dc; > const char *driver, *path; > - DeviceState *dev; > + DeviceState *dev = NULL; > BusState *bus = NULL; > Error *err = NULL; > + bool hide; > > driver = qemu_opt_get(opts, "driver"); > if (!driver) { > @@ -602,11 +627,17 @@ DeviceState *qdev_device_add(QemuOpts *opts, Error > **errp) > return NULL; > } > } > - if (qdev_hotplug && bus && !qbus_is_hotpluggable(bus)) { > + hide = should_hide_device(opts); > + > + if ((hide || qdev_hotplug) && bus && !qbus_is_hotpluggable(bus)) { > error_setg(errp, QERR_BUS_NO_HOTPLUG, bus->name); > return NULL; > } > > + if (hide) { > + return NULL; > + } > + > if (!migration_is_idle()) { > error_setg(errp, "device_add not allowed while migrating"); > return NULL; > @@ -648,8 +679,10 @@ DeviceState *qdev_device_add(QemuOpts *opts, Error > **errp) > > err_del_dev: > error_propagate(errp, err); > - object_unparent(OBJECT(dev)); > - object_unref(OBJECT(dev)); > + if (dev) { > + object_unparent(OBJECT(dev)); > + object_unref(OBJECT(dev)); > + } > return NULL; > } > > diff --git a/vl.c b/vl.c > index 4489cfb2bb..62c388cb49 100644 > --- a/vl.c > +++ b/vl.c > @@ -2204,10 +2204,12 @@ static int device_init_func(void *opaque, QemuOpts > *opts, Error **errp) > DeviceState *dev; > > dev = qdev_device_add(opts, errp); > - if (!dev) { > + if (!dev && *errp) { > + error_report_err(*errp); > return -1; > + } else if (dev) { > + object_unref(OBJECT(dev)); > } > - object_unref(OBJECT(dev)); > return 0; > } > > -- > 2.21.0 > -- Dr. David Alan Gilbert / dgilb...@redhat.com / Manchester, UK