On Sat, Mar 08, 2025 at 14:57:41 +0900, Akihiko Odaki wrote:
> usb-storage is a compound device that automatically creates a USB mass
> storage device and a SCSI device as its backend. Unfortunately it lacks
> some configuration options that are usually present with a SCSI device,
> and cannot represent CD-ROM in particular.
> 
> Replace usb-storage with usb-bot, which can be combined with a manually
> created SCSI device. libvirt will configure the SCSI device in a way
> identical with how QEMU does for usb-storage except that now it respects
> a configuration option to represent CD-ROM.

Mention that the devices are ABI compatible so the patch is replacing
the old implementation without keeping the legacy code.

> 
> Resolves: https://gitlab.com/libvirt/libvirt/-/issues/368
> Signed-off-by: Akihiko Odaki <akihiko.od...@daynix.com>
> ---
>  src/qemu/qemu_alias.c                              |  3 +-
>  src/qemu/qemu_capabilities.c                       |  2 +-
>  src/qemu/qemu_command.c                            | 55 
> ++++++++++++++++++++--
>  src/qemu/qemu_command.h                            |  5 ++
>  src/qemu/qemu_hotplug.c                            | 34 ++++++++++---
>  src/qemu/qemu_validate.c                           |  4 +-
>  tests/qemuhotplugtest.c                            |  8 +++-
>  .../qemuxmlconfdata/disk-cache.x86_64-latest.args  |  3 +-
>  .../disk-cdrom-bus-other.x86_64-latest.args        |  3 +-
>  .../disk-device-removable.x86_64-latest.args       |  3 +-
>  .../disk-usb-device.x86_64-latest.args             |  3 +-
>  11 files changed, 102 insertions(+), 21 deletions(-)

Sorry for not forgetting this series for long time ...



> 
> diff --git a/src/qemu/qemu_alias.c b/src/qemu/qemu_alias.c
> index 
> 3e6bced4a88538a040dd8a65f40dc9f7f56b8ec9..64986368d0588f7778c8e3a09ae3169c1961b456
>  100644
> --- a/src/qemu/qemu_alias.c
> +++ b/src/qemu/qemu_alias.c
> @@ -267,8 +267,7 @@ qemuAssignDeviceDiskAlias(virDomainDef *def,
>              break;
>  
>          case VIR_DOMAIN_DISK_BUS_USB:
> -            diskPriv->qomName = 
> g_strdup_printf("/machine/peripheral/%s/%s.0/legacy[0]",
> -                                                disk->info.alias, 
> disk->info.alias);
> +            diskPriv->qomName = g_strconcat("scsi", disk->info.alias, NULL);a

What you create here is not the qom path. when used it's reported as

/machine/peripheral/scsiusb-disk0

This'll most likely cause failures if throttling were applied but I
didn't test this.

When you change this make sure to re-test it as qemu sends 2
DEVICE_DELETED events in this case. Fixing the above may make libvirt to
trigger on both.

>              break;
>  
>          case VIR_DOMAIN_DISK_BUS_XEN:


> diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c
> index 
> 0ad73af335974a236341fa85d02c83f14af08934..29db916e70804ec482392c078a280904a992eaf3
>  100644
> --- a/src/qemu/qemu_command.c
> +++ b/src/qemu/qemu_command.c
> @@ -1611,6 +1611,31 @@ qemuBuildIothreadMappingProps(GSList *iothreads)
>      return g_steal_pointer(&ret);
>  }
>  
> +int
> +qemuBuildDiskBusProps(const virDomainDef *def,
> +                      const virDomainDiskDef *disk,
> +                      virJSONValue **propsRet)

Normally this would be a "controller" the disk would just connect to it.

In case when we'd want to use it as a direct replacement libvirt doesn't
yet have any mechanism to allow adding controller and disk separately.
Also it wouldn't make sense beceause we want it to be a direct
replacement.

So doing this is okay but the function name ought to be limited to just
USB controllers, because this results in another -device (the
controller) and not just "bus properties".

how about qemuBuildDiskUSBBotProps

> +{
> +    g_autoptr(virJSONValue) props = NULL;
> +
> +    *propsRet = NULL;
> +
> +    if (disk->bus != VIR_DOMAIN_DISK_BUS_USB)
> +        return 0;
> +
> +    if (virJSONValueObjectAdd(&props,
> +                              "s:driver", "usb-bot",
> +                              "s:id", disk->info.alias,
> +                              NULL) < 0)
> +        return -1;
> +
> +    if (qemuBuildDeviceAddressProps(props, def, &disk->info) < 0)
> +        return -1;
> +
> +    *propsRet = g_steal_pointer(&props);
> +
> +    return 0;
> +}
>  
>  virJSONValue *
>  qemuBuildDiskDeviceProps(const virDomainDef *def,
> @@ -1619,6 +1644,7 @@ qemuBuildDiskDeviceProps(const virDomainDef *def,
>  {
>      g_autoptr(virJSONValue) props = NULL;
>      const char *driver = NULL;
> +    g_autofree char *usbBusId = NULL;
>      g_autofree char *scsiVPDDeviceId = NULL;
>      virTristateSwitch shareRW = VIR_TRISTATE_SWITCH_ABSENT;
>      g_autofree char *chardev = NULL;
> @@ -1637,6 +1663,7 @@ qemuBuildDiskDeviceProps(const virDomainDef *def,
>      const char *rpolicy = NULL;
>      const char *model = NULL;
>      const char *product = NULL;
> +    const char *id = disk->info.alias;
>  
>      switch (disk->bus) {
>      case VIR_DOMAIN_DISK_BUS_IDE:
> @@ -1650,6 +1677,17 @@ qemuBuildDiskDeviceProps(const virDomainDef *def,
>  
>          break;
>  
> +    case VIR_DOMAIN_DISK_BUS_USB:
> +        usbBusId = g_strconcat(disk->info.alias, ".0", NULL);
> +        if (virJSONValueObjectAdd(&props,
> +                                  "s:bus", usbBusId,
> +                                  "u:scsi-id", 0,
> +                                  "u:lun", 0,
> +                                  NULL) < 0)

Formatting this here makes it appear before 'driver'. While not a
problem for qemu it makes it harder when looking at commandlines to see
what's happening.

Please move the USB address formatting after the pre-existing props are
formatted even if it involves another block/check.

> +            return NULL;
> +
> +        id = QEMU_DOMAIN_DISK_PRIVATE(disk)->qomName;
> +        G_GNUC_FALLTHROUGH;

Also add comment why we are falling trhough.

>      case VIR_DOMAIN_DISK_BUS_SCSI:
>          if (disk->device == VIR_DOMAIN_DISK_DEVICE_LUN) {
>              driver = "scsi-block";
> @@ -1719,8 +1757,6 @@ qemuBuildDiskDeviceProps(const virDomainDef *def,
>      }
>          break;
>  
> -    case VIR_DOMAIN_DISK_BUS_USB:
> -        driver = "usb-storage";
>  
>          if (disk->removable == VIR_TRISTATE_SWITCH_ABSENT)
>              removable = VIR_TRISTATE_SWITCH_OFF;
> @@ -1755,7 +1791,8 @@ qemuBuildDiskDeviceProps(const virDomainDef *def,
>      if (disk->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DRIVE)
>          disk->info.addr.drive.diskbus = disk->bus;
>  
> -    if (qemuBuildDeviceAddressProps(props, def, &disk->info) < 0)

Please add a comment why the address is not formatted here.

> +    if (disk->bus != VIR_DOMAIN_DISK_BUS_USB &&
> +        qemuBuildDeviceAddressProps(props, def, &disk->info) < 0)
>          return NULL;
>  
>      if (disk->src->shared)
> @@ -1816,7 +1853,7 @@ qemuBuildDiskDeviceProps(const virDomainDef *def,
>                                "T:share-rw", shareRW,
>                                "S:drive", drive,
>                                "S:chardev", chardev,
> -                              "s:id", disk->info.alias,
> +                              "s:id", id,
>                                "p:bootindex", bootindex,
>                                "S:loadparm", bootLoadparm,
>                                "p:logical_block_size", logical_block_size,
> @@ -2110,6 +2147,16 @@ qemuBuildDiskCommandLine(virCommand *cmd,
>      if (qemuCommandAddExtDevice(cmd, &disk->info, def, qemuCaps) < 0)
>          return -1;
>  
> +    if (qemuBuildDiskBusProps(def, disk, &devprops) < 0)
> +        return -1;
> +
> +    if (devprops) {

Change the name to 'usbprops ...

> +        if (qemuBuildDeviceCommandlineFromJSON(cmd, devprops, def, qemuCaps) 
> < 0)
> +            return -1;
> +
> +        g_clear_pointer(&devprops, virJSONValueFree);

and don't reuse the variable.

> +    }
> +
>      if (!(devprops = qemuBuildDiskDeviceProps(def, disk, qemuCaps)))
>          return -1;
>  
> diff --git a/src/qemu/qemu_command.h b/src/qemu/qemu_command.h
> index 
> 76c514b5f744c60e433f0d8d8760a8730c886eed..48b8ed8ac58d005503c8bdb7e255af097a548fd9
>  100644
> --- a/src/qemu/qemu_command.h
> +++ b/src/qemu/qemu_command.h
> @@ -116,6 +116,11 @@ qemuBlockStorageSourceChainData *
>  qemuBuildStorageSourceChainAttachPrepareBlockdevTop(virStorageSource *top,
>                                                      virStorageSource 
> *backingStore);
>  
> +int
> +qemuBuildDiskBusProps(const virDomainDef *def,
> +                      const virDomainDiskDef *disk,
> +                      virJSONValue **propsRet);
> +
>  virJSONValue *
>  qemuBuildDiskDeviceProps(const virDomainDef *def,
>                           virDomainDiskDef *disk,
> diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c
> index 
> 28ca321c5c194678d25d67aa02ec82c13175f4f5..cfbaf195bd0a8991c8902795560bda3ee85e42bc
>  100644
> --- a/src/qemu/qemu_hotplug.c
> +++ b/src/qemu/qemu_hotplug.c
> @@ -31,6 +31,7 @@
>  #include "qemu_command.h"
>  #include "qemu_hostdev.h"
>  #include "qemu_interface.h"
> +#include "qemu_monitor_json.h"
>  #include "qemu_passt.h"
>  #include "qemu_process.h"
>  #include "qemu_security.h"
> @@ -709,8 +710,10 @@ qemuDomainAttachDiskGeneric(virDomainObj *vm,
>  {
>      g_autoptr(qemuBlockStorageSourceChainData) data = NULL;
>      qemuDomainObjPrivate *priv = vm->privateData;
> +    g_autoptr(virJSONValue) busprops = NULL;
>      g_autoptr(virJSONValue) devprops = NULL;
>      bool extensionDeviceAttached = false;
> +    bool busAdded = false;
>      int rc;
>      g_autoptr(qemuSnapshotDiskContext) transientDiskSnapshotCtxt = NULL;
>      bool origReadonly = disk->src->readonly;
> @@ -766,6 +769,9 @@ qemuDomainAttachDiskGeneric(virDomainObj *vm,
>          }
>      }
>  
> +    if (qemuBuildDiskBusProps(vm->def, disk, &busprops) < 0)
> +        goto rollback;

Move the preparation of the property object before code that needs to be
rolled back.

I need to also do the same for other bits of code that were added
recently and I missed that during review.

> +
>      if (!(devprops = qemuBuildDiskDeviceProps(vm->def, disk, 
> priv->qemuCaps)))
>          goto rollback;
>  
> @@ -775,16 +781,20 @@ qemuDomainAttachDiskGeneric(virDomainObj *vm,
>      if ((rc = qemuDomainAttachExtensionDevice(priv->mon, &disk->info)) == 0)
>          extensionDeviceAttached = true;
>  
> +    if (rc == 0 && busprops &&
> +        (rc = qemuMonitorAddDeviceProps(priv->mon, &busprops)) == 0)
> +        busAdded = true;
> +
>      if (rc == 0)
>          rc = qemuMonitorAddDeviceProps(priv->mon, &devprops);
>  
> -    /* Setup throttling of disk via block_set_io_throttle QMP command. This
> -     * is a hack until the 'throttle' blockdev driver will support 
> modification
> -     * of the trhottle group. See also 
> qemuProcessSetupDiskThrottlingBlockdev.
> -     * As there isn't anything sane to do if this fails, let's just return
> -     * success.
> -     */
>      if (rc == 0) {
> +        /* Setup throttling of disk via block_set_io_throttle QMP command. 
> This
> +         * is a hack until the 'throttle' blockdev driver will support 
> modification
> +         * of the trhottle group. See also 
> qemuProcessSetupDiskThrottlingBlockdev.
> +         * As there isn't anything sane to do if this fails, let's just 
> return
> +         * success.
> +         */
>          qemuDomainDiskPrivate *diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk);
>          g_autoptr(GHashTable) blockinfo = NULL;
>  
> @@ -801,6 +811,15 @@ qemuDomainAttachDiskGeneric(virDomainObj *vm,
>                  qemuProcessRefreshDiskProps(disk, diskinfo);
>              }
>          }
> +
> +        if (disk->bus == VIR_DOMAIN_DISK_BUS_USB) {
> +            qemuMonitorJSONObjectProperty prop = {
> +                .type = QEMU_MONITOR_OBJECT_PROPERTY_BOOLEAN,
> +                .val.b = true,
> +            };
> +
> +            rc = qemuMonitorJSONSetObjectProperty(priv->mon, 
> disk->info.alias, "attached", &prop);
> +        }

Preferrably use another block starting with if (rc == 0). The
optimization to reuse the block which doesn't actually modify 'rc' may
obscure the fact that this does.

>      }
>  
>      qemuDomainObjExitMonitor(vm);
> @@ -814,6 +833,9 @@ qemuDomainAttachDiskGeneric(virDomainObj *vm,
>      if (qemuDomainObjEnterMonitorAsync(vm, asyncJob) < 0)
>          return -1;
>  
> +    if (busAdded)
> +        ignore_value(qemuMonitorDelDevice(priv->mon, disk->info.alias));
> +
>      if (extensionDeviceAttached)
>          ignore_value(qemuDomainDetachExtensionDevice(priv->mon, 
> &disk->info));
>  
> diff --git a/src/qemu/qemu_validate.c b/src/qemu/qemu_validate.c
> index 
> f3ef1be660b1cbbfe8ed5643baf2e9f4a63cf60d..f9ec3a7c076c3755e1c3ddf3c63aeeb7ed576f2b
>  100644
> --- a/src/qemu/qemu_validate.c
> +++ b/src/qemu/qemu_validate.c
> @@ -3146,9 +3146,9 @@ qemuValidateDomainDeviceDefDiskFrontend(const 
> virDomainDiskDef *disk,
>          break;
>  
>      case VIR_DOMAIN_DISK_BUS_USB:
> -        if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_USB_STORAGE)) {
> +        if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_USB_BOT)) {

This will break hotplug for existing running VMs if libvirt is upgraded.

Libvirt will not reprobe the capabilities of the active VM and this
implementation is replacing the detection here with a new flag. A flag
which didn't exist at the time the VM was started and so it will not
appear.

Since it's being replaced by a supposedly identical configuration (From
guest OS point of view) we don't actually need to keep the legacy code
path around. What we need though is to reuse existing capability.


>              virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
> -                           _("This QEMU doesn't support '-device 
> usb-storage'"));
> +                           _("This QEMU doesn't support '-device usb-bot'"));
>              return -1;
>          }
>  

Reply via email to