All of lore.kernel.org
 help / color / mirror / Atom feed
From: Jamin Lin <jamin_lin@aspeedtech.com>
To: "berrange@redhat.com" <berrange@redhat.com>,
	"junjie.cao@linux.dev" <junjie.cao@linux.dev>,
	"kraxel@redhat.com" <kraxel@redhat.com>,
	"marcandre.lureau@redhat.com" <marcandre.lureau@redhat.com>,
	"clg@kaod.org" <clg@kaod.org>,
	"junjie.cao@intel.com" <junjie.cao@intel.com>,
	"Pierrick Bouvier" <pierrick.bouvier@oss.qualcomm.com>,
	"Peter Maydell" <peter.maydell@linaro.org>,
	"Kane Chen" <kane_chen@aspeedtech.com>,
	"Philippe Mathieu-Daudé" <philmd@oss.qualcomm.com>,
	"Tyrone Ting" <kfting@nuvoton.com>,
	"Hao Wu" <wuhaotsh@google.com>, "Zhao Liu" <zhao1.liu@intel.com>,
	"Richard Henderson" <richard.henderson@linaro.org>,
	"Helge Deller" <deller@gmx.de>,
	"Stefano Stabellini" <sstabellini@kernel.org>,
	"Anthony PERARD" <anthony@xenproject.org>,
	"Edgar E. Iglesias" <edgar.iglesias@gmail.com>,
	"open list:All patches CC here" <qemu-devel@nongnu.org>,
	"open list:ASPEED BMCs" <qemu-arm@nongnu.org>,
	"open list:X86 Xen CPUs" <xen-devel@lists.xenproject.org>
Cc: Jamin Lin <jamin_lin@aspeedtech.com>, Troy Lee <troy_lee@aspeedtech.com>
Subject: [PATCH v9 18/19] hw/usb/redirect-server: Implement isochronous OUT transfers
Date: Tue, 10 Nov 2026 07:28:45 +0000	[thread overview]
Message-ID: <20261110072818.2883349-19-jamin_lin@aspeedtech.com> (raw)
In-Reply-To: <20261110072818.2883349-1-jamin_lin@aspeedtech.com>

The protocol has three callbacks for isochronous transfers:
start_iso_stream_func, stop_iso_stream_func and iso_packet_func.
Isochronous has no capability bit, and the parser calls them without
checking, so a server cannot leave them out.

Implement the three for an OUT endpoint. Endpoint 0 is control, so
refuse it. The host sends a packet for every OUT transfer. It sends
nothing for an IN transfer, so refuse an IN endpoint with a stall.
Only a stall makes the host close the stream. Any other status leaves
it open on the host side.

An isochronous packet has no id, and the host wants no answer. Report
a failure with an iso_stream_status. A broken endpoint fails every
frame, so send at most one report a second. A stall does not wait. A
stall ends the stream, and the host has to hear it.

With no id, a cancel never matches an isochronous packet. A NAK does
not park it for a retry either.

Reported-by: Junjie Cao <junjie.cao@linux.dev>
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Junjie Cao <junjie.cao@linux.dev>
Tested-by: Junjie Cao <junjie.cao@linux.dev>
---
 hw/usb/redirect-server.c | 161 ++++++++++++++++++++++++++++++++++++++-
 hw/usb/trace-events      |   5 ++
 2 files changed, 162 insertions(+), 4 deletions(-)

diff --git a/hw/usb/redirect-server.c b/hw/usb/redirect-server.c
index 2abbec74c6..65fc9788cd 100644
--- a/hw/usb/redirect-server.c
+++ b/hw/usb/redirect-server.c
@@ -126,6 +126,9 @@ OBJECT_DECLARE_SIMPLE_TYPE(USBRedirServer, USB_REDIR_SERVER)
 /* Buffer size for an interrupt IN endpoint before its descriptor is seen. */
 #define USBREDIR_SERVER_INTR_STREAM_DEFAULT_LEN 64
 
+/* The shortest time between two failure reports. */
+#define USBREDIR_SERVER_ISO_STREAM_STATUS_MS 1000
+
 /*
  * The steps to read the descriptors, in order:
  *
@@ -157,6 +160,7 @@ typedef enum {
     USBREDIR_SERVER_BULK,
     USBREDIR_SERVER_INTERRUPT,
     USBREDIR_SERVER_INTR_STREAM,
+    USBREDIR_SERVER_ISO_STREAM,
 } USBRedirServerPktType;
 
 /* Which message answers the host when a control transfer ends. */
@@ -183,6 +187,7 @@ typedef struct USBRedirServerPkt {
     struct usb_redir_interrupt_packet_header intr_hdr;
     struct usb_redir_control_packet_header ctrl_hdr;
     struct usb_redir_bulk_packet_header bulk_hdr;
+    struct usb_redir_iso_packet_header iso_hdr;
 
     /*
      * The IOV points here. IN data lands in it, OUT data is copied in.
@@ -224,6 +229,13 @@ typedef struct USBRedirServerEp {
     QEMUBH *intr_stream_bh;
     bool intr_streaming;
 
+    /*
+     * Isochronous OUT streaming. The next failure report goes out at
+     * this time.
+     */
+    int64_t iso_status_next_ns;
+    bool iso_streaming;
+
     uint16_t max_packet;
     uint8_t interval;
     uint8_t iface;
@@ -1280,6 +1292,47 @@ static void usbredir_server_intr_stream_complete(USBRedirServer *s,
     }
 }
 
+/*
+ * Isochronous transfers have no answer and no retry, so a good packet
+ * needs nothing back.
+ *
+ * A broken endpoint fails every frame, so we do not report every
+ * failure.
+ */
+static void usbredir_server_iso_stream_complete(USBRedirServer *s,
+                                                USBRedirServerPkt *rp)
+{
+    struct usb_redir_iso_stream_status_header st;
+    USBRedirServerEp *ep = &s->ep[rp->ep_idx];
+    USBPacket *p = &rp->pkt;
+    int64_t now_ns;
+
+    trace_usbredir_server_iso_stream_complete(rp->iso_hdr.endpoint, p->status,
+                                              p->actual_length);
+
+    switch (p->status) {
+    case USB_RET_SUCCESS:
+        return;
+    case USB_RET_STALL:
+        /* The stream ends here, so the host has to hear this one. */
+        ep->iso_streaming = false;
+        break;
+    default:
+        now_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+        if (now_ns < ep->iso_status_next_ns) {
+            return;
+        }
+        ep->iso_status_next_ns = now_ns +
+            (int64_t)USBREDIR_SERVER_ISO_STREAM_STATUS_MS * SCALE_MS;
+        break;
+    }
+
+    st.endpoint = rp->iso_hdr.endpoint;
+    st.status = usbredir_server_status(p->status);
+    usbredirparser_send_iso_stream_status(s->parser, 0, &st);
+    usbredirparser_do_write(s->parser);
+}
+
 /*
  * USB port ops
  */
@@ -1380,6 +1433,9 @@ static void usbredir_server_packet_complete(USBPort *port, USBPacket *p)
     case USBREDIR_SERVER_INTR_STREAM:
         usbredir_server_intr_stream_complete(s, rp);
         break;
+    case USBREDIR_SERVER_ISO_STREAM:
+        usbredir_server_iso_stream_complete(s, rp);
+        break;
     default:
         g_assert_not_reached();
     }
@@ -1419,11 +1475,12 @@ static USBRedirServerAsk usbredir_server_ask_device(USBRedirServer *s,
 
     /*
      * Keep the request and send it again when the device wakes the
-     * endpoint. A streamed request asks again by itself, with its own
-     * timer.
+     * endpoint. A streamed request does not wait here. The interrupt
+     * stream has a timer of its own, and isochronous never retries.
      */
     if (rp->pkt.status == USB_RET_NAK &&
-        rp->type != USBREDIR_SERVER_INTR_STREAM) {
+        rp->type != USBREDIR_SERVER_INTR_STREAM &&
+        rp->type != USBREDIR_SERVER_ISO_STREAM) {
         trace_usbredir_server_nak_park(rp->redir_id, rp->ep_idx);
         QTAILQ_INSERT_HEAD(&ep->waiting, rp, waiting_link);
         timer_mod(ep->retry, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
@@ -2081,6 +2138,93 @@ static void usbredir_server_stop_interrupt_receiving(void *priv, uint64_t id,
     usbredir_server_intr_stream_cancel(s, ep_nr);
 }
 
+static void usbredir_server_start_iso_stream(void *priv, uint64_t id,
+    struct usb_redir_start_iso_stream_header *hdr)
+{
+    int idx = usbredir_server_ep_index(hdr->endpoint);
+    struct usb_redir_iso_stream_status_header st = {
+        .endpoint = hdr->endpoint,
+    };
+    USBRedirServer *s = priv;
+
+    st.status = usbredir_server_ep_status(s, hdr->endpoint,
+                                          USB_ENDPOINT_XFER_ISOC);
+
+    /*
+     * Only an OUT stream works here. Only a stall makes the host
+     * close the stream.
+     */
+    if (hdr->endpoint & USB_DIR_IN) {
+        st.status = usb_redir_stall;
+    }
+
+    if (st.status == usb_redir_success) {
+        s->ep[idx].iso_streaming = true;
+        s->ep[idx].iso_status_next_ns = 0;
+    }
+
+    trace_usbredir_server_iso_stream_start(hdr->endpoint, hdr->pkts_per_urb,
+                                           hdr->no_urbs, st.status);
+    usbredirparser_send_iso_stream_status(s->parser, id, &st);
+    usbredirparser_do_write(s->parser);
+}
+
+static void usbredir_server_stop_iso_stream(void *priv, uint64_t id,
+    struct usb_redir_stop_iso_stream_header *hdr)
+{
+    int idx = usbredir_server_ep_index(hdr->endpoint);
+    int ep_nr = hdr->endpoint & 0x0f;
+    USBRedirServer *s = priv;
+
+    trace_usbredir_server_iso_stream_stop(hdr->endpoint);
+
+    /* Endpoint 0 is control, and an IN endpoint never streams. */
+    if (ep_nr == 0 || (hdr->endpoint & USB_DIR_IN)) {
+        return;
+    }
+
+    s->ep[idx].iso_streaming = false;
+}
+
+/* An isochronous packet has no id, and the host wants no answer. */
+static void usbredir_server_iso_packet(void *priv, uint64_t id,
+    struct usb_redir_iso_packet_header *hdr, uint8_t *data, int data_len)
+{
+    int idx = usbredir_server_ep_index(hdr->endpoint);
+    USBDevice *device = usbredir_server_device(priv);
+    int ep_nr = hdr->endpoint & 0x0f;
+    USBRedirServer *s = priv;
+    USBRedirServerPkt *rp;
+    USBEndpoint *ep;
+    uint8_t status;
+
+    status = usbredir_server_ep_status(s, hdr->endpoint,
+                                       USB_ENDPOINT_XFER_ISOC);
+
+    if (status != usb_redir_success || !s->ep[idx].iso_streaming) {
+        trace_usbredir_server_iso_stream_dropped(hdr->endpoint, data_len);
+        usbredirparser_free_packet_data(s->parser, data);
+        return;
+    }
+
+    ep = usb_ep_get(device, USB_TOKEN_OUT, ep_nr);
+    rp = usbredir_server_pkt_alloc(data_len, idx);
+    rp->type = USBREDIR_SERVER_ISO_STREAM;
+    rp->iso_hdr = *hdr;
+    rp->redir_id = 0;
+
+    usb_packet_setup(&rp->pkt, USB_TOKEN_OUT, ep, 0, s->next_id++,
+                     false, false);
+    if (data_len > 0) {
+        memcpy(rp->data, data, data_len);
+    }
+    usb_packet_addbuf(&rp->pkt, rp->data, data_len);
+
+    trace_usbredir_server_iso_stream_packet(hdr->endpoint, data_len);
+    usbredirparser_free_packet_data(s->parser, data);
+    usbredir_server_submit(s, rp);
+}
+
 static void usbredir_server_alloc_bulk_streams(void *priv, uint64_t id,
     struct usb_redir_alloc_bulk_streams_header *hdr)
 {
@@ -2110,7 +2254,8 @@ static void usbredir_server_cancel_data_packet(void *priv, uint64_t id)
          * A streamed request comes from us, not from the host.
          * The host has no id for it, so a cancel never matches it.
          */
-        if (rp->type == USBREDIR_SERVER_INTR_STREAM) {
+        if (rp->type == USBREDIR_SERVER_INTR_STREAM ||
+            rp->type == USBREDIR_SERVER_ISO_STREAM) {
             continue;
         }
 
@@ -2197,6 +2342,11 @@ static void usbredir_server_stop_transfers(USBRedirServer *s)
         timer_del(s->ep[i].intr_stream_timer);
     }
 
+    for (i = 1; i < USBREDIR_SERVER_EP_IN_BASE; i++) {
+        s->ep[i].iso_streaming = false;
+        s->ep[i].iso_status_next_ns = 0;
+    }
+
     /*
      * The host waits for an answer to each of these. Send cancelled
      * before we free them.
@@ -2249,6 +2399,9 @@ static void usbredir_server_create_parser(USBRedirServer *s)
         usbredir_server_start_interrupt_receiving;
     s->parser->stop_interrupt_receiving_func =
         usbredir_server_stop_interrupt_receiving;
+    s->parser->start_iso_stream_func = usbredir_server_start_iso_stream;
+    s->parser->stop_iso_stream_func = usbredir_server_stop_iso_stream;
+    s->parser->iso_packet_func = usbredir_server_iso_packet;
     s->parser->alloc_bulk_streams_func = usbredir_server_alloc_bulk_streams;
     s->parser->cancel_data_packet_func = usbredir_server_cancel_data_packet;
     s->parser->start_bulk_receiving_func =
diff --git a/hw/usb/trace-events b/hw/usb/trace-events
index 20c910e28a..36a870689f 100644
--- a/hw/usb/trace-events
+++ b/hw/usb/trace-events
@@ -434,3 +434,8 @@ usbredir_server_intr_stream_start(uint8_t ep, int status) "start_interrupt_recei
 usbredir_server_intr_stream_stop(uint8_t ep) "stop_interrupt_receiving ep 0x%02x"
 usbredir_server_intr_stream_park(unsigned ep_nr, int len) "parked IN packet ep %u len %d"
 usbredir_server_intr_stream_complete(unsigned ep_nr, int status, int actual) "ep %u status %d actual %d"
+usbredir_server_iso_stream_start(uint8_t ep, uint8_t pkts, uint8_t urbs, int status) "start_iso_stream ep 0x%02x pkts_per_urb %u no_urbs %u -> status %d"
+usbredir_server_iso_stream_stop(uint8_t ep) "stop_iso_stream ep 0x%02x"
+usbredir_server_iso_stream_packet(uint8_t ep, int len) "iso_packet ep 0x%02x len %d"
+usbredir_server_iso_stream_dropped(uint8_t ep, int len) "iso_packet dropped ep 0x%02x len %d"
+usbredir_server_iso_stream_complete(uint8_t ep, int status, int actual) "iso ep 0x%02x status %d actual %d"
-- 
2.43.0


  parent reply	other threads:[~2026-11-10  7:29 UTC|newest]

Thread overview: 20+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-10  7:28 [PATCH v9 00/19] hw/usb: Add a usbredir server transport Jamin Lin
2026-11-10  7:28 ` [PATCH v9 01/19] hw/usb/bus: Add an auto-hub flag to the USB bus Jamin Lin
2026-11-10  7:28 ` [PATCH v9 02/19] hw/usb/hcd-xhci: Add an auto-hub property and leave it off Jamin Lin
2026-11-10  7:28 ` [PATCH v9 03/19] hw/usb/hcd-ehci: " Jamin Lin
2026-11-10  7:28 ` [PATCH v9 04/19] hw/usb/hcd-uhci: " Jamin Lin
2026-11-10  7:28 ` [PATCH v9 05/19] hw/usb/hcd-ohci: " Jamin Lin
2026-11-10  7:28 ` [PATCH v9 06/19] hw/usb/hcd-dwc2: " Jamin Lin
2026-11-10  7:28 ` [PATCH v9 07/19] hw/usb/xen-usb: Do not insert an automatic hub Jamin Lin
2026-11-10  7:28 ` [PATCH v9 08/19] hw/arm/npcm7xx: Keep the " Jamin Lin
2026-11-10  7:28 ` [PATCH v9 09/19] hw/usb/bus: Turn off the automatic hub by default Jamin Lin
2026-11-10  7:28 ` [PATCH v9 10/19] docs/system/devices/usb: Note the automatic hub change Jamin Lin
2026-11-10  7:28 ` [PATCH v9 11/19] hw/usb/redirect-server: Add a usbredir server device Jamin Lin
2026-11-10  7:28 ` [PATCH v9 12/19] hw/usb/redirect-server: Connect usbredirparser to a chardev Jamin Lin
2026-11-10  7:28 ` [PATCH v9 13/19] hw/usb/redirect-server: Announce the exported device Jamin Lin
2026-11-10  7:28 ` [PATCH v9 14/19] hw/usb/redirect-server: Implement control transfers Jamin Lin
2026-11-10  7:28 ` [PATCH v9 15/19] hw/usb/redirect-server: Send device and interface information Jamin Lin
2026-11-10  7:28 ` [PATCH v9 16/19] hw/usb/redirect-server: Implement bulk and interrupt transfers Jamin Lin
2026-11-10  7:28 ` [PATCH v9 17/19] hw/usb/redirect-server: Stream interrupt IN endpoints Jamin Lin
2026-11-10  7:28 ` Jamin Lin [this message]
2026-11-10  7:28 ` [PATCH v9 19/19] hw/arm/aspeed: Enable the usbredir server transport Jamin Lin

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=20261110072818.2883349-19-jamin_lin@aspeedtech.com \
    --to=jamin_lin@aspeedtech.com \
    --cc=anthony@xenproject.org \
    --cc=berrange@redhat.com \
    --cc=clg@kaod.org \
    --cc=deller@gmx.de \
    --cc=edgar.iglesias@gmail.com \
    --cc=junjie.cao@intel.com \
    --cc=junjie.cao@linux.dev \
    --cc=kane_chen@aspeedtech.com \
    --cc=kfting@nuvoton.com \
    --cc=kraxel@redhat.com \
    --cc=marcandre.lureau@redhat.com \
    --cc=peter.maydell@linaro.org \
    --cc=philmd@oss.qualcomm.com \
    --cc=pierrick.bouvier@oss.qualcomm.com \
    --cc=qemu-arm@nongnu.org \
    --cc=qemu-devel@nongnu.org \
    --cc=richard.henderson@linaro.org \
    --cc=sstabellini@kernel.org \
    --cc=troy_lee@aspeedtech.com \
    --cc=wuhaotsh@google.com \
    --cc=xen-devel@lists.xenproject.org \
    --cc=zhao1.liu@intel.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.