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