[PATCH 11/27] hw/usb/dev-smartcard-reader: apply backpressure to bulk-out
Marc-André Lureau <[email protected]> Wed, 05 Aug 2026 15:51:01 +0400
| Newsgroups | gmane.comp.emulators.qemu |
|---|---|
| Message-ID | <[email protected]> |
A guest can send bulk-out commands faster than the card backend produces
responses. Each XfrBlock adds a pending answer slot, and other commands
immediately allocate a bulk_in_pending entry. Once all
BULK_IN_PENDING_NUM slots are full, ccid_reserve_recv_buf() returns NULL
and responses are silently dropped.
NAK the bulk-out endpoint at message boundaries when bulk_in_pending_num
+ pending_answers_num >= BULK_IN_PENDING_NUM, so the guest retries
later, giving the response pipeline time to drain.
Change ccid_write_data_block_answer() to peek the next answer before
attempting to write the data block: if the bulk-in ring is full, the
pending answer is preserved for retry rather than being consumed and
lost. Update ccid_flush_pending_answers() to break on failure so it does
not loop forever when the ring is full.
Fixes: 367071447ec5 ("usb-ccid: add CCID bus")
Signed-off-by: Marc-André Lureau <[email protected]>
---
hw/usb/dev-smartcard-reader.c | 38 ++++++++++++++++++++++++++++++--------
1 file changed, 30 insertions(+), 8 deletions(-)
diff --git a/hw/usb/dev-smartcard-reader.c b/hw/usb/dev-smartcard-reader.c
index 12c82c8f6458..279fef03652b 100644
--- a/hw/usb/dev-smartcard-reader.c
+++ b/hw/usb/dev-smartcard-reader.c
@@ -765,13 +765,13 @@ static void ccid_write_parameters(USBCCIDState *s, CCID_Header *recv)
usb_wakeup(s->bulk, 0);
}
-static void ccid_write_data_block(USBCCIDState *s, uint8_t slot, uint8_t seq,
+static bool ccid_write_data_block(USBCCIDState *s, uint8_t slot, uint8_t seq,
const uint8_t *data, uint32_t len)
{
CCID_DataBlock *p = ccid_reserve_recv_buf(s, sizeof(*p) + len);
if (p == NULL) {
- return;
+ return false;
}
p->b.hdr.bMessageType = CCID_MESSAGE_TYPE_RDR_to_PC_DataBlock;
p->b.hdr.dwLength = cpu_to_le32(len);
@@ -788,6 +788,7 @@ static void ccid_write_data_block(USBCCIDState *s, uint8_t slot, uint8_t seq,
}
ccid_reset_error_status(s);
usb_wakeup(s->bulk, 0);
+ return true;
}
static void ccid_report_error_failed(USBCCIDState *s, uint8_t error)
@@ -796,19 +797,26 @@ static void ccid_report_error_failed(USBCCIDState *s, uint8_t error)
s->bError = error;
}
-static void ccid_write_data_block_answer(USBCCIDState *s,
- const uint8_t *data, uint32_t len)
+static Answer *ccid_peek_next_answer(USBCCIDState *s);
+
+static bool ccid_write_data_block_answer(USBCCIDState *s,
+ const uint8_t *data, uint32_t len)
{
- uint8_t seq;
uint8_t slot;
+ uint8_t seq;
+ Answer *answer;
if (!ccid_has_pending_answers(s)) {
DPRINTF(s, D_WARN, "error: no pending answer to return to guest\n");
ccid_report_error_failed(s, ERROR_ICC_MUTE);
- return;
+ return false;
+ }
+ answer = ccid_peek_next_answer(s);
+ if (!ccid_write_data_block(s, answer->slot, answer->seq, data, len)) {
+ return false;
}
ccid_remove_pending_answer(s, &slot, &seq);
- ccid_write_data_block(s, slot, seq, data, len);
+ return true;
}
static uint8_t atr_get_protocol_num(const uint8_t *atr, uint32_t len)
@@ -1008,6 +1016,18 @@ static void ccid_handle_bulk_out(USBCCIDState *s, USBPacket *p)
CCID_Header *ccid_header;
uint32_t payload_len;
+ /*
+ * Every accepted command either queues a response immediately or commits a
+ * slot to a later APDU response. Apply backpressure before consuming the
+ * first packet when all response slots are committed.
+ */
+ if (s->bulk_out_pos == 0 && /* start of message */
+ s->bulk_in_pending_num + s->pending_answers_num >=
+ BULK_IN_PENDING_NUM) {
+ p->status = USB_RET_NAK;
+ return;
+ }
+
if (p->iov.size + s->bulk_out_pos > BULK_OUT_DATA_SIZE) {
goto err;
}
@@ -1190,7 +1210,9 @@ static void ccid_unrealize(USBDevice *dev)
static void ccid_flush_pending_answers(USBCCIDState *s)
{
while (ccid_has_pending_answers(s)) {
- ccid_write_data_block_answer(s, NULL, 0);
+ if (!ccid_write_data_block_answer(s, NULL, 0)) {
+ break;
+ }
}
}
--
2.55.0