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 16/19] hw/usb/redirect-server: Implement bulk and interrupt transfers
Date: Tue, 10 Nov 2026 07:28:42 +0000	[thread overview]
Message-ID: <20261110072818.2883349-17-jamin_lin@aspeedtech.com> (raw)
In-Reply-To: <20261110072818.2883349-1-jamin_lin@aspeedtech.com>

Send bulk and interrupt packets to the device and return the result to
the host. Each one is a request and an answer, so they reuse the packet
tracking and the NAK handling added for control transfers.

A bulk request carries a 32 bit length, so the host can ask us to
allocate up to 4 GB. Refuse a request that big. Do not answer it with
fewer bytes instead: the host counts the bytes it gets back, and it
resets the device when some are missing.

Advertise usb_redir_cap_32bits_bulk_length. We already read and write
the high half of the length, but without the capability redirect.c
asserts when its own guest asks for a transfer larger than 64KiB.

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 | 240 ++++++++++++++++++++++++++++++++++++++-
 hw/usb/trace-events      |   6 +
 2 files changed, 245 insertions(+), 1 deletion(-)

diff --git a/hw/usb/redirect-server.c b/hw/usb/redirect-server.c
index 914697fdea..dce92eb263 100644
--- a/hw/usb/redirect-server.c
+++ b/hw/usb/redirect-server.c
@@ -117,6 +117,12 @@ OBJECT_DECLARE_SIMPLE_TYPE(USBRedirServer, USB_REDIR_SERVER)
  */
 #define USBREDIR_SERVER_RETRY_MS 1000
 
+/*
+ * The bulk length field is 32 bits, so the host can ask for up to 4 GB.
+ * This is the largest transfer accepted.
+ */
+#define USBREDIR_SERVER_MAX_BULK (1 * MiB)
+
 /*
  * The steps to read the descriptors, in order:
  *
@@ -145,6 +151,8 @@ typedef enum {
 typedef enum {
     USBREDIR_SERVER_CTRL,
     USBREDIR_SERVER_DESCRIPTOR,
+    USBREDIR_SERVER_BULK,
+    USBREDIR_SERVER_INTERRUPT,
 } USBRedirServerPktType;
 
 /* Which message answers the host when a control transfer ends. */
@@ -168,7 +176,9 @@ typedef struct USBRedirServerPkt {
     USBPacket pkt;
 
     /* Kept for the usbredir response. */
+    struct usb_redir_interrupt_packet_header intr_hdr;
     struct usb_redir_control_packet_header ctrl_hdr;
+    struct usb_redir_bulk_packet_header bulk_hdr;
 
     /*
      * The IOV points here. IN data lands in it, OUT data is copied in.
@@ -1105,6 +1115,67 @@ static void usbredir_server_ctrl_complete(USBRedirServer *s,
     }
 }
 
+static void usbredir_server_bulk_reply(USBRedirServer *s, uint64_t id,
+    const struct usb_redir_bulk_packet_header *hdr, uint8_t status,
+    uint8_t *data, int actual)
+{
+    struct usb_redir_bulk_packet_header resp = *hdr;
+    bool is_in = !!(hdr->endpoint & USB_DIR_IN);
+    int data_len = is_in ? actual : 0;
+
+    resp.status = status;
+    /* Both directions report how much the device moved. */
+    resp.length = actual;
+    resp.length_high = actual >> 16;
+
+    usbredirparser_send_bulk_packet(s->parser, id, &resp,
+                                    data_len > 0 ? data : NULL, data_len);
+    usbredirparser_do_write(s->parser);
+}
+
+static void usbredir_server_bulk_complete(USBRedirServer *s,
+                                          USBRedirServerPkt *rp)
+{
+    uint8_t status = usbredir_server_status(rp->pkt.status);
+    int actual = rp->pkt.actual_length;
+
+    trace_usbredir_server_bulk_complete(rp->redir_id, rp->bulk_hdr.endpoint,
+                                        status, actual);
+    usbredir_server_bulk_reply(s, rp->redir_id, &rp->bulk_hdr, status,
+                               rp->data, actual);
+}
+
+static void usbredir_server_interrupt_reply(USBRedirServer *s, uint64_t id,
+    const struct usb_redir_interrupt_packet_header *hdr, uint8_t status,
+    uint8_t *data, int actual)
+{
+    struct usb_redir_interrupt_packet_header resp = *hdr;
+    bool is_in = !!(hdr->endpoint & USB_DIR_IN);
+    int data_len = is_in ? actual : 0;
+
+    resp.status = status;
+    /* Both directions report how much the device moved. */
+    resp.length = actual;
+
+    usbredirparser_send_interrupt_packet(s->parser, id, &resp,
+                                         data_len > 0 ? data : NULL,
+                                         data_len);
+    usbredirparser_do_write(s->parser);
+}
+
+static void usbredir_server_interrupt_complete(USBRedirServer *s,
+                                               USBRedirServerPkt *rp)
+{
+    uint8_t status = usbredir_server_status(rp->pkt.status);
+    int actual = rp->pkt.actual_length;
+
+    trace_usbredir_server_interrupt_complete(rp->redir_id,
+                                             rp->intr_hdr.endpoint,
+                                             status, actual);
+    usbredir_server_interrupt_reply(s, rp->redir_id, &rp->intr_hdr, status,
+                                    rp->data, actual);
+}
+
 /*
  * USB port ops
  */
@@ -1196,6 +1267,12 @@ static void usbredir_server_packet_complete(USBPort *port, USBPacket *p)
     case USBREDIR_SERVER_CTRL:
         usbredir_server_ctrl_complete(s, rp);
         break;
+    case USBREDIR_SERVER_BULK:
+        usbredir_server_bulk_complete(s, rp);
+        break;
+    case USBREDIR_SERVER_INTERRUPT:
+        usbredir_server_interrupt_complete(s, rp);
+        break;
     default:
         g_assert_not_reached();
     }
@@ -1601,6 +1678,121 @@ static void usbredir_server_get_alt_setting(void *priv, uint64_t id,
                                USBREDIR_SERVER_REPLY_ALT);
 }
 
+static void usbredir_server_bulk_packet(void *priv, uint64_t id,
+    struct usb_redir_bulk_packet_header *hdr,
+    uint8_t *data, int data_len)
+{
+    USBRedirServer *s = priv;
+    int idx = usbredir_server_ep_index(hdr->endpoint);
+    USBDevice *device = usbredir_server_device(s);
+    bool is_in = !!(hdr->endpoint & USB_DIR_IN);
+    int pid = is_in ? USB_TOKEN_IN : USB_TOKEN_OUT;
+    int ep_nr = hdr->endpoint & 0x0f;
+    USBRedirServerPkt *rp;
+    USBEndpoint *ep;
+    uint8_t status;
+    uint32_t len;
+
+    /* The host still waits for an answer, even to a request we refuse. */
+    status = usbredir_server_ep_status(s, hdr->endpoint,
+                                       USB_ENDPOINT_XFER_BULK);
+    if (status != usb_redir_success) {
+        trace_usbredir_server_refused(id, hdr->endpoint, status,
+                                      USB_ENDPOINT_XFER_BULK,
+                                      s->ep[idx].type);
+        usbredir_server_bulk_reply(s, id, hdr, status, NULL, 0);
+        usbredirparser_free_packet_data(s->parser, data);
+        return;
+    }
+
+    /* IN: what the host asked for. OUT: what the host sent. */
+    len = is_in ? (((uint32_t)hdr->length_high << 16) | hdr->length)
+                : (uint32_t)data_len;
+
+    /*
+     * Too big. Tell the host the transfer failed.
+     * Do not send back less data instead. The host would see the
+     * missing bytes as an error and reset the device.
+     */
+    if (len > USBREDIR_SERVER_MAX_BULK) {
+        trace_usbredir_server_bulk_too_big(id, hdr->endpoint, len);
+        usbredir_server_bulk_reply(s, id, hdr, usb_redir_inval, NULL, 0);
+        usbredirparser_free_packet_data(s->parser, data);
+        return;
+    }
+
+    ep = usb_ep_get(device, pid, ep_nr);
+    rp = usbredir_server_pkt_alloc(len, idx);
+    rp->redir_id = id;
+    rp->type = USBREDIR_SERVER_BULK;
+    rp->bulk_hdr = *hdr;
+
+    usb_packet_setup(&rp->pkt, pid, ep, 0, s->next_id++, false, false);
+
+    /* OUT data comes from the host. IN data is written by the device. */
+    if (!is_in && len > 0) {
+        memcpy(rp->data, data, len);
+    }
+    usb_packet_addbuf(&rp->pkt, rp->data, len);
+
+    trace_usbredir_server_bulk(id, hdr->endpoint, len);
+    usbredirparser_free_packet_data(s->parser, data);
+    usbredir_server_submit(s, rp);
+}
+
+/*
+ * The host sends an interrupt_packet. It is a request: interrupt OUT
+ * data to write, or a single IN request to answer.
+ */
+static void usbredir_server_interrupt_packet(void *priv, uint64_t id,
+    struct usb_redir_interrupt_packet_header *hdr,
+    uint8_t *data, int data_len)
+{
+    USBRedirServer *s = priv;
+    int idx = usbredir_server_ep_index(hdr->endpoint);
+    USBDevice *device = usbredir_server_device(s);
+    bool is_in = !!(hdr->endpoint & USB_DIR_IN);
+    int pid = is_in ? USB_TOKEN_IN : USB_TOKEN_OUT;
+    int ep_nr = hdr->endpoint & 0x0f;
+    USBRedirServerPkt *rp;
+    USBEndpoint *ep;
+    uint8_t status;
+    int len;
+
+    /* The host still waits for an answer, even to a request we refuse. */
+    status = usbredir_server_ep_status(s, hdr->endpoint,
+                                       USB_ENDPOINT_XFER_INT);
+    if (status != usb_redir_success) {
+        trace_usbredir_server_refused(id, hdr->endpoint, status,
+                                      USB_ENDPOINT_XFER_INT,
+                                      s->ep[idx].type);
+        usbredir_server_interrupt_reply(s, id, hdr, status, NULL, 0);
+        usbredirparser_free_packet_data(s->parser, data);
+        return;
+    }
+
+    /* IN: what the host asked for. OUT: what the host sent. */
+    len = is_in ? hdr->length : data_len;
+
+    ep = usb_ep_get(device, pid, ep_nr);
+    rp = usbredir_server_pkt_alloc(len, idx);
+    rp->redir_id = id;
+    rp->type = USBREDIR_SERVER_INTERRUPT;
+    rp->intr_hdr = *hdr;
+
+    usb_packet_setup(&rp->pkt, pid, ep, 0, s->next_id++, false, false);
+
+    /* OUT data comes from the host. IN data is written by the device. */
+    if (!is_in && len > 0) {
+        memcpy(rp->data, data, len);
+    }
+    usb_packet_addbuf(&rp->pkt, rp->data, len);
+
+    trace_usbredir_server_interrupt(id, hdr->endpoint, len);
+    usbredirparser_free_packet_data(s->parser, data);
+    usbredir_server_submit(s, rp);
+}
+
 static void usbredir_server_filter_reject(void *priv)
 {
     trace_usbredir_server_filter_reject();
@@ -1624,6 +1816,24 @@ static void usbredir_server_interface_info(void *priv,
     /* The host should not send this to a device. Nothing to do. */
 }
 
+static void usbredir_server_alloc_bulk_streams(void *priv, uint64_t id,
+    struct usb_redir_alloc_bulk_streams_header *hdr)
+{
+    /* We do not advertise bulk streams. Nothing to do. */
+}
+
+static void usbredir_server_start_bulk_receiving(void *priv, uint64_t id,
+    struct usb_redir_start_bulk_receiving_header *hdr)
+{
+    /* We do not advertise this. Nothing to do. */
+}
+
+static void usbredir_server_stop_bulk_receiving(void *priv, uint64_t id,
+    struct usb_redir_stop_bulk_receiving_header *hdr)
+{
+    /* We do not advertise this. Nothing to do. */
+}
+
 static void usbredir_server_cancel_data_packet(void *priv, uint64_t id)
 {
     USBRedirServer *s = priv;
@@ -1664,7 +1874,9 @@ static void usbredir_server_cancel_data_packet(void *priv, uint64_t id)
 static void usbredir_server_send_cancelled(USBRedirServer *s,
                                            USBRedirServerPkt *rp)
 {
+    struct usb_redir_interrupt_packet_header intr;
     struct usb_redir_control_packet_header ctrl;
+    struct usb_redir_bulk_packet_header bulk;
 
     switch (rp->type) {
     case USBREDIR_SERVER_CTRL:
@@ -1674,6 +1886,21 @@ static void usbredir_server_send_cancelled(USBRedirServer *s,
         usbredirparser_send_control_packet(s->parser, rp->redir_id,
                                            &ctrl, NULL, 0);
         break;
+    case USBREDIR_SERVER_BULK:
+        bulk = rp->bulk_hdr;
+        bulk.status = usb_redir_cancelled;
+        bulk.length_high = 0;
+        bulk.length = 0;
+        usbredirparser_send_bulk_packet(s->parser, rp->redir_id,
+                                        &bulk, NULL, 0);
+        break;
+    case USBREDIR_SERVER_INTERRUPT:
+        intr = rp->intr_hdr;
+        intr.status = usb_redir_cancelled;
+        intr.length = 0;
+        usbredirparser_send_interrupt_packet(s->parser, rp->redir_id,
+                                             &intr, NULL, 0);
+        break;
     default:
         return;
     }
@@ -1726,6 +1953,8 @@ static void usbredir_server_create_parser(USBRedirServer *s)
     s->parser->hello_func = usbredir_server_hello;
     s->parser->reset_func = usbredir_server_reset;
     s->parser->control_packet_func = usbredir_server_control_packet;
+    s->parser->bulk_packet_func = usbredir_server_bulk_packet;
+    s->parser->interrupt_packet_func = usbredir_server_interrupt_packet;
     s->parser->set_configuration_func = usbredir_server_set_configuration;
 
     /* The parser calls these directly, so they must not be NULL. */
@@ -1737,12 +1966,21 @@ static void usbredir_server_create_parser(USBRedirServer *s)
     s->parser->device_disconnect_ack_func =
         usbredir_server_device_disconnect_ack;
     s->parser->interface_info_func = usbredir_server_interface_info;
+    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 =
+        usbredir_server_start_bulk_receiving;
+    s->parser->stop_bulk_receiving_func =
+        usbredir_server_stop_bulk_receiving;
 
-    /* Capabilities: 64-bit IDs, connect_device_version, ep_info sizes */
+    /*
+     * Capabilities: 64-bit IDs, connect_device_version, ep_info sizes
+     * and 32-bit bulk lengths.
+     */
     usbredirparser_caps_set_cap(caps, usb_redir_cap_connect_device_version);
     usbredirparser_caps_set_cap(caps, usb_redir_cap_ep_info_max_packet_size);
     usbredirparser_caps_set_cap(caps, usb_redir_cap_64bits_ids);
+    usbredirparser_caps_set_cap(caps, usb_redir_cap_32bits_bulk_length);
 
     /*
      * In USB the host is the side that starts every transfer; a device
diff --git a/hw/usb/trace-events b/hw/usb/trace-events
index 549f5f6e23..fe73ef6698 100644
--- a/hw/usb/trace-events
+++ b/hw/usb/trace-events
@@ -424,3 +424,9 @@ usbredir_server_nak_park(uint64_t id, int ep) "id %" PRIu64 " ep %d"
 usbredir_server_nak_retry(uint64_t id, int ep) "id %" PRIu64 " ep %d"
 usbredir_server_ctrl_complete(uint64_t id, int status, int actual) "id %" PRIu64 " status %d actual %d"
 usbredir_server_ctrl_refused(uint64_t id, int status) "id %" PRIu64 " status %d"
+usbredir_server_bulk(uint64_t id, uint8_t ep, size_t len) "id %" PRIu64 " ep 0x%02x len %zu"
+usbredir_server_bulk_complete(uint64_t id, uint8_t ep, int status, int actual) "id %" PRIu64 " ep 0x%02x status %d actual %d"
+usbredir_server_bulk_too_big(uint64_t id, uint8_t ep, uint32_t len) "id %" PRIu64 " ep 0x%02x len %u"
+usbredir_server_refused(uint64_t id, uint8_t ep, int status, uint8_t want, uint8_t got) "id %" PRIu64 " ep 0x%02x status %d, wanted type %u, it is %u"
+usbredir_server_interrupt(uint64_t id, uint8_t ep, size_t len) "id %" PRIu64 " ep 0x%02x len %zu"
+usbredir_server_interrupt_complete(uint64_t id, uint8_t ep, int status, int actual) "id %" PRIu64 " 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 ` Jamin Lin [this message]
2026-11-10  7:28 ` [PATCH v9 17/19] hw/usb/redirect-server: Stream interrupt IN endpoints Jamin Lin
2026-11-10  7:28 ` [PATCH v9 18/19] hw/usb/redirect-server: Implement isochronous OUT transfers Jamin Lin
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-17-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