All of lore.kernel.org
 help / color / mirror / Atom feed
From: Jamin Lin <jamin_lin@aspeedtech.com>
To: "Cédric Le Goater" <clg@kaod.org>,
	"Peter Maydell" <peter.maydell@linaro.org>,
	"Steven Lee" <steven_lee@aspeedtech.com>,
	"Troy Lee" <leetroy@gmail.com>,
	"Kane Chen" <kane_chen@aspeedtech.com>,
	"Andrew Jeffery" <andrew@codeconstruct.com.au>,
	"Joel Stanley" <joel@jms.id.au>,
	"open list:ASPEED BMCs" <qemu-arm@nongnu.org>,
	"open list:All patches CC here" <qemu-devel@nongnu.org>
Cc: Jamin Lin <jamin_lin@aspeedtech.com>, Troy Lee <troy_lee@aspeedtech.com>
Subject: [PATCH v3 6/8] hw/usb/redirect-server: Implement bulk and interrupt transfers
Date: Mon, 12 Oct 2026 02:16:42 +0000	[thread overview]
Message-ID: <20261012021634.2006424-7-jamin_lin@aspeedtech.com> (raw)
In-Reply-To: <20261012021634.2006424-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 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.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
 hw/usb/redirect-server.c | 203 +++++++++++++++++++++++++++++++++++++++
 hw/usb/trace-events      |   5 +
 2 files changed, 208 insertions(+)

diff --git a/hw/usb/redirect-server.c b/hw/usb/redirect-server.c
index 22083394ab..a46d7aec43 100644
--- a/hw/usb/redirect-server.c
+++ b/hw/usb/redirect-server.c
@@ -82,8 +82,16 @@ OBJECT_DECLARE_SIMPLE_TYPE(USBRedirServer, USB_REDIR_SERVER)
 #define USBREDIR_SERVER_MAX_EP 32
 #define USBREDIR_SERVER_EP_IN_BASE 16
 
+/*
+ * 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)
+
 #define USBREDIR_SERVER_CTRL_SETUP 0
 #define USBREDIR_SERVER_CTRL_STATUS 1
+#define USBREDIR_SERVER_BULK 2
+#define USBREDIR_SERVER_INTR 3
 
 /* Which message answers the host when a control transfer ends. */
 typedef enum {
@@ -96,7 +104,9 @@ typedef struct USBRedirServerPkt {
     USBPacket pkt;
 
     /* Saved headers 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.
@@ -428,6 +438,52 @@ static void usbredir_server_ctrl_status_complete(USBRedirServer *s,
     }
 }
 
+static void usbredir_server_bulk_complete(USBRedirServer *s,
+                                          USBRedirServerPkt *rp)
+{
+    struct usb_redir_bulk_packet_header resp = rp->bulk_hdr;
+    bool is_in = !!(rp->bulk_hdr.endpoint & USB_DIR_IN);
+    int actual = rp->pkt.actual_length;
+    int len;
+
+    resp.status = usbredir_server_status(rp->pkt.status);
+
+    /* Only an IN transfer carries data back. */
+    len = is_in ? actual : 0;
+    resp.length = len;
+    resp.length_high = len >> 16;
+
+    trace_usbredir_server_bulk_complete(rp->redir_id, resp.endpoint,
+                                        resp.status, actual);
+
+    usbredirparser_send_bulk_packet(s->parser, rp->redir_id, &resp,
+                                    len > 0 ? rp->data : NULL, len);
+    usbredirparser_do_write(s->parser);
+}
+
+static void usbredir_server_intr_complete(USBRedirServer *s,
+                                          USBRedirServerPkt *rp)
+{
+    struct usb_redir_interrupt_packet_header resp = rp->intr_hdr;
+    bool is_in = !!(rp->intr_hdr.endpoint & USB_DIR_IN);
+    int actual = rp->pkt.actual_length;
+    int len;
+
+    resp.status = usbredir_server_status(rp->pkt.status);
+
+    /* Only an IN transfer carries data back. */
+    len = is_in ? actual : 0;
+    resp.length = len;
+
+    trace_usbredir_server_intr_complete(rp->redir_id, resp.endpoint,
+                                        resp.status, actual);
+
+    usbredirparser_send_interrupt_packet(s->parser, rp->redir_id, &resp,
+                                         len > 0 ? rp->data : NULL,
+                                         len);
+    usbredirparser_do_write(s->parser);
+}
+
 /*
  * USB port ops
  */
@@ -497,6 +553,12 @@ static void usbredir_server_packet_complete(USBPort *port, USBPacket *p)
     case USBREDIR_SERVER_CTRL_STATUS:
         usbredir_server_ctrl_status_complete(s, rp);
         break;
+    case USBREDIR_SERVER_BULK:
+        usbredir_server_bulk_complete(s, rp);
+        break;
+    case USBREDIR_SERVER_INTR:
+        usbredir_server_intr_complete(s, rp);
+        break;
     }
 
     usbredir_server_pkt_free(rp);
@@ -873,6 +935,105 @@ 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;
+    USBDevice *device = usbredir_server_device(s);
+    bool is_in = !!(hdr->endpoint & USB_DIR_IN);
+    int pid = is_in ? USB_TOKEN_IN : USB_TOKEN_OUT;
+    struct usb_redir_bulk_packet_header resp;
+    int ep_nr = hdr->endpoint & 0x0f;
+    USBRedirServerPkt *rp;
+    USBEndpoint *ep;
+    uint32_t len;
+
+    if (!s->host_connected || !device || !device->attached) {
+        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) {
+        resp = *hdr;
+        resp.status = usb_redir_inval;
+        resp.length = 0;
+        resp.length_high = 0;
+
+        trace_usbredir_server_bulk_too_big(id, hdr->endpoint, len);
+        usbredirparser_send_bulk_packet(s->parser, id, &resp, NULL, 0);
+        usbredirparser_do_write(s->parser);
+        return;
+    }
+
+    ep = usb_ep_get(device, pid, ep_nr);
+    rp = usbredir_server_pkt_alloc(len);
+    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);
+    usbredir_server_submit_to_device(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;
+    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;
+    int len;
+
+    if (!s->host_connected || !device || !device->attached) {
+        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);
+    rp->redir_id = id;
+    rp->type = USBREDIR_SERVER_INTR;
+    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);
+    usbredir_server_submit_to_device(s, rp);
+}
+
 static void usbredir_server_filter_reject(void *priv)
 {
     trace_usbredir_server_filter_reject();
@@ -896,6 +1057,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)
 {
     struct usb_redir_control_packet_header resp = {
@@ -944,7 +1123,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_SETUP:
@@ -955,6 +1136,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 = 0;
+        bulk.length_high = 0;
+        usbredirparser_send_bulk_packet(s->parser, rp->redir_id,
+                                        &bulk, NULL, 0);
+        break;
+    case USBREDIR_SERVER_INTR:
+        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;
     }
@@ -997,6 +1193,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. */
@@ -1008,7 +1206,12 @@ 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 */
     usbredirparser_caps_set_cap(caps, usb_redir_cap_connect_device_version);
diff --git a/hw/usb/trace-events b/hw/usb/trace-events
index 25219a018a..c9ab80c6ba 100644
--- a/hw/usb/trace-events
+++ b/hw/usb/trace-events
@@ -411,3 +411,8 @@ usbredir_server_ep_info(unsigned idx, uint8_t type, uint16_t mps, uint8_t interv
 usbredir_server_control(uint64_t id, uint8_t requesttype, uint8_t request, uint16_t value, uint16_t index, uint16_t length) "id %" PRIu64 " type 0x%02x request 0x%02x value 0x%04x index 0x%04x length %u"
 usbredir_server_ctrl_setup_complete(uint64_t id, int status, int actual) "id %" PRIu64 " status %d actual %d"
 usbredir_server_ctrl_status_complete(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_interrupt(uint64_t id, uint8_t ep, size_t len) "id %" PRIu64 " ep 0x%02x len %zu"
+usbredir_server_intr_complete(uint64_t id, uint8_t ep, int status, int actual) "id %" PRIu64 " ep 0x%02x status %d actual %d"
-- 
2.53.0


  parent reply	other threads:[~2026-10-12  2:18 UTC|newest]

Thread overview: 17+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-12  2:16 [PATCH v3 0/8] hw/usb: Add a usbredir server transport Jamin Lin
2026-10-12  2:16 ` [PATCH v3 1/8] hw/usb/bus: Let a bus opt out of automatic hub insertion Jamin Lin
2026-10-12  2:16 ` [PATCH v3 2/8] hw/usb/redirect-server: Add a usbredir server device Jamin Lin
2026-10-12 10:01   ` marcandre.lureau
2026-10-12  2:16 ` [PATCH v3 3/8] hw/usb/redirect-server: Connect usbredirparser to a chardev Jamin Lin
2026-10-12  2:16 ` [PATCH v3 4/8] hw/usb/redirect-server: Announce the exported device Jamin Lin
2026-10-12 10:01   ` marcandre.lureau
2026-10-12  2:16 ` [PATCH v3 5/8] hw/usb/redirect-server: Implement control transfers Jamin Lin
2026-10-12 10:01   ` marcandre.lureau
2026-10-13  1:30     ` Jamin Lin
2026-10-12  2:16 ` Jamin Lin [this message]
2026-10-12 10:01   ` [PATCH v3 6/8] hw/usb/redirect-server: Implement bulk and interrupt transfers marcandre.lureau
2026-10-12  2:16 ` [PATCH v3 7/8] hw/usb/redirect-server: Stream interrupt IN endpoints Jamin Lin
2026-10-12 10:01   ` marcandre.lureau
2026-10-12  2:16 ` [PATCH v3 8/8] hw/arm/aspeed: Enable the usbredir server transport Jamin Lin
2026-10-12 10:01   ` marcandre.lureau
2026-10-12  2:19 ` [PATCH v3 0/8] hw/usb: Add a " 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=20261012021634.2006424-7-jamin_lin@aspeedtech.com \
    --to=jamin_lin@aspeedtech.com \
    --cc=andrew@codeconstruct.com.au \
    --cc=clg@kaod.org \
    --cc=joel@jms.id.au \
    --cc=kane_chen@aspeedtech.com \
    --cc=leetroy@gmail.com \
    --cc=peter.maydell@linaro.org \
    --cc=qemu-arm@nongnu.org \
    --cc=qemu-devel@nongnu.org \
    --cc=steven_lee@aspeedtech.com \
    --cc=troy_lee@aspeedtech.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.