[PATCH 17/27] hw/usb/dev-smartcard-reader: fix pending queue migration

Marc-AndrĂ© Lureau <[email protected]> Wed, 05 Aug 2026 15:51:07 +0400
Newsgroups gmane.comp.emulators.qemu
Message-ID <[email protected]>
The original vmstate migrated the bulk_in_pending and pending_answers
arrays but not the pending_answers queue indices (pending_answers_start,
pending_answers_end). This made migration silently lose in-flight
smartcard responses.

Add a vmstate subsection "usb-ccid/pending-answers" that migrates the
missing pending_answers indices. A compat property
x-migrate-pending-answers (default on, off for <= 11.0) gates the
subsection so older machine types keep the old behavior.

Populate pending_answers_num in pre_save so post_load can reconstruct
start/end when loading from an older source that lacks the subsection.

When the subsection is absent, post_load derives pending_answers
start/end from the legacy count. Validate all wire-sourced indices
and bulk-in entry bounds on load.

Signed-off-by: Marc-AndrĂ© Lureau <[email protected]>
---
 hw/core/machine.c             |  1 +
 hw/usb/dev-smartcard-reader.c | 70 ++++++++++++++++++++++++++++++++++++++++---
 2 files changed, 67 insertions(+), 4 deletions(-)

diff --git a/hw/core/machine.c b/hw/core/machine.c
index 01e50ebe05e0..28842f53ae4f 100644
--- a/hw/core/machine.c
+++ b/hw/core/machine.c
@@ -52,6 +52,7 @@ GlobalProperty hw_compat_11_0[] = {
     { TYPE_ARM_SMMUV3, "oas", "44" },
     { "migration", "switchover-ack-legacy", "on" },
     { "usb-ccid", "x-accurate-message-length", "off" },
+    { "usb-ccid", "x-migrate-pending-answers", "off" },
 };
 const size_t hw_compat_11_0_len = G_N_ELEMENTS(hw_compat_11_0);
 
diff --git a/hw/usb/dev-smartcard-reader.c b/hw/usb/dev-smartcard-reader.c
index e3c86cb90e6c..5ca50c111217 100644
--- a/hw/usb/dev-smartcard-reader.c
+++ b/hw/usb/dev-smartcard-reader.c
@@ -312,6 +312,8 @@ struct USBCCIDState {
     uint8_t  notify_slot_change;
     uint8_t  debug;
     bool accurate_message_length;
+    bool migrate_pending_answers;
+    bool pending_answers_loaded;
 };
 
 static uint32_t ccid_bulk_in_pending_num(USBCCIDState *s)
@@ -1409,16 +1411,35 @@ static void ccid_realize(USBDevice *dev, Error **errp)
     s->debug = parse_debug_env("QEMU_CCID_DEBUG", D_VERBOSE, s->debug);
 }
 
+static int ccid_pre_load(void *opaque)
+{
+    USBCCIDState *s = opaque;
+
+    s->pending_answers_loaded = false;
+    return 0;
+}
+
 static int ccid_post_load(void *opaque, int version_id)
 {
     USBCCIDState *s = opaque;
     int i;
 
-    if (s->bulk_in_pending_end - s->bulk_in_pending_start > BULK_IN_PENDING_NUM) {
+    if (!s->pending_answers_loaded) {
+        /*
+         * Version 1 did not migrate the pending_answers[] queue indices.
+         * The historical interpretation of pending_answers[] as starting at
+         * element zero.
+         */
+        if (s->pending_answers_num > PENDING_ANSWERS_NUM) {
+            return -EINVAL;
+        }
+        s->pending_answers_start = 0;
+        s->pending_answers_end = s->pending_answers_num;
+    } else if (s->pending_answers_end - s->pending_answers_start > PENDING_ANSWERS_NUM) {
         return -EINVAL;
     }
 
-    if (s->pending_answers_num > PENDING_ANSWERS_NUM) {
+    if (s->bulk_in_pending_end - s->bulk_in_pending_start > BULK_IN_PENDING_NUM) {
         return -EINVAL;
     }
 
@@ -1447,12 +1468,14 @@ static bool ccid_pre_save(void *opaque, Error **errp)
 {
     USBCCIDState *s = opaque;
 
-    if (ccid_pending_answers_num(s) || ccid_bulk_in_pending_num(s)) {
+    if (!s->migrate_pending_answers &&
+        (ccid_pending_answers_num(s) || ccid_bulk_in_pending_num(s))) {
         error_setg(errp, "usb-ccid has pending queue state which cannot be "
                    "migrated safely");
         return false;
     }
 
+    s->pending_answers_num = ccid_pending_answers_num(s);
     s->state_vmstate = s->dev.state;
 
     return true;
@@ -1493,10 +1516,43 @@ static const VMStateDescription usb_device_vmstate = {
     }
 };
 
+static bool ccid_pending_answers_needed(void *opaque)
+{
+    USBCCIDState *s = opaque;
+
+    if (!s->migrate_pending_answers) {
+        return false;
+    }
+
+    return ccid_pending_answers_num(s) > 0;
+}
+
+static int ccid_pending_answers_post_load(void *opaque, int version_id)
+{
+    USBCCIDState *s = opaque;
+
+    s->pending_answers_loaded = true;
+    return 0;
+}
+
+static const VMStateDescription ccid_pending_answers_vmstate = {
+    .name = "usb-ccid/pending-answers",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .needed = ccid_pending_answers_needed,
+    .post_load = ccid_pending_answers_post_load,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT32(pending_answers_start, USBCCIDState),
+        VMSTATE_UINT32(pending_answers_end, USBCCIDState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
 static const VMStateDescription ccid_vmstate = {
     .name = "usb-ccid",
     .version_id = 1,
     .minimum_version_id = 1,
+    .pre_load = ccid_pre_load,
     .post_load = ccid_post_load,
     .pre_save_errp = ccid_pre_save,
     .fields = (const VMStateField[]) {
@@ -1523,13 +1579,19 @@ static const VMStateDescription ccid_vmstate = {
         VMSTATE_UNUSED(1), /* was migration_state */
         VMSTATE_UINT32(state_vmstate, USBCCIDState),
         VMSTATE_END_OF_LIST()
-    }
+    },
+    .subsections = (const VMStateDescription * const []) {
+        &ccid_pending_answers_vmstate,
+        NULL
+    },
 };
 
 static const Property ccid_properties[] = {
     DEFINE_PROP_UINT8("debug", USBCCIDState, debug, 0),
     DEFINE_PROP_BOOL("x-accurate-message-length", USBCCIDState,
                      accurate_message_length, true),
+    DEFINE_PROP_BOOL("x-migrate-pending-answers", USBCCIDState,
+                     migrate_pending_answers, true),
 };
 
 static void ccid_class_initfn(ObjectClass *klass, const void *data)

-- 
2.55.0