Xen-Devel Archive on 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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox