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 v1 04/19] hw/usb/redirect-server: Announce the exported device
Date: Tue, 22 Sep 2026 09:02:00 +0000	[thread overview]
Message-ID: <20260922090154.1792077-5-jamin_lin@aspeedtech.com> (raw)
In-Reply-To: <20260922090154.1792077-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.53.0


  parent reply	other threads:[~2026-09-22  9:03 UTC|newest]

Thread overview: 24+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-22  9:01 [PATCH v1 00/19] hw/usb: Add a usbredir server transport and AST1030 UDC support Jamin Lin
2026-09-22  9:01 ` [PATCH v1 01/19] hw/usb/bus: Let a bus opt out of automatic hub insertion Jamin Lin
2026-09-22  9:01 ` [PATCH v1 02/19] hw/usb/redirect-server: Add a usbredir server device Jamin Lin
2026-09-22  9:01 ` [PATCH v1 03/19] hw/usb/redirect-server: Connect usbredirparser to a chardev Jamin Lin
2026-09-22  9:02 ` Jamin Lin [this message]
2026-09-22  9:02 ` [PATCH v1 05/19] hw/usb/redirect-server: Implement control transfers Jamin Lin
2026-09-22  9:02 ` [PATCH v1 06/19] hw/usb/redirect-server: Implement bulk and interrupt transfers Jamin Lin
2026-09-22  9:02 ` [PATCH v1 07/19] hw/usb/redirect-server: Stream interrupt IN endpoints Jamin Lin
2026-09-22  9:02 ` [PATCH v1 08/19] hw/arm/aspeed: Enable the usbredir server transport Jamin Lin
2026-09-22  9:02 ` [PATCH v1 09/19] hw/usb/aspeed-udc: Make the SETUP buffer byte addressable Jamin Lin
2026-09-22  9:02 ` [PATCH v1 10/19] hw/usb/aspeed-udc: Mask the reserved bits on register writes Jamin Lin
2026-09-22  9:02 ` [PATCH v1 11/19] hw/usb/aspeed-udc: Clear EP_POOL_ACK when no endpoint ack is left Jamin Lin
2026-09-22  9:02 ` [PATCH v1 12/19] hw/usb/aspeed-udc: Rename the transfer helpers after their DMA mode Jamin Lin
2026-09-22  9:02 ` [PATCH v1 13/19] hw/usb/aspeed-udc: Store the EP_DMA_STS write first Jamin Lin
2026-09-22  9:02 ` [PATCH v1 14/19] hw/usb/aspeed-udc: Pick the EP_DMA_STS path by direction Jamin Lin
2026-09-22  9:02 ` [PATCH v1 15/19] hw/usb/aspeed-udc: Support single-stage IN transfers Jamin Lin
2026-09-22  9:02 ` [PATCH v1 16/19] hw/usb/aspeed-udc: Use the endpoint max packet size for OUT transfers Jamin Lin
2026-09-22  9:02 ` [PATCH v1 17/19] hw/usb/aspeed-udc: Add trace events for the transfer paths Jamin Lin
2026-09-22  9:02 ` [PATCH v1 18/19] hw/arm/aspeed_ast10x0: Add the USB device controller Jamin Lin
2026-09-22  9:02 ` [PATCH v1 19/19] tests/functional/arm/test_aspeed_ast1030: Test the usbredir server transport Jamin Lin
2026-09-30  7:48 ` [PATCH v1 00/19] hw/usb: Add a usbredir server transport and AST1030 UDC support Jamin Lin
2026-09-30  7:59   ` Cédric Le Goater
2026-09-30  8:05     ` Jamin Lin
2026-09-30  8:22       ` Cédric Le Goater

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=20260922090154.1792077-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.