diff mbox series

[01/11] qdev/qbus: add hidden device support

Message ID 20191018202040.30349-2-jfreimann@redhat.com
State New
Headers show
Series add failover feature for assigned network devices | expand

Commit Message

Jens Freimann Oct. 18, 2019, 8:20 p.m. UTC
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 net_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 "net_failover_pair_id" option, e.g.

 -device virtio-net-pci,...,failover=on,...
 -device vfio-pci,...,net_failover_pair_id=net1,...

Signed-off-by: Jens Freimann <jfreimann@redhat.com>
---
 hw/core/qdev.c         | 23 +++++++++++++++++++++++
 include/hw/qdev-core.h |  8 ++++++++
 qdev-monitor.c         | 36 +++++++++++++++++++++++++++++++++---
 vl.c                   |  6 ++++--
 4 files changed, 68 insertions(+), 5 deletions(-)

Comments

Cornelia Huck Oct. 21, 2019, 12:44 p.m. UTC | #1
On Fri, 18 Oct 2019 22:20:30 +0200
Jens Freimann <jfreimann@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 net_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 "net_failover_pair_id" option, e.g.
> 
>  -device virtio-net-pci,...,failover=on,...
>  -device vfio-pci,...,net_failover_pair_id=net1,...
> 
> Signed-off-by: Jens Freimann <jfreimann@redhat.com>
> ---
>  hw/core/qdev.c         | 23 +++++++++++++++++++++++
>  include/hw/qdev-core.h |  8 ++++++++
>  qdev-monitor.c         | 36 +++++++++++++++++++++++++++++++++---
>  vl.c                   |  6 ++++--
>  4 files changed, 68 insertions(+), 5 deletions(-)
> 
> diff --git a/hw/core/qdev.c b/hw/core/qdev.c
> index cbad6c1d55..89c134ec53 100644
> --- a/hw/core/qdev.c
> +++ b/hw/core/qdev.c
> @@ -212,6 +212,29 @@ void device_listener_unregister(DeviceListener *listener)
>      QTAILQ_REMOVE(&device_listeners, listener, link);
>  }
>  
> +bool qdev_should_hide_device(QemuOpts *opts)
> +{
> +    int rc;

Initialize to 0?

> +    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)
>  {

(...)

> +static bool should_hide_device(QemuOpts *opts)
> +{
> +    if (qemu_opt_foreach(opts, is_failover_device, opts, NULL) == 0) {
> +        return false;
> +    }
> +    return true;
> +}

I still think you should turn the check around to make it easier to
extend in the future, but this is fine as well.

(...)

With the rc thing changed,

Reviewed-by: Cornelia Huck <cohuck@redhat.com>
Jens Freimann Oct. 21, 2019, 12:52 p.m. UTC | #2
On Mon, Oct 21, 2019 at 02:44:08PM +0200, Cornelia Huck wrote:
>On Fri, 18 Oct 2019 22:20:30 +0200
>Jens Freimann <jfreimann@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 net_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 "net_failover_pair_id" option, e.g.
>>
>>  -device virtio-net-pci,...,failover=on,...
>>  -device vfio-pci,...,net_failover_pair_id=net1,...
>>
>> Signed-off-by: Jens Freimann <jfreimann@redhat.com>
>> ---
>>  hw/core/qdev.c         | 23 +++++++++++++++++++++++
>>  include/hw/qdev-core.h |  8 ++++++++
>>  qdev-monitor.c         | 36 +++++++++++++++++++++++++++++++++---
>>  vl.c                   |  6 ++++--
>>  4 files changed, 68 insertions(+), 5 deletions(-)
>>
>> diff --git a/hw/core/qdev.c b/hw/core/qdev.c
>> index cbad6c1d55..89c134ec53 100644
>> --- a/hw/core/qdev.c
>> +++ b/hw/core/qdev.c
>> @@ -212,6 +212,29 @@ void device_listener_unregister(DeviceListener *listener)
>>      QTAILQ_REMOVE(&device_listeners, listener, link);
>>  }
>>
>> +bool qdev_should_hide_device(QemuOpts *opts)
>> +{
>> +    int rc;
>
>Initialize to 0?

Yes, that's what the test bot found as well. Fixed it already. 
Though I initialize to -1, otherwise all devices will be hidden.

>> +    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)
>>  {
>
>(...)
>
>> +static bool should_hide_device(QemuOpts *opts)
>> +{
>> +    if (qemu_opt_foreach(opts, is_failover_device, opts, NULL) == 0) {
>> +        return false;
>> +    }
>> +    return true;
>> +}
>
>I still think you should turn the check around to make it easier to
>extend in the future, but this is fine as well.

Sorry, I've gone back and forth on this and ended up with the same.
I'll take another look at it. 

>(...)
>
>With the rc thing changed,
>
>Reviewed-by: Cornelia Huck <cohuck@redhat.com>

Thanks!

regards,
Jens
Peter Maydell Oct. 21, 2019, 12:53 p.m. UTC | #3
On Fri, 18 Oct 2019 at 21:22, Jens Freimann <jfreimann@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.

In what sense is a hidden device hidden? Is it hidden from
the guest (in what way) ? Is it hidden from the QMP/HMP monitor
commands? Is it hidden from QEMU functions that iterate through
the qbus, or that walk the QOM tree ? What does a hidden device
have to do to be successfully hidden ? Is it completely disconnected
from the rest of the simulation, or does it itself have to avoid
doing things like asserting IRQ lines ?

Expanding the DeviceClass doc comment might be a good place to answer
questions like the above and generally describe the 'hidden device'
mechanism.

>  };
>
> @@ -451,4 +457,6 @@ static inline bool qbus_is_hotpluggable(BusState *bus)
>  void device_listener_register(DeviceListener *listener);
>  void device_listener_unregister(DeviceListener *listener);
>
> +bool qdev_should_hide_device(QemuOpts *opts);

New globally visible functions should always have doc-comment
format documentation comments, please.

thanks
-- PMM
Jens Freimann Oct. 21, 2019, 1 p.m. UTC | #4
On Mon, Oct 21, 2019 at 01:53:58PM +0100, Peter Maydell wrote:
>On Fri, 18 Oct 2019 at 21:22, Jens Freimann <jfreimann@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.
>
>In what sense is a hidden device hidden? Is it hidden from
>the guest (in what way) ? Is it hidden from the QMP/HMP monitor
>commands? Is it hidden from QEMU functions that iterate through
>the qbus, or that walk the QOM tree ? What does a hidden device
>have to do to be successfully hidden ? Is it completely disconnected
>from the rest of the simulation, or does it itself have to avoid
>doing things like asserting IRQ lines ?
>
>Expanding the DeviceClass doc comment might be a good place to answer
>questions like the above and generally describe the 'hidden device'
>mechanism.

ok, I will add to the DeviceClass comment. 

>
>>  };
>>
>> @@ -451,4 +457,6 @@ static inline bool qbus_is_hotpluggable(BusState *bus)
>>  void device_listener_register(DeviceListener *listener);
>>  void device_listener_unregister(DeviceListener *listener);
>>
>> +bool qdev_should_hide_device(QemuOpts *opts);
>
>New globally visible functions should always have doc-comment
>format documentation comments, please.

Will add this too. Thanks for the review!

regards,
Jens
diff mbox series

Patch

diff --git a/hw/core/qdev.c b/hw/core/qdev.c
index cbad6c1d55..89c134ec53 100644
--- a/hw/core/qdev.c
+++ b/hw/core/qdev.c
@@ -212,6 +212,29 @@  void device_listener_unregister(DeviceListener *listener)
     QTAILQ_REMOVE(&device_listeners, listener, link);
 }
 
+bool qdev_should_hide_device(QemuOpts *opts)
+{
+    int rc;
+    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..28f594a47d 100644
--- a/include/hw/qdev-core.h
+++ b/include/hw/qdev-core.h
@@ -154,6 +154,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 +457,6 @@  static inline bool qbus_is_hotpluggable(BusState *bus)
 void device_listener_register(DeviceListener *listener);
 void device_listener_unregister(DeviceListener *listener);
 
+bool qdev_should_hide_device(QemuOpts *opts);
+
 #endif
diff --git a/qdev-monitor.c b/qdev-monitor.c
index 148df9cacf..508d85df87 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,14 +564,40 @@  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, "net_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;
 
+    if (opts && should_hide_device(opts)) {
+        return NULL;
+    }
+
     driver = qemu_opt_get(opts, "driver");
     if (!driver) {
         error_setg(errp, QERR_MISSING_PARAMETER, "driver");
@@ -648,8 +676,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;
 }