All Virtuozzo development lists (kernel + QEMU)
 help / color / mirror / Atom feed
From: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>
To: svt-core@virtuozzo.com
Cc: den@openvz.org, andrey.drobyshev@virtuozzo.com
Subject: [PATCH QEMU HCI-8.0 v7 07/14] usb-host: re-issue drained URBs if CPR is aborted #VSTOR-137800
Date: Tue, 06 Oct 2026 18:35:58 +0300	[thread overview]
Message-ID: <20261006-usb-host_v3-v7-7-044f668789a1@virtuozzo.com> (raw)
In-Reply-To: <20261006-usb-host_v3-v7-0-044f668789a1@virtuozzo.com>

If CPR fails, the source VM resumes with the drained packets still owned
by the host controller as in-flight, and nothing left to complete them.

Remember the packets suppressed during the drain on a list, and re-issue
them on the preserved fd from the vm-state handler's resume branch -- it
runs on any resume but only acts once we have actually drained
(cpr_drained).  Control transfers cannot be reconstructed from the packet
alone, so complete those as errors and let the guest driver retry.

A re-issued transfer is replayed from the start of the TD, so a partial
transfer loses its progress -- an OUT fragment already consumed by the
device may be sent twice.  Only the paused-VM .pre_save() fallback queues
partial transfers for re-issue; the normal running-VM drain delivers them
as short transfers and is unaffected.

Signed-off-by: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>
---
 hw/usb/host-libusb.c | 79 ++++++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 74 insertions(+), 5 deletions(-)

diff --git a/hw/usb/host-libusb.c b/hw/usb/host-libusb.c
index e631b683ed1..d90117e4292 100644
--- a/hw/usb/host-libusb.c
+++ b/hw/usb/host-libusb.c
@@ -124,6 +124,7 @@ struct USBHostDevice {
     /* CPR migration */
     Error                            *cpr_blocker;
     VMChangeStateEntry               *cpr_vmse;
+    GSList                           *cpr_inflight;
     bool                             cpr_drained;
     bool                             cpr_replay_all;
 
@@ -169,6 +170,7 @@ static void usb_host_release_interfaces(USBHostDevice *s);
 static void usb_host_nodev(USBHostDevice *s);
 static void usb_host_detach_kernel(USBHostDevice *s);
 static void usb_host_attach_kernel(USBHostDevice *s);
+static void usb_host_handle_data(USBDevice *udev, USBPacket *p);
 
 /* ------------------------------------------------------------------------ */
 
@@ -433,9 +435,10 @@ static USBHostRequest *usb_host_req_find(USBHostDevice *s, USBPacket *p)
 /*
  * Classify a reap during the CPR drain (r->p is still set).  The paused-VM
  * fallback (cpr_replay_all) can't deliver completions, so it suppresses every
- * reap for the target to replay; otherwise one that moved no data is suppressed
- * and one that moved some is kept as a short transfer.  Returns true if
- * suppressed, so the caller frees the request.
+ * reap; otherwise one that moved no data is suppressed and one that moved some
+ * is kept as a short transfer.  Suppressed packets are queued on cpr_inflight
+ * for the target to replay.  Returns true if suppressed, so the caller frees
+ * the request.
  */
 static bool usb_host_req_cpr_replay(USBHostRequest *r,
                                     struct libusb_transfer *xfer)
@@ -450,6 +453,9 @@ static bool usb_host_req_cpr_replay(USBHostRequest *r,
             return false;
         }
     }
+    if (r->p->state == USB_PACKET_ASYNC) {
+        r->host->cpr_inflight = g_slist_append(r->host->cpr_inflight, r->p);
+    }
     return true;
 }
 
@@ -1271,16 +1277,74 @@ static int usb_host_cpr_quiesce(USBHostDevice *s, MigMode mode)
     return 0;
 }
 
+/*
+ * If CPR is aborted the source resumes with the drained packets still owned
+ * by the controller as in-flight; re-issue them on the preserved fd.  Control
+ * transfers can't be reconstructed from the packet, so error those out.
+ */
+static void usb_host_cpr_reissue(USBHostDevice *s)
+{
+    USBDevice *udev = USB_DEVICE(s);
+    GSList *it;
+    USBPacket *p;
+
+    if (!s->cpr_drained) {
+        return;
+    }
+
+    for (it = s->cpr_inflight; it; it = it->next) {
+        p = it->data;
+        /*
+         * Replay from the start of the TD.  A transfer that already moved some
+         * data is re-run in full, so an OUT fragment the device consumed may be
+         * sent twice.  Only the paused-VM pre_save fallback queues such partial
+         * transfers here; the normal drain delivers them as short transfers.
+         */
+        p->actual_length = 0;
+        if (p->ep->nr == 0) {
+            /* control endpoint: can't be reconstructed, so error it out */
+            p->status = USB_RET_IOERROR;
+            usb_generic_async_ctrl_complete(udev, p);
+        } else {
+            usb_host_handle_data(udev, p);
+            if (p->status != USB_RET_ASYNC) {
+                /* completed synchronously with an error */
+                if (usb_host_use_combining(p->ep)) {
+                    usb_combined_input_packet_complete(udev, p);
+                } else {
+                    usb_packet_complete(udev, p);
+                }
+            }
+        }
+    }
+    g_slist_free(s->cpr_inflight);
+    s->cpr_inflight = NULL;
+    s->cpr_replay_all = false;
+    s->cpr_drained = false;
+}
+
 /*
  * Run the CPR quiesce from the vm-state handler, on the stop-for-CPR
- * transition before the device/irqchip snapshot.
+ * transition before the device/irqchip snapshot, and re-issue the drained
+ * packets on resume if CPR was aborted.
  */
 static void usb_host_cpr_vm_state(void *opaque, bool running, RunState state)
 {
     USBHostDevice *s = opaque;
     MigMode mode;
 
-    if (running || !s->dh || state != RUN_STATE_FINISH_MIGRATE) {
+    if (running) {
+        /*
+         * Resume: re-issue drained packets and reset state, even if the
+         * device was lost between the drain and now (dh == NULL).
+         */
+        usb_host_cpr_reissue(s);
+        return;
+    }
+    if (!s->dh) {
+        return;
+    }
+    if (state != RUN_STATE_FINISH_MIGRATE) {
         return;
     }
     mode = migrate_mode();
@@ -1486,6 +1550,8 @@ static void usb_host_unrealize(USBDevice *udev)
     if (s->cpr_vmse) {
         qemu_del_vm_change_state_handler(s->cpr_vmse);
     }
+    g_slist_free(s->cpr_inflight);
+    s->cpr_inflight = NULL;
 }
 
 static void usb_host_cancel_packet(USBDevice *udev, USBPacket *p)
@@ -1493,6 +1559,9 @@ static void usb_host_cancel_packet(USBDevice *udev, USBPacket *p)
     USBHostDevice *s = USB_HOST_DEVICE(udev);
     USBHostRequest *r;
 
+    /* Packet is being cancelled -> it mustn't be re-issued on failed CPR */
+    s->cpr_inflight = g_slist_remove(s->cpr_inflight, p);
+
     if (p->combined) {
         usb_combined_packet_cancel(udev, p);
         return;

-- 
2.47.1


  parent reply	other threads:[~2026-10-06 15:36 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 ` Andrey Drobyshev [this message]
2026-10-06 15:46   ` [PATCH QEMU HCI-8.0 v7 07/14] usb-host: re-issue drained URBs if CPR is aborted #VSTOR-137800 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
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=20261006-usb-host_v3-v7-7-044f668789a1@virtuozzo.com \
    --to=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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.