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 17/19] hw/usb/redirect-server: Stream interrupt IN endpoints
Date: Tue, 10 Nov 2026 07:28:43 +0000 [thread overview]
Message-ID: <20261110072818.2883349-18-jamin_lin@aspeedtech.com> (raw)
In-Reply-To: <20261110072818.2883349-1-jamin_lin@aspeedtech.com>
Interrupt IN is not a request and an answer in usbredir. The host asks
once with start_interrupt_receiving. After that it expects a packet
every time the device has data.
Park one IN packet on the device for each streaming endpoint. A device
that answers USB_RET_ASYNC holds the packet and completes it when it
has data. A device that answers USB_RET_NAK gives the packet back at
once, so park a new one when the bus reports a wakeup on that endpoint.
A slow timer parks one too, for a device that does not report a wakeup.
A streamed request does not wait on the endpoint's list the way a host
request does, because this timer already asks again.
Wait for the endpoint's bInterval before parking the next one. Some
devices answer every time. Without the wait we would ask as fast as
the main loop runs, and the guest would get no time.
On an error other than a stall the stream stays up, but back off to the
slow timer. At a high speed bInterval of 125us an endpoint that keeps
failing would otherwise send thousands of status messages a second.
Give every endpoint its own timer and bottom half. We ask each endpoint
at its own rate. Drop the timer when the bottom half has already asked,
so it does not fire for nothing.
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
hw/usb/redirect-server.c | 309 ++++++++++++++++++++++++++++++++++++++-
hw/usb/trace-events | 4 +
2 files changed, 308 insertions(+), 5 deletions(-)
diff --git a/hw/usb/redirect-server.c b/hw/usb/redirect-server.c
index dce92eb263..2abbec74c6 100644
--- a/hw/usb/redirect-server.c
+++ b/hw/usb/redirect-server.c
@@ -123,6 +123,9 @@ OBJECT_DECLARE_SIMPLE_TYPE(USBRedirServer, USB_REDIR_SERVER)
*/
#define USBREDIR_SERVER_MAX_BULK (1 * MiB)
+/* Buffer size for an interrupt IN endpoint before its descriptor is seen. */
+#define USBREDIR_SERVER_INTR_STREAM_DEFAULT_LEN 64
+
/*
* The steps to read the descriptors, in order:
*
@@ -153,6 +156,7 @@ typedef enum {
USBREDIR_SERVER_DESCRIPTOR,
USBREDIR_SERVER_BULK,
USBREDIR_SERVER_INTERRUPT,
+ USBREDIR_SERVER_INTR_STREAM,
} USBRedirServerPktType;
/* Which message answers the host when a control transfer ends. */
@@ -211,6 +215,15 @@ typedef struct USBRedirServerEp {
/* Asks again for a device that does not wake the endpoint. */
QEMUTimer *retry;
+ /*
+ * Interrupt IN streaming. The timer asks the device again at the
+ * endpoint's own rate, and more slowly after a NAK or an error.
+ * The bottom half asks at once when the device says it has data.
+ */
+ QEMUTimer *intr_stream_timer;
+ QEMUBH *intr_stream_bh;
+ bool intr_streaming;
+
uint16_t max_packet;
uint8_t interval;
uint8_t iface;
@@ -255,7 +268,7 @@ struct USBRedirServer {
bool desc_answered;
QEMUBH *desc_bh;
- /* Every request the host is still waiting for an answer to. */
+ /* Every request waiting for the device to answer. */
QTAILQ_HEAD(, USBRedirServerPkt) inflight;
uint64_t next_id;
@@ -1176,6 +1189,97 @@ static void usbredir_server_interrupt_complete(USBRedirServer *s,
rp->data, actual);
}
+/*
+ * How long to wait before asking the device again. bInterval means
+ * different things at different speeds: a number of frames at low and
+ * full speed, an exponent at high speed.
+ */
+static int64_t usbredir_server_intr_stream_period_ns(USBRedirServer *s,
+ int ep_nr)
+{
+ int idx = usbredir_server_ep_index(ep_nr | USB_DIR_IN);
+ USBDevice *device = usbredir_server_device(s);
+ uint8_t interval = s->ep[idx].interval;
+
+ if (interval == 0) {
+ interval = 1;
+ }
+
+ if (device && device->speed == USB_SPEED_HIGH) {
+ interval = MIN(interval, 16);
+ return (1LL << (interval - 1)) * 125 * SCALE_US;
+ }
+
+ return (int64_t)interval * SCALE_MS;
+}
+
+/*
+ * The device answered a streamed request. Send the answer to the host, then
+ * ask again, because the host still wants more. Do not ask from here: the
+ * device may answer at once, and this function would call itself over and
+ * over. Arm the timer instead. It also waits for the endpoint's bInterval.
+ */
+static void usbredir_server_intr_stream_complete(USBRedirServer *s,
+ USBRedirServerPkt *rp)
+{
+ struct usb_redir_interrupt_packet_header resp = rp->intr_hdr;
+ struct usb_redir_interrupt_receiving_status_header st;
+ int idx = usbredir_server_ep_index(resp.endpoint);
+ int ep_nr = resp.endpoint & 0x0f;
+ USBRedirServerEp *ep = &s->ep[idx];
+ USBPacket *p = &rp->pkt;
+ int actual = p->actual_length;
+ bool retry = false;
+ int64_t delay_ns;
+
+ trace_usbredir_server_intr_stream_complete(ep_nr, p->status, actual);
+
+ switch (p->status) {
+ case USB_RET_SUCCESS:
+ resp.status = usb_redir_success;
+ resp.length = actual;
+ usbredirparser_send_interrupt_packet(s->parser, 0, &resp,
+ actual ? rp->data : NULL,
+ actual);
+ usbredirparser_do_write(s->parser);
+ break;
+ case USB_RET_NAK:
+ /* no data yet; wait, unless the device wakes us first */
+ retry = true;
+ break;
+ default:
+ /*
+ * The endpoint failed. There is no data, so send a status
+ * message instead. The host gives it to its guest.
+ *
+ * redirect.c ends the stream only on a stall, so do the same
+ * here. If we end it on any other error, the host still
+ * thinks the stream runs, and it never asks again.
+ */
+ st.endpoint = resp.endpoint;
+ st.status = usbredir_server_status(p->status);
+ usbredirparser_send_interrupt_receiving_status(s->parser, 0, &st);
+ usbredirparser_do_write(s->parser);
+ if (p->status == USB_RET_STALL) {
+ ep->intr_streaming = false;
+ } else {
+ /* the stream stays up, but do not ask at the endpoint's rate */
+ retry = true;
+ }
+ break;
+ }
+
+ if (ep->intr_streaming) {
+ if (retry) {
+ delay_ns = (int64_t)USBREDIR_SERVER_RETRY_MS * SCALE_MS;
+ } else {
+ delay_ns = usbredir_server_intr_stream_period_ns(s, ep_nr);
+ }
+ timer_mod(ep->intr_stream_timer,
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + delay_ns);
+ }
+}
+
/*
* USB port ops
*/
@@ -1273,6 +1377,9 @@ static void usbredir_server_packet_complete(USBPort *port, USBPacket *p)
case USBREDIR_SERVER_INTERRUPT:
usbredir_server_interrupt_complete(s, rp);
break;
+ case USBREDIR_SERVER_INTR_STREAM:
+ usbredir_server_intr_stream_complete(s, rp);
+ break;
default:
g_assert_not_reached();
}
@@ -1312,9 +1419,11 @@ static USBRedirServerAsk usbredir_server_ask_device(USBRedirServer *s,
/*
* Keep the request and send it again when the device wakes the
- * endpoint.
+ * endpoint. A streamed request asks again by itself, with its own
+ * timer.
*/
- if (rp->pkt.status == USB_RET_NAK) {
+ if (rp->pkt.status == USB_RET_NAK &&
+ rp->type != USBREDIR_SERVER_INTR_STREAM) {
trace_usbredir_server_nak_park(rp->redir_id, rp->ep_idx);
QTAILQ_INSERT_HEAD(&ep->waiting, rp, waiting_link);
timer_mod(ep->retry, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
@@ -1360,6 +1469,15 @@ static void usbredir_server_wakeup_ep(USBBus *bus, USBEndpoint *ep,
{
USBRedirServer *s = container_of(bus, USBRedirServer, bus);
uint8_t addr = ep->nr | (ep->pid == USB_TOKEN_IN ? USB_DIR_IN : 0);
+ int idx = usbredir_server_ep_index(addr);
+
+ /*
+ * The device has data. A streaming interrupt IN endpoint may have
+ * answered NAK. Ask now instead of waiting for the retry timer.
+ */
+ if (s->ep[idx].intr_streaming) {
+ qemu_bh_schedule(s->ep[idx].intr_stream_bh);
+ }
/*
* Our descriptor request is not on inflight or waiting, so
@@ -1371,7 +1489,8 @@ static void usbredir_server_wakeup_ep(USBBus *bus, USBEndpoint *ep,
qemu_bh_schedule(s->desc_bh);
}
- usbredir_server_kick_ep(s, usbredir_server_ep_index(addr));
+ /* The host may also have a request stopped on this endpoint. */
+ usbredir_server_kick_ep(s, idx);
}
static USBBusOps usbredir_server_bus_ops = {
@@ -1391,6 +1510,95 @@ static void usbredir_server_submit(USBRedirServer *s, USBRedirServerPkt *rp)
usbredir_server_ask_device(s, rp);
}
+/*
+ * Interrupt IN streaming
+ */
+
+static bool usbredir_server_intr_stream_inflight(USBRedirServer *s, int ep_nr)
+{
+ USBRedirServerPkt *rp;
+
+ QTAILQ_FOREACH(rp, &s->inflight, inflight_link) {
+ if (rp->type == USBREDIR_SERVER_INTR_STREAM &&
+ (rp->intr_hdr.endpoint & 0x0f) == ep_nr) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/*
+ * Ask the device for data on @ep_nr and leave the request waiting. A device
+ * sends data only when asked. Do nothing if a request on @ep_nr is still
+ * unanswered.
+ */
+static void usbredir_server_intr_stream_park(USBRedirServer *s, int ep_nr)
+{
+ USBDevice *device = usbredir_server_device(s);
+ int idx = usbredir_server_ep_index(ep_nr | USB_DIR_IN);
+ USBRedirServerPkt *rp;
+ USBEndpoint *ep;
+ int len;
+
+ /* No device to ask. */
+ if (!device || !device->attached) {
+ return;
+ }
+
+ /* The host did not ask for this endpoint, or a request is unanswered. */
+ if (!s->ep[idx].intr_streaming ||
+ usbredir_server_intr_stream_inflight(s, ep_nr)) {
+ return;
+ }
+
+ len = s->ep[idx].max_packet;
+ if (len == 0) {
+ len = USBREDIR_SERVER_INTR_STREAM_DEFAULT_LEN;
+ }
+ ep = usb_ep_get(device, USB_TOKEN_IN, ep_nr);
+ rp = usbredir_server_pkt_alloc(len, idx);
+ rp->type = USBREDIR_SERVER_INTR_STREAM;
+ /* A streamed request comes from us, not from the host. */
+ rp->redir_id = 0;
+ rp->intr_hdr.endpoint = ep_nr | USB_DIR_IN;
+
+ usb_packet_setup(&rp->pkt, USB_TOKEN_IN, ep, 0, s->next_id++,
+ false, false);
+ usb_packet_addbuf(&rp->pkt, rp->data, len);
+
+ /*
+ * The timer is there for a device that does not wake the endpoint.
+ * We are asking now, so drop it. The answer sets it again.
+ */
+ timer_del(s->ep[idx].intr_stream_timer);
+
+ trace_usbredir_server_intr_stream_park(ep_nr, len);
+ usbredir_server_submit(s, rp);
+}
+
+/* BH and timer callback: ask again on one streaming endpoint. */
+static void usbredir_server_intr_stream_kick(void *opaque)
+{
+ USBRedirServerEp *ep = opaque;
+ int ep_nr = ep->idx - USBREDIR_SERVER_EP_IN_BASE;
+
+ usbredir_server_intr_stream_park(ep->s, ep_nr);
+}
+
+static void usbredir_server_intr_stream_cancel(USBRedirServer *s, int ep_nr)
+{
+ USBRedirServerPkt *tmp;
+ USBRedirServerPkt *rp;
+
+ QTAILQ_FOREACH_SAFE(rp, &s->inflight, inflight_link, tmp) {
+ if (rp->type != USBREDIR_SERVER_INTR_STREAM ||
+ (rp->intr_hdr.endpoint & 0x0f) != ep_nr) {
+ continue;
+ }
+ usbredir_server_pkt_free(s, rp);
+ }
+}
+
/*
* usbredirparser I/O and logging callbacks
*/
@@ -1816,6 +2024,63 @@ static void usbredir_server_interface_info(void *priv,
/* The host should not send this to a device. Nothing to do. */
}
+/*
+ * The host asks to stream an interrupt IN endpoint. Send the receiving
+ * status first. Without that status the host throws the interrupt
+ * packet away. Then ask the device once.
+ */
+static void usbredir_server_start_interrupt_receiving(void *priv,
+ uint64_t id, struct usb_redir_start_interrupt_receiving_header *hdr)
+{
+ struct usb_redir_interrupt_receiving_status_header st = {
+ .endpoint = hdr->endpoint,
+ .status = usb_redir_success,
+ };
+ int idx = usbredir_server_ep_index(hdr->endpoint);
+ int ep_nr = hdr->endpoint & 0x0f;
+ USBRedirServer *s = priv;
+
+ st.status = usbredir_server_ep_status(s, hdr->endpoint,
+ USB_ENDPOINT_XFER_INT);
+
+ /* Only an interrupt IN endpoint can stream. */
+ if (!(hdr->endpoint & USB_DIR_IN)) {
+ st.status = usb_redir_inval;
+ }
+
+ if (st.status == usb_redir_success) {
+ s->ep[idx].intr_streaming = true;
+ }
+
+ trace_usbredir_server_intr_stream_start(hdr->endpoint, st.status);
+ usbredirparser_send_interrupt_receiving_status(s->parser, id, &st);
+ usbredirparser_do_write(s->parser);
+
+ if (st.status == usb_redir_success) {
+ usbredir_server_intr_stream_park(s, ep_nr);
+ }
+}
+
+static void usbredir_server_stop_interrupt_receiving(void *priv, uint64_t id,
+ struct usb_redir_stop_interrupt_receiving_header *hdr)
+{
+ int idx = usbredir_server_ep_index(hdr->endpoint);
+ int ep_nr = hdr->endpoint & 0x0f;
+ USBRedirServer *s = priv;
+
+ trace_usbredir_server_intr_stream_stop(hdr->endpoint);
+
+ /* Endpoint 0 is control, and an OUT endpoint never streams. */
+ if (ep_nr == 0 || !(hdr->endpoint & USB_DIR_IN)) {
+ return;
+ }
+
+ s->ep[idx].intr_streaming = false;
+ qemu_bh_cancel(s->ep[idx].intr_stream_bh);
+ timer_del(s->ep[idx].intr_stream_timer);
+ usbredir_server_intr_stream_cancel(s, ep_nr);
+}
+
static void usbredir_server_alloc_bulk_streams(void *priv, uint64_t id,
struct usb_redir_alloc_bulk_streams_header *hdr)
{
@@ -1841,6 +2106,14 @@ static void usbredir_server_cancel_data_packet(void *priv, uint64_t id)
int idx;
QTAILQ_FOREACH(rp, &s->inflight, inflight_link) {
+ /*
+ * A streamed request comes from us, not from the host.
+ * The host has no id for it, so a cancel never matches it.
+ */
+ if (rp->type == USBREDIR_SERVER_INTR_STREAM) {
+ continue;
+ }
+
if (rp->redir_id == id) {
idx = rp->ep_idx;
@@ -1918,6 +2191,12 @@ static void usbredir_server_stop_transfers(USBRedirServer *s)
timer_del(s->ep[i].retry);
}
+ for (i = USBREDIR_SERVER_EP_IN_BASE + 1; i < USBREDIR_SERVER_MAX_EP; i++) {
+ s->ep[i].intr_streaming = false;
+ qemu_bh_cancel(s->ep[i].intr_stream_bh);
+ timer_del(s->ep[i].intr_stream_timer);
+ }
+
/*
* The host waits for an answer to each of these. Send cancelled
* before we free them.
@@ -1966,6 +2245,10 @@ 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->start_interrupt_receiving_func =
+ usbredir_server_start_interrupt_receiving;
+ s->parser->stop_interrupt_receiving_func =
+ usbredir_server_stop_interrupt_receiving;
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 =
@@ -2115,6 +2398,16 @@ static void usbredir_server_realize(DeviceState *dev, Error **errp)
s->ep[i].retry = timer_new_ns(QEMU_CLOCK_VIRTUAL,
usbredir_server_retry_ep, &s->ep[i]);
}
+
+ /* Only an interrupt IN endpoint streams, so endpoint 0 and OUT are out. */
+ for (i = USBREDIR_SERVER_EP_IN_BASE + 1; i < USBREDIR_SERVER_MAX_EP; i++) {
+ s->ep[i].intr_stream_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
+ usbredir_server_intr_stream_kick,
+ &s->ep[i]);
+ s->ep[i].intr_stream_bh =
+ qemu_bh_new_guarded(usbredir_server_intr_stream_kick, &s->ep[i],
+ &dev->mem_reentrancy_guard);
+ }
s->config_table = g_hash_table_new_full(g_direct_hash, g_direct_equal,
NULL, g_free);
usbredir_server_clear_ep_table(s);
@@ -2125,7 +2418,6 @@ static void usbredir_server_realize(DeviceState *dev, Error **errp)
usb_register_port(&s->bus, &s->port, s, 0, &usbredir_server_port_ops,
USB_SPEED_MASK_LOW | USB_SPEED_MASK_FULL |
USB_SPEED_MASK_HIGH);
-
s->chardev_close_bh = qemu_bh_new_guarded(usbredir_server_chardev_close_bh,
s, &dev->mem_reentrancy_guard);
s->announce_bh = qemu_bh_new_guarded(usbredir_server_announce_bh, s,
@@ -2178,6 +2470,13 @@ static void usbredir_server_unrealize(DeviceState *dev)
timer_free(s->ep[i].retry);
s->ep[i].retry = NULL;
}
+
+ for (i = USBREDIR_SERVER_EP_IN_BASE + 1; i < USBREDIR_SERVER_MAX_EP; i++) {
+ timer_free(s->ep[i].intr_stream_timer);
+ s->ep[i].intr_stream_timer = NULL;
+ qemu_bh_delete(s->ep[i].intr_stream_bh);
+ s->ep[i].intr_stream_bh = NULL;
+ }
}
static const Property usbredir_server_props[] = {
diff --git a/hw/usb/trace-events b/hw/usb/trace-events
index fe73ef6698..20c910e28a 100644
--- a/hw/usb/trace-events
+++ b/hw/usb/trace-events
@@ -430,3 +430,7 @@ usbredir_server_bulk_too_big(uint64_t id, uint8_t ep, uint32_t len) "id %" PRIu6
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"
+usbredir_server_intr_stream_start(uint8_t ep, int status) "start_interrupt_receiving ep 0x%02x -> status %d"
+usbredir_server_intr_stream_stop(uint8_t ep) "stop_interrupt_receiving ep 0x%02x"
+usbredir_server_intr_stream_park(unsigned ep_nr, int len) "parked IN packet ep %u len %d"
+usbredir_server_intr_stream_complete(unsigned ep_nr, int status, int actual) "ep %u status %d actual %d"
--
2.43.0
next prev 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 ` [PATCH v9 16/19] hw/usb/redirect-server: Implement bulk and interrupt transfers Jamin Lin
2026-11-10 7:28 ` Jamin Lin [this message]
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-18-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.