From: Jamin Lin <jamin_lin@aspeedtech.com>
To: "clg@kaod.org" <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 v2 6/8] hw/usb/redirect-server: Implement bulk and interrupt transfers
Date: Mon, 5 Oct 2026 03:20:31 +0000 [thread overview]
Message-ID: <20261005032022.3980903-7-jamin_lin@aspeedtech.com> (raw)
In-Reply-To: <20261005032022.3980903-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>
---
include/hw/usb/redirect-server.h | 11 ++
hw/usb/redirect-server.c | 193 +++++++++++++++++++++++++++++++
hw/usb/trace-events | 5 +
3 files changed, 209 insertions(+)
diff --git a/include/hw/usb/redirect-server.h b/include/hw/usb/redirect-server.h
index 83c4524040..f7b2261db4 100644
--- a/include/hw/usb/redirect-server.h
+++ b/include/hw/usb/redirect-server.h
@@ -9,6 +9,7 @@
#ifndef HW_USB_REDIRECT_SERVER_H
#define HW_USB_REDIRECT_SERVER_H
+#include "qemu/units.h"
#include "hw/core/sysbus.h"
#include "hw/usb/usb.h"
#include "chardev/char-fe.h"
@@ -27,8 +28,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 {
@@ -41,7 +50,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.
diff --git a/hw/usb/redirect-server.c b/hw/usb/redirect-server.c
index 5005ea5f8a..a733a007e3 100644
--- a/hw/usb/redirect-server.c
+++ b/hw/usb/redirect-server.c
@@ -356,6 +356,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
*/
@@ -452,6 +498,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);
@@ -828,6 +880,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();
@@ -851,6 +1002,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 = {
@@ -899,7 +1068,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:
@@ -910,6 +1081,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;
}
@@ -952,6 +1138,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. */
@@ -963,7 +1151,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.43.0
next prev parent reply other threads:[~2026-10-05 3:22 UTC|newest]
Thread overview: 19+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-05 3:20 [PATCH v2 0/8] hw/usb: Add a usbredir server transport Jamin Lin
2026-10-05 3:20 ` [PATCH v2 1/8] hw/usb/bus: Let a bus opt out of automatic hub insertion Jamin Lin
2026-10-05 8:59 ` marcandre.lureau
2026-10-05 3:20 ` [PATCH v2 2/8] hw/usb/redirect-server: Add a usbredir server device Jamin Lin
2026-10-05 8:59 ` marcandre.lureau
2026-10-07 2:15 ` Jamin Lin
2026-10-07 6:11 ` Marc-André Lureau
2026-10-07 8:43 ` Jamin Lin
2026-10-05 3:20 ` [PATCH v2 3/8] hw/usb/redirect-server: Connect usbredirparser to a chardev Jamin Lin
2026-10-05 8:59 ` marcandre.lureau
2026-10-05 3:20 ` [PATCH v2 4/8] hw/usb/redirect-server: Announce the exported device Jamin Lin
2026-10-05 8:59 ` marcandre.lureau
2026-10-07 7:32 ` Jamin Lin
2026-10-05 3:20 ` [PATCH v2 5/8] hw/usb/redirect-server: Implement control transfers Jamin Lin
2026-10-05 3:20 ` Jamin Lin [this message]
2026-10-05 3:20 ` [PATCH v2 7/8] hw/usb/redirect-server: Stream interrupt IN endpoints Jamin Lin
2026-10-05 3:20 ` [PATCH v2 8/8] hw/arm/aspeed: Enable the usbredir server transport Jamin Lin
2026-10-05 8:59 ` [PATCH v2 0/8] hw/usb: Add a " marcandre.lureau
2026-10-07 8:42 ` 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=20261005032022.3980903-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.