Virtuozzo QEMU development (svt-core@virtuozzo.com)
 help / color / mirror / Atom feed
From: no-reply@sashiko.virtuozzo.com
To: svt-core@virtuozzo.com,
	 Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>
Cc: den@openvz.org, andrey.drobyshev@virtuozzo.com
Subject: Re: [PATCH QEMU HCI-8.0 v7 10/14] usb-host: hand off the device across cpr-transfer #VSTOR-137800
Date: Tue, 6 Oct 2026 15:52:11 +0000	[thread overview]
Message-ID: <010701a111ea5277-f87b3df8-9b09-4ef0-9041-fa9d05c7d972-000000@eu-central-1.amazonses.com> (raw)
In-Reply-To: <20261006-usb-host_v3-v7-10-044f668789a1@virtuozzo.com>

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [Medium] usb-host: cpr_handed_off set before migration outcome is known, skips cleanup on failure
- [Medium] usb-host: cpr_is_incoming() in realize() stops global event reaping with no restart
- [Medium] usb-host: unrealize() clears handoff flag without restarting the global event reaping
- [Low] usb-host: unrealize() forces full reset even if the target may already own the device
- [Low] usb-host: commit message does not match where reaping is stopped and restarted
--

commit d5825316f822699756346d45e052e4ba605a47fe
Author: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>

usb-host: hand off the device across cpr-transfer #VSTOR-137800

This patch hands the usbfs FD and URB reaping off from the source QEMU to
the target QEMU during cpr-transfer. The source stops reaping at quiesce
time and skips device reset, interface release and driver rebind on exit.
The target stops reaping in realize() and restarts it in post_load().

> diff --git a/hw/usb/host-libusb.c b/hw/usb/host-libusb.c
> --- a/hw/usb/host-libusb.c
> +++ b/hw/usb/host-libusb.c

[ ... ]

> +static void usb_host_stop_events(void)
> +{
> +    const struct libusb_pollfd **poll = libusb_get_pollfds(ctx);
> +
> +    libusb_set_pollfd_notifiers(ctx, NULL, NULL, NULL);
> +    if (poll) {
> +        for (int i = 0; poll[i] != NULL; i++) {
> +            usb_host_del_fd(poll[i]->fd, ctx);
> +        }
> +        free(poll);
> +    }
> +}

[ ... ]

>  static int usb_host_cpr_quiesce(USBHostDevice *s, MigMode mode)
>  {
>      if (usb_host_cpr_drain_xfers(s) < 0) {
>          return -1;
>      }
> +    if (mode == MIG_MODE_CPR_TRANSFER) {
> +        usb_host_stop_events();
> +        s->cpr_handed_off = true;
> +    }
>      return 0;
>  }

[Severity: Medium]
Is it safe to set cpr_handed_off here, before the outcome of the migration
is known?

The flag is cleared only in usb_host_cpr_reissue(), which runs on a running
transition, and in usb_host_unrealize().

Consider the case where usb_host_pre_save() calls usb_host_cpr_quiesce() as
the fallback for an already-paused VM, and the migration then fails.  Nothing
resumes the VM, so cpr_handed_off stays true.

usb_host_exit_notifier() then skips the following on every later quit and
only calls libusb_close():

    usb_host_abort_xfers(s);
    usb_host_release_interfaces(s);
    libusb_reset_device(s->dh);
    usb_host_attach_kernel(s);

Can this leave the device without a reset and without the host kernel
driver rebound after a failed or cancelled cpr-transfer?

The commit message says the skip happens only "On success", but the exit
notifier cannot tell success from failure.

[ ... ]

> @@ -1371,16 +1402,19 @@ static int usb_host_close(USBHostDevice *s)
>
>      trace_usb_host_close(s->bus_num, s->addr);
>
> -    usb_host_abort_xfers(s);
> -    usb_host_iso_free_all(s);
> +    if (!s->cpr_handed_off) {
> +        usb_host_abort_xfers(s);
> +        usb_host_iso_free_all(s);
>
> -    if (udev->attached) {
> -        usb_device_detach(udev);
> +        if (udev->attached) {
> +            usb_device_detach(udev);
> +        }
> +
> +        usb_host_release_interfaces(s);
> +        libusb_reset_device(s->dh);
> +        usb_host_attach_kernel(s);
>      }
>
> -    usb_host_release_interfaces(s);
> -    libusb_reset_device(s->dh);
> -    usb_host_attach_kernel(s);
>      libusb_close(s->dh);
>      s->dh = NULL;
>      s->dev = NULL;

[Severity: Medium]
The cpr_handed_off gate here also skips the QEMU-local steps
usb_host_abort_xfers(), usb_host_iso_free_all() and usb_device_detach(),
not only the reset, release and rebind that must not run under the target.

If usb_host_close() is reached while the flag is stale, for example via
usb_host_nodev_bh() or usb_host_auto_check(), s->dh and s->dev are still set
to NULL.  Can that leave a guest-attached device with no libusb handle?

Should the gate cover only the device-affecting calls (release, reset,
rebind)?

[ ... ]

> @@ -1487,6 +1528,14 @@ static void usb_host_realize(USBDevice *udev, Error **errp)
> +        if (cpr_is_incoming()) {
> +            /*
> +             * The source still owns the shared fd until it hands off.  Don't
> +             * reap URBs on it yet, or we would steal the source's in-flight
> +             * completions; .post_load() restarts events once we take over.
> +             */
> +            usb_host_stop_events();
> +        }

[Severity: Medium]
Can this stop event reaping permanently for a device that is not migrated?

cpr_is_incoming() is "incoming_mode != MIG_MODE_NONE", and incoming_mode
does not appear to be reset after the migration completes.

If a usb-host device with hostdevice= is hotplugged on the target after the
CPR migration, usb_host_realize() takes this branch and calls
usb_host_stop_events(), which clears the pollfd notifiers and fd handlers on
the shared libusb ctx.

usb_host_post_load() is the only place that restarts events, and a hotplugged
device has no vmstate section to load.  Would URB completions then stop being
reaped for that device and for every other usb-host device?

[Severity: Low]
This isn't a functional problem, but the commit message and the code differ
in a few places:

    The target doesn't reap until it takes over: it stops events right
    after opening the FD in .realize() and restarts in .post_load().

The condition used is cpr_is_incoming(), which is true for any incoming CPR
mode, including cpr-exec, where the fd is not shared.

    The source stops reaping in .pre_save() and, since it stays alive,
    restarts reaping from the vm-state handler's resume branch on failure.

The stop happens in usb_host_cpr_quiesce(), which usb_host_cpr_vm_state()
calls on the FINISH_MIGRATE transition.  usb_host_pre_save() reaches it only
as a fallback for an already-paused VM.  Could the commit message be updated
to describe this?

[ ... ]

> @@ -1550,6 +1599,12 @@ static void usb_host_unrealize(USBDevice *udev)
> +    /*
> +     * An explicit device_del means we still own the device (a successful
> +     * cpr-transfer handoff exits the process instead), so clear the handoff
> +     * flag to force a full reset/release in usb_host_close().
> +     */
> +    s->cpr_handed_off = false;
>      usb_host_close(s);
>      migrate_del_blocker(&s->cpr_blocker);
>      if (s->cpr_vmse) {
>          qemu_del_vm_change_state_handler(s->cpr_vmse);
>      }

[Severity: Medium]
Does this path leave event reaping stopped for the other usb-host devices?

Suppose usb_host_cpr_quiesce() has called usb_host_stop_events() on the whole
ctx, and the migration then fails or is cancelled while the VM is paused.  No
running transition occurs, so usb_host_cpr_reissue() never calls
usb_host_start_events().

A later device_del reaches usb_host_unrealize(), which sets
cpr_handed_off = false and removes cpr_vmse without calling
usb_host_start_events().  usb_host_init() returns early once ctx exists, so
nothing else restarts reaping.

Can transfers on the other usb-host devices (hostdevice=, autoscan or
bus/addr) hang here, since the stop applies to the shared context?  The
commit message says only one hostdevice= device is supported, but nothing in
the code appears to enforce that.

[Severity: Low]
Can this unconditional clearing of cpr_handed_off cause a reset of a device
the target already owns?

The comment assumes that unrealize means the source still owns the device.
During the handoff window the source has cpr_handed_off = true and the target
may already be reaping.  If a device_del or guest eject reaches
usb_host_unrealize() then, usb_host_close() runs usb_host_abort_xfers(),
usb_host_release_interfaces(), libusb_reset_device() and
usb_host_attach_kernel() on the shared fd.

Is it intended that this disrupts the target's session?

[ ... ]

> @@ -2033,6 +2088,7 @@ static int usb_host_post_load(void *opaque, int version_id)
>      if (cpr_is_incoming()) {
> +        usb_host_start_events();
>          return 0;
>      }

This is the only restart point on the target, and it runs only for devices
whose vmstate section is loaded, as noted in the usb_host_realize() comment
above.

-- 
Sashiko AI review · https://sashiko.vzint.dev/#/patchset/20261006-usb-host_v3-v7-0-044f668789a1@virtuozzo.com?part=10

  reply	other threads:[~2026-10-06 15:52 UTC|newest]

Thread overview: 29+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-06 15:35 [PATCH QEMU HCI-8.0 v7 00/14] usb-host: support CPR migration Andrey Drobyshev
2026-10-06 15:35 ` [PATCH QEMU HCI-8.0 v7 01/14] usb-host: don't leak hostdev FD on open failure #VSTOR-137800 Andrey Drobyshev
2026-10-06 15:38   ` no-reply
2026-10-06 15:35 ` [PATCH QEMU HCI-8.0 v7 02/14] usb-host: don't leak config descriptor on claim " Andrey Drobyshev
2026-10-06 15:37   ` no-reply
2026-10-06 15:35 ` [PATCH QEMU HCI-8.0 v7 03/14] usb-host: add migration blocker for CPR modes #VSTOR-137800 Andrey Drobyshev
2026-10-06 15:38   ` no-reply
2026-10-06 15:35 ` [PATCH QEMU HCI-8.0 v7 04/14] usb-host: preserve hostdev FD during CPR migration #VSTOR-137800 Andrey Drobyshev
2026-10-06 15:41   ` no-reply
2026-10-06 15:35 ` [PATCH QEMU HCI-8.0 v7 05/14] usb-host: factor out usb_host_reap_xfers() #VSTOR-137800 Andrey Drobyshev
2026-10-06 15:39   ` no-reply
2026-10-06 15:35 ` [PATCH QEMU HCI-8.0 v7 06/14] usb-host: drain in-flight URBs across CPR #VSTOR-137800 Andrey Drobyshev
2026-10-06 15:45   ` no-reply
2026-10-06 15:35 ` [PATCH QEMU HCI-8.0 v7 07/14] usb-host: re-issue drained URBs if CPR is aborted #VSTOR-137800 Andrey Drobyshev
2026-10-06 15:46   ` no-reply
2026-10-06 15:35 ` [PATCH QEMU HCI-8.0 v7 08/14] usb-host: skip product-string read on CPR incoming #VSTOR-137800 Andrey Drobyshev
2026-10-06 15:47   ` no-reply
2026-10-06 15:36 ` [PATCH QEMU HCI-8.0 v7 09/14] usb-host: factor out usb_host_start_events() #VSTOR-137800 Andrey Drobyshev
2026-10-06 15:46   ` no-reply
2026-10-06 15:36 ` [PATCH QEMU HCI-8.0 v7 10/14] usb-host: hand off the device across cpr-transfer #VSTOR-137800 Andrey Drobyshev
2026-10-06 15:52   ` no-reply [this message]
2026-10-06 15:36 ` [PATCH QEMU HCI-8.0 v7 11/14] usb: migrate the interface altsetting #VSTOR-137800 Andrey Drobyshev
2026-10-06 15:51   ` no-reply
2026-10-06 15:36 ` [PATCH QEMU HCI-8.0 v7 12/14] usb-host: reconfigure endpoints on CPR incoming #VSTOR-137800 Andrey Drobyshev
2026-10-06 15:57   ` no-reply
2026-10-06 15:36 ` [PATCH QEMU HCI-8.0 v7 13/14] hcd-xhci: resync isochronous endpoints after CPR #VSTOR-137800 Andrey Drobyshev
2026-10-06 15:56   ` no-reply
2026-10-06 15:36 ` [PATCH QEMU HCI-8.0 v7 14/14] usb-host: make CPR migration blocker conditional #VSTOR-137800 Andrey Drobyshev
2026-10-06 15:57   ` no-reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=010701a111ea5277-f87b3df8-9b09-4ef0-9041-fa9d05c7d972-000000@eu-central-1.amazonses.com \
    --to=no-reply@sashiko.virtuozzo.com \
    --cc=andrey.drobyshev@virtuozzo.com \
    --cc=den@openvz.org \
    --cc=svt-core@virtuozzo.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox