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 4/8] hw/usb/redirect-server: Announce the exported device
Date: Mon, 5 Oct 2026 03:20:28 +0000 [thread overview]
Message-ID: <20261005032022.3980903-5-jamin_lin@aspeedtech.com> (raw)
In-Reply-To: <20261005032022.3980903-1-jamin_lin@aspeedtech.com>
Send device_connect when a device is attached and the host has greeted
us, device_disconnect when it goes away, and handle the bus reset the
host sends before enumerating.
Wait a short time before sending device_connect. Firmware often toggles
the USB pull-up a few times while it starts its controller. If the host
starts to enumerate while the device is off, we cannot answer its first
request, and it only retries after a control timeout of several seconds.
device_connect carries no identity. The peer asks the device itself with
GET_DESCRIPTOR as soon as it enumerates, so anything we put there would
be made up. The speed is real: it comes from the device on our port.
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
include/hw/usb/redirect-server.h | 2 +
hw/usb/redirect-server.c | 166 ++++++++++++++++++++++++++++++-
hw/usb/trace-events | 7 ++
3 files changed, 174 insertions(+), 1 deletion(-)
diff --git a/include/hw/usb/redirect-server.h b/include/hw/usb/redirect-server.h
index 2af881bda9..a3d5084424 100644
--- a/include/hw/usb/redirect-server.h
+++ b/include/hw/usb/redirect-server.h
@@ -31,12 +31,14 @@ struct USBRedirServer {
/* usbredir over the chardev */
struct usbredirparser *parser;
+ QEMUTimer *announce_timer;
QEMUBH *chardev_close_bh;
const uint8_t *read_buf;
int read_buf_size;
bool in_write;
guint watch;
bool host_connected;
+ bool device_announced;
};
#endif /* HW_USB_REDIRECT_SERVER_H */
diff --git a/hw/usb/redirect-server.c b/hw/usb/redirect-server.c
index c36b5c16c4..824cd274b8 100644
--- a/hw/usb/redirect-server.c
+++ b/hw/usb/redirect-server.c
@@ -56,6 +56,8 @@
#include "qemu/main-loop.h"
#include "qemu/module.h"
#include "migration/vmstate.h"
+#include "qemu/timer.h"
+#include "qemu/cutils.h"
#include "hw/usb/redirect-server.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/qdev-properties-system.h"
@@ -63,10 +65,115 @@
#define USBREDIR_SERVER_VERSION "qemu " TYPE_USB_REDIR_SERVER " " QEMU_VERSION
+/* Wait this long after attach before we announce the device. */
+#define USBREDIR_SERVER_ANNOUNCE_DEBOUNCE_MS 10
+
+/*
+ * The device is whatever USBDevice the user plugged into our port with
+ * "-device <device>,bus=<id>.0". NULL until then.
+ */
+static USBDevice *usbredir_server_device(USBRedirServer *s)
+{
+ return s->port.dev;
+}
+
+/*
+ * Device announcement
+ */
+
+static uint8_t usbredir_server_speed(USBDevice *device)
+{
+ switch (device->speed) {
+ case USB_SPEED_LOW:
+ return usb_redir_speed_low;
+ case USB_SPEED_FULL:
+ return usb_redir_speed_full;
+ default:
+ return usb_redir_speed_high;
+ }
+}
+
+static void usbredir_server_announce_device(USBRedirServer *s)
+{
+ USBDevice *device = usbredir_server_device(s);
+ struct usb_redir_interface_info_header iface_info = {
+ .interface_count = 0,
+ };
+ struct usb_redir_device_connect_header conn = {
+ .speed = usbredir_server_speed(device),
+ };
+
+ if (s->device_announced) {
+ return;
+ }
+ s->device_announced = true;
+
+ /* Put the device in DEFAULT state. The host may not reset the bus. */
+ device->addr = 0;
+ device->state = USB_STATE_DEFAULT;
+
+ /* Send this before device_connect. The peer needs it to accept us. */
+ usbredirparser_send_interface_info(s->parser, &iface_info);
+ usbredirparser_do_write(s->parser);
+
+ trace_usbredir_server_announce(conn.speed);
+ usbredirparser_send_device_connect(s->parser, &conn);
+ usbredirparser_do_write(s->parser);
+}
+
/*
* USB port ops
*/
+static void usbredir_server_schedule_announce(USBRedirServer *s)
+{
+ USBDevice *device = usbredir_server_device(s);
+
+ if (!s->host_connected || s->device_announced || !device ||
+ !device->attached) {
+ return;
+ }
+
+ timer_mod(s->announce_timer,
+ qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) +
+ USBREDIR_SERVER_ANNOUNCE_DEBOUNCE_MS);
+}
+
+static void usbredir_server_do_announce(void *opaque)
+{
+ USBRedirServer *s = opaque;
+ USBDevice *device = usbredir_server_device(s);
+
+ /* Only announce if the device is still attached and the host is here. */
+ if (s->host_connected && s->parser && device && device->attached) {
+ usbredir_server_announce_device(s);
+ }
+}
+
+static void usbredir_server_port_attach(USBPort *port)
+{
+ USBRedirServer *s = port->opaque;
+
+ trace_usbredir_server_attach();
+ usbredir_server_schedule_announce(s);
+}
+
+static void usbredir_server_port_detach(USBPort *port)
+{
+ USBRedirServer *s = port->opaque;
+
+ trace_usbredir_server_detach(s->device_announced);
+
+ timer_del(s->announce_timer);
+
+ if (s->host_connected && s->parser && s->device_announced) {
+ trace_usbredir_server_disconnect();
+ usbredirparser_send_device_disconnect(s->parser);
+ usbredirparser_do_write(s->parser);
+ }
+ s->device_announced = false;
+}
+
static void usbredir_server_port_child_detach(USBPort *port, USBDevice *child)
{
/* We only export the device on our own port. Nothing to do. */
@@ -78,6 +185,8 @@ static void usbredir_server_port_wakeup(USBPort *port)
}
static USBPortOps usbredir_server_port_ops = {
+ .attach = usbredir_server_port_attach,
+ .detach = usbredir_server_port_detach,
.child_detach = usbredir_server_port_child_detach,
.wakeup = usbredir_server_port_wakeup,
};
@@ -181,13 +290,53 @@ static int usbredir_server_write(void *priv, uint8_t *data, int count)
return ret;
}
-/* The remote host greets us once the socket is up. */
+/*
+ * usbredirparser message callbacks
+ */
+
static void usbredir_server_hello(void *priv,
struct usb_redir_hello_header *hello)
{
USBRedirServer *s = priv;
+ char version[sizeof(hello->version) + 1];
+
+ pstrcpy(version, sizeof(version), hello->version);
+ trace_usbredir_server_hello(version);
s->host_connected = true;
+ usbredir_server_schedule_announce(s);
+}
+
+static void usbredir_server_reset(void *priv)
+{
+ USBRedirServer *s = priv;
+ USBDevice *device = usbredir_server_device(s);
+
+ trace_usbredir_server_bus_reset(device && device->attached);
+ usb_device_reset(device);
+}
+
+static void usbredir_server_filter_reject(void *priv)
+{
+ trace_usbredir_server_filter_reject();
+}
+
+static void usbredir_server_filter_filter(void *priv,
+ struct usbredirfilter_rule *rules, int rules_count)
+{
+ /* We accept any host. The callback owns the rules, so free them. */
+ free(rules);
+}
+
+static void usbredir_server_device_disconnect_ack(void *priv)
+{
+ /* The host saw our device_disconnect. Nothing to do. */
+}
+
+static void usbredir_server_interface_info(void *priv,
+ struct usb_redir_interface_info_header *hdr)
+{
+ /* The host should not send this to a device. Nothing to do. */
}
/*
@@ -211,6 +360,14 @@ static void usbredir_server_create_parser(USBRedirServer *s)
/* Callbacks for messages the remote host sends to us */
s->parser->hello_func = usbredir_server_hello;
+ s->parser->reset_func = usbredir_server_reset;
+
+ /* The parser calls these directly, so they must not be NULL. */
+ s->parser->filter_reject_func = usbredir_server_filter_reject;
+ s->parser->filter_filter_func = usbredir_server_filter_filter;
+ s->parser->device_disconnect_ack_func =
+ usbredir_server_device_disconnect_ack;
+ s->parser->interface_info_func = usbredir_server_interface_info;
/* Capabilities: 64-bit IDs, connect_device_version, ep_info sizes */
usbredirparser_caps_set_cap(caps, usb_redir_cap_connect_device_version);
@@ -238,7 +395,10 @@ static void usbredir_server_create_parser(USBRedirServer *s)
static void usbredir_server_destroy_parser(USBRedirServer *s)
{
s->host_connected = false;
+ s->device_announced = false;
+ /* The announce timer may still be pending. */
+ timer_del(s->announce_timer);
g_clear_handle_id(&s->watch, g_source_remove);
if (s->parser) {
@@ -336,6 +496,8 @@ static void usbredir_server_realize(DeviceState *dev, Error **errp)
USB_SPEED_MASK_LOW | USB_SPEED_MASK_FULL |
USB_SPEED_MASK_HIGH);
+ s->announce_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL,
+ usbredir_server_do_announce, s);
s->chardev_close_bh = qemu_bh_new_guarded(usbredir_server_chardev_close_bh,
s, &dev->mem_reentrancy_guard);
@@ -353,6 +515,8 @@ static void usbredir_server_unrealize(DeviceState *dev)
qemu_chr_fe_deinit(&s->cs, true);
usbredir_server_destroy_parser(s);
+ timer_free(s->announce_timer);
+
if (s->chardev_close_bh) {
qemu_bh_delete(s->chardev_close_bh);
s->chardev_close_bh = NULL;
diff --git a/hw/usb/trace-events b/hw/usb/trace-events
index d141661673..2cc6a244b9 100644
--- a/hw/usb/trace-events
+++ b/hw/usb/trace-events
@@ -399,3 +399,10 @@ usbredir_server_chardev_open(void) "chardev opened"
usbredir_server_chardev_close(void) "chardev closed"
usbredir_server_write_recursion(void) "recursive write, leaving it queued"
usbredir_server_log(const char *msg) "%s"
+usbredir_server_hello(const char *version) "peer is %s"
+usbredir_server_attach(void) "device attached"
+usbredir_server_detach(bool announced) "device detached, was announced %d"
+usbredir_server_announce(uint8_t speed) "device_connect speed %u"
+usbredir_server_disconnect(void) "device_disconnect sent"
+usbredir_server_bus_reset(bool attached) "bus reset, attached %d"
+usbredir_server_filter_reject(void) "host rejected our device"
--
2.43.0
next prev parent reply other threads:[~2026-10-05 3:21 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 ` Jamin Lin [this message]
2026-10-05 8:59 ` [PATCH v2 4/8] hw/usb/redirect-server: Announce the exported device 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 ` [PATCH v2 6/8] hw/usb/redirect-server: Implement bulk and interrupt transfers Jamin Lin
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-5-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.