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 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:30 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 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.