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 3/8] hw/usb/redirect-server: Connect usbredirparser to a chardev
Date: Mon, 5 Oct 2026 03:20:27 +0000 [thread overview]
Message-ID: <20261005032022.3980903-4-jamin_lin@aspeedtech.com> (raw)
In-Reply-To: <20261005032022.3980903-1-jamin_lin@aspeedtech.com>
Create a usbredirparser when the chardev opens. Feed it from the chardev
read handler. Destroy it when the chardev closes.
The side that exports a device sets usbredirparser_fl_usb_host. This
side drives the device, so it is the USB host for it. redirect.c is the
other side and does not set the flag. The library uses it to decide
which direction a packet may travel.
The chardev handling follows hw/usb/redirect.c: take no data before the
parser exists, do not write to a closed backend, do not re-enter the
writer, and let a close that is still queued finish before a new
connection opens.
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
include/hw/usb/redirect-server.h | 15 ++
hw/usb/redirect-server.c | 268 +++++++++++++++++++++++++++++++
hw/usb/trace-events | 6 +
3 files changed, 289 insertions(+)
diff --git a/include/hw/usb/redirect-server.h b/include/hw/usb/redirect-server.h
index e81d4e0608..2af881bda9 100644
--- a/include/hw/usb/redirect-server.h
+++ b/include/hw/usb/redirect-server.h
@@ -11,8 +11,11 @@
#include "hw/core/sysbus.h"
#include "hw/usb/usb.h"
+#include "chardev/char-fe.h"
#include "qom/object.h"
+#include <usbredirparser.h>
+
#define TYPE_USB_REDIR_SERVER "usb-redir-server"
OBJECT_DECLARE_SIMPLE_TYPE(USBRedirServer, USB_REDIR_SERVER)
@@ -22,6 +25,18 @@ struct USBRedirServer {
/* USB bus */
USBBus bus;
USBPort port;
+
+ /* Properties */
+ CharFrontend cs;
+
+ /* usbredir over the chardev */
+ struct usbredirparser *parser;
+ QEMUBH *chardev_close_bh;
+ const uint8_t *read_buf;
+ int read_buf_size;
+ bool in_write;
+ guint watch;
+ bool host_connected;
};
#endif /* HW_USB_REDIRECT_SERVER_H */
diff --git a/hw/usb/redirect-server.c b/hw/usb/redirect-server.c
index 5f365f0a35..c36b5c16c4 100644
--- a/hw/usb/redirect-server.c
+++ b/hw/usb/redirect-server.c
@@ -50,10 +50,18 @@
*/
#include "qemu/osdep.h"
+#include "qemu/units.h"
#include "qapi/error.h"
+#include "qemu/error-report.h"
+#include "qemu/main-loop.h"
#include "qemu/module.h"
#include "migration/vmstate.h"
#include "hw/usb/redirect-server.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/core/qdev-properties-system.h"
+#include "trace.h"
+
+#define USBREDIR_SERVER_VERSION "qemu " TYPE_USB_REDIR_SERVER " " QEMU_VERSION
/*
* USB port ops
@@ -81,6 +89,232 @@ static USBPortOps usbredir_server_port_ops = {
static USBBusOps usbredir_server_bus_ops = {
};
+/*
+ * usbredirparser I/O and logging callbacks
+ */
+
+static void usbredir_server_log(void *priv, int level, const char *msg)
+{
+ switch (level) {
+ case usbredirparser_error:
+ error_report(TYPE_USB_REDIR_SERVER ": %s", msg);
+ break;
+ case usbredirparser_warning:
+ warn_report(TYPE_USB_REDIR_SERVER ": %s", msg);
+ break;
+ default:
+ trace_usbredir_server_log(msg);
+ break;
+ }
+}
+
+static int usbredir_server_read(void *priv, uint8_t *data, int count)
+{
+ USBRedirServer *s = priv;
+
+ if (s->read_buf_size < count) {
+ count = s->read_buf_size;
+ }
+
+ memcpy(data, s->read_buf, count);
+
+ s->read_buf_size -= count;
+ if (s->read_buf_size) {
+ s->read_buf += count;
+ } else {
+ s->read_buf = NULL;
+ }
+
+ return count;
+}
+
+static gboolean usbredir_server_write_unblocked(void *do_not_use,
+ GIOCondition cond,
+ void *opaque)
+{
+ USBRedirServer *s = opaque;
+
+ s->watch = 0;
+ usbredirparser_do_write(s->parser);
+
+ return G_SOURCE_REMOVE;
+}
+
+static int usbredir_server_write(void *priv, uint8_t *data, int count)
+{
+ USBRedirServer *s = priv;
+ int ret;
+
+ if (!qemu_chr_fe_backend_open(&s->cs)) {
+ return 0;
+ }
+
+ /*
+ * Re-entry guard. The chain is:
+ * do_write() -> this -> qemu_chr_fe_write() -> chardev feeds us
+ * -> do_read() -> a callback -> do_write() again
+ *
+ * The second do_write() would walk the same buffer queue as the
+ * first. Returning 0 means "sent nothing", so the buffer stays for
+ * the outer one to send.
+ */
+ if (s->in_write) {
+ trace_usbredir_server_write_recursion();
+ return 0;
+ }
+ s->in_write = true;
+
+ ret = qemu_chr_fe_write(&s->cs, data, count);
+ if (ret < count) {
+ if (!s->watch) {
+ s->watch = qemu_chr_fe_add_watch(&s->cs, G_IO_OUT | G_IO_HUP,
+ usbredir_server_write_unblocked,
+ s);
+ }
+ if (ret < 0) {
+ ret = 0;
+ }
+ }
+
+ s->in_write = false;
+
+ return ret;
+}
+
+/* The remote host greets us once the socket is up. */
+static void usbredir_server_hello(void *priv,
+ struct usb_redir_hello_header *hello)
+{
+ USBRedirServer *s = priv;
+
+ s->host_connected = true;
+}
+
+/*
+ * Parser setup and teardown
+ */
+
+static void usbredir_server_create_parser(USBRedirServer *s)
+{
+ uint32_t caps[USB_REDIR_CAPS_SIZE] = {};
+
+ s->parser = usbredirparser_create();
+ if (!s->parser) {
+ error_report(TYPE_USB_REDIR_SERVER ": failed to create usbredirparser");
+ return;
+ }
+
+ s->parser->priv = s;
+ s->parser->log_func = usbredir_server_log;
+ s->parser->read_func = usbredir_server_read;
+ s->parser->write_func = usbredir_server_write;
+
+ /* Callbacks for messages the remote host sends to us */
+ s->parser->hello_func = usbredir_server_hello;
+
+ /* Capabilities: 64-bit IDs, connect_device_version, ep_info sizes */
+ usbredirparser_caps_set_cap(caps, usb_redir_cap_connect_device_version);
+ usbredirparser_caps_set_cap(caps, usb_redir_cap_ep_info_max_packet_size);
+ usbredirparser_caps_set_cap(caps, usb_redir_cap_64bits_ids);
+
+ /*
+ * In USB the host is the side that starts every transfer; a device
+ * only answers. The exported device sits on our bus and we issue
+ * its transfers, so for that device we are the host. That is what
+ * fl_usb_host means, and why the side that exports a device sets it.
+ *
+ * The other QEMU does not drive the device, it receives it. In this
+ * protocol that makes it the client, and redirect.c leaves the flag
+ * clear.
+ *
+ * Without it the library refuses to send device_connect.
+ */
+ usbredirparser_init(s->parser, USBREDIR_SERVER_VERSION,
+ caps, USB_REDIR_CAPS_SIZE,
+ usbredirparser_fl_usb_host);
+ usbredirparser_do_write(s->parser);
+}
+
+static void usbredir_server_destroy_parser(USBRedirServer *s)
+{
+ s->host_connected = false;
+
+ g_clear_handle_id(&s->watch, g_source_remove);
+
+ if (s->parser) {
+ usbredirparser_destroy(s->parser);
+ s->parser = NULL;
+ }
+}
+
+static void usbredir_server_chardev_close_bh(void *opaque)
+{
+ usbredir_server_destroy_parser(opaque);
+}
+
+/*
+ * chardev callbacks
+ */
+
+static int usbredir_server_chardev_can_read(void *opaque)
+{
+ USBRedirServer *s = opaque;
+
+ if (!s->parser) {
+ return 0;
+ }
+ /* usbredirparser_do_read() consumes everything we hand it */
+ return 1 * MiB;
+}
+
+static void usbredir_server_chardev_read(void *opaque, const uint8_t *buf,
+ int size)
+{
+ USBRedirServer *s = opaque;
+
+ if (!s->parser) {
+ return;
+ }
+
+ /* No recursion allowed */
+ assert(s->read_buf == NULL);
+
+ s->read_buf = buf;
+ s->read_buf_size = size;
+
+ usbredirparser_do_read(s->parser);
+ /* do_read() ran our callbacks; flush whatever replies they queued */
+ usbredirparser_do_write(s->parser);
+}
+
+static void usbredir_server_chardev_event(void *opaque,
+ QEMUChrEvent event)
+{
+ USBRedirServer *s = opaque;
+
+ switch (event) {
+ case CHR_EVENT_OPENED:
+ trace_usbredir_server_chardev_open();
+ /*
+ * A close event only schedules chardev_close_bh. If it has not
+ * run yet, it would destroy the parser we are about to create,
+ * so run it now and cancel it.
+ */
+ usbredir_server_chardev_close_bh(s);
+ qemu_bh_cancel(s->chardev_close_bh);
+ usbredir_server_create_parser(s);
+ break;
+ case CHR_EVENT_CLOSED:
+ trace_usbredir_server_chardev_close();
+ qemu_bh_schedule(s->chardev_close_bh);
+ break;
+ case CHR_EVENT_BREAK:
+ case CHR_EVENT_MUX_IN:
+ case CHR_EVENT_MUX_OUT:
+ break;
+ }
+}
+
/*
* Device registration
*/
@@ -89,14 +323,46 @@ static void usbredir_server_realize(DeviceState *dev, Error **errp)
{
USBRedirServer *s = USB_REDIR_SERVER(dev);
+ if (!qemu_chr_fe_backend_connected(&s->cs)) {
+ error_setg(errp,
+ TYPE_USB_REDIR_SERVER ": 'chardev' property must be set");
+ return;
+ }
+
/* One port: usbredir carries a single device. */
usb_bus_new(&s->bus, sizeof(s->bus), &usbredir_server_bus_ops, dev);
s->bus.no_auto_hub = true;
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);
+
+ qemu_chr_fe_set_handlers(&s->cs,
+ usbredir_server_chardev_can_read,
+ usbredir_server_chardev_read,
+ usbredir_server_chardev_event,
+ NULL, s, NULL, true);
}
+static void usbredir_server_unrealize(DeviceState *dev)
+{
+ USBRedirServer *s = USB_REDIR_SERVER(dev);
+
+ qemu_chr_fe_deinit(&s->cs, true);
+ usbredir_server_destroy_parser(s);
+
+ if (s->chardev_close_bh) {
+ qemu_bh_delete(s->chardev_close_bh);
+ s->chardev_close_bh = NULL;
+ }
+}
+
+static const Property usbredir_server_props[] = {
+ DEFINE_PROP_CHR("chardev", USBRedirServer, cs),
+};
+
/*
* The link to the remote host is a chardev, and we cannot migrate
* that. So this device cannot be migrated either.
@@ -112,8 +378,10 @@ static void usbredir_server_class_init(ObjectClass *klass, const void *data)
dc->desc = "USB Redirection Server";
dc->realize = usbredir_server_realize;
+ dc->unrealize = usbredir_server_unrealize;
dc->vmsd = &vmstate_usbredir_server;
set_bit(DEVICE_CATEGORY_USB, dc->categories);
+ device_class_set_props(dc, usbredir_server_props);
}
static const TypeInfo usbredir_server_types[] = {
diff --git a/hw/usb/trace-events b/hw/usb/trace-events
index 80ead23358..d141661673 100644
--- a/hw/usb/trace-events
+++ b/hw/usb/trace-events
@@ -393,3 +393,9 @@ aspeed_udc_handle_data(int ep_nr, const char *dir, uint32_t iov, int ep_idx) "ep
aspeed_udc_ep_data_in(unsigned ep, uint32_t rptr, uint32_t wptr, uint32_t iov) "ep %u, rptr %u, wptr %u, iov %u"
aspeed_udc_ep_data_out(unsigned ep, uint32_t wptr, uint32_t avail, uint32_t iov) "ep %u, wptr %u, avail %u, iov %u"
aspeed_udc_ep_ack(unsigned ep) "ep %u"
+
+# redirect-server.c
+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"
--
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 ` Jamin Lin [this message]
2026-10-05 8:59 ` [PATCH v2 3/8] hw/usb/redirect-server: Connect usbredirparser to a chardev 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 ` [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-4-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.