Add "usb-redir-server". It exports a USB device from this QEMU to a USB host in another QEMU, using the usbredir protocol. The other side is the existing "usb-redir" device.
The exported device is plugged in with "-device <dev>,bus=<id>.0". So we have to provide a USB bus. To find that bus, qdev starts at the main system bus and walks down the tree. A device that is not on a bus is not in the tree, so the bus it provides cannot be found. We have to sit on the main system bus. That makes this a sysbus device, even though it has no MMIO and no IRQ. usbredir carries one device, not a bus. A hub cannot be exported because the protocol has no device address field, so only the device on the first port is redirected. To export several devices, run one usb-redir-server per device, each with its own chardev. The link to the remote host is a chardev, so this device cannot be migrated. Its VMStateDescription says so with unmigratable. Signed-off-by: Jamin Lin <[email protected]> --- include/hw/usb/redirect-server.h | 27 +++++++ hw/usb/redirect-server.c | 130 +++++++++++++++++++++++++++++++ hw/usb/meson.build | 3 +- 3 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 include/hw/usb/redirect-server.h create mode 100644 hw/usb/redirect-server.c diff --git a/include/hw/usb/redirect-server.h b/include/hw/usb/redirect-server.h new file mode 100644 index 0000000000..e81d4e0608 --- /dev/null +++ b/include/hw/usb/redirect-server.h @@ -0,0 +1,27 @@ +/* + * USB redirector, server side + * + * Copyright (c) 2026 ASPEED Technology Inc. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_USB_REDIRECT_SERVER_H +#define HW_USB_REDIRECT_SERVER_H + +#include "hw/core/sysbus.h" +#include "hw/usb/usb.h" +#include "qom/object.h" + +#define TYPE_USB_REDIR_SERVER "usb-redir-server" +OBJECT_DECLARE_SIMPLE_TYPE(USBRedirServer, USB_REDIR_SERVER) + +struct USBRedirServer { + SysBusDevice parent_obj; + + /* USB bus */ + USBBus bus; + USBPort port; +}; + +#endif /* HW_USB_REDIRECT_SERVER_H */ diff --git a/hw/usb/redirect-server.c b/hw/usb/redirect-server.c new file mode 100644 index 0000000000..5f365f0a35 --- /dev/null +++ b/hw/usb/redirect-server.c @@ -0,0 +1,130 @@ +/* + * USB redirector, server side + * + * Copyright (c) 2026 ASPEED Technology Inc. + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Exports a locally emulated USB device to a remote USB host over the + * usbredir protocol, so a device emulated in one QEMU instance can be + * enumerated by a host controller emulated in another one. + * + * Architecture. The left column is a request going to the device. The + * right column is the answer coming back. The middle hop carries + * usbredir messages over a socket. The top and bottom hops carry + * USBPackets inside QEMU. + * + * remote QEMU: guest driver -> EHCI/XHCI + * | ^ + * USBPacket | | USBPacket + * v | + * "usb-redir" (the client) + * | ^ + * usbredir | chardev socket | usbredir + * v | + * usb-redir-server (the server, this file) + * | ^ + * USBPacket | | USBPacket + * v | + * any USBDevice, "-device <dev>,bus=<id>.0" + * + * "usb-redir" (hw/usb/redirect.c) is the client: + * - it takes a USBPacket from the remote guest and writes it to the + * socket as a usbredir message + * - it reads the answer from the socket and completes the USBPacket + * + * This file is the server. It does the same thing, but backwards: + * - it reads a usbredir message from the socket and runs it as a + * USBPacket on the bus below + * - it takes the result of that USBPacket and writes it back to the + * same socket as a usbredir message, for the client to read + * + * A USB device has to sit on a USB bus, and in QEMU a USB bus is always + * made by a host controller. So this file makes one and acts as the + * host controller on this side. It models no real chip: its cable is + * the chardev socket. The real host is in the other QEMU. + * + * usbredir carries one device, not a bus. A hub cannot be exported: + * the protocol has no device address field. To export several devices, + * run one usb-redir-server per device, each with its own chardev. + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "qemu/module.h" +#include "migration/vmstate.h" +#include "hw/usb/redirect-server.h" + +/* + * USB port ops + */ + +static void usbredir_server_port_child_detach(USBPort *port, USBDevice *child) +{ + /* We only export the device on our own port. Nothing to do. */ +} + +static void usbredir_server_port_wakeup(USBPort *port) +{ + /* We do not pass remote wakeup to the host. Nothing to do. */ +} + +static USBPortOps usbredir_server_port_ops = { + .child_detach = usbredir_server_port_child_detach, + .wakeup = usbredir_server_port_wakeup, +}; + +/* + * USB bus ops + */ + +static USBBusOps usbredir_server_bus_ops = { +}; + +/* + * Device registration + */ + +static void usbredir_server_realize(DeviceState *dev, Error **errp) +{ + USBRedirServer *s = USB_REDIR_SERVER(dev); + + /* 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); +} + +/* + * The link to the remote host is a chardev, and we cannot migrate + * that. So this device cannot be migrated either. + */ +static const VMStateDescription vmstate_usbredir_server = { + .name = TYPE_USB_REDIR_SERVER, + .unmigratable = 1, +}; + +static void usbredir_server_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->desc = "USB Redirection Server"; + dc->realize = usbredir_server_realize; + dc->vmsd = &vmstate_usbredir_server; + set_bit(DEVICE_CATEGORY_USB, dc->categories); +} + +static const TypeInfo usbredir_server_types[] = { + { + .name = TYPE_USB_REDIR_SERVER, + .parent = TYPE_DYNAMIC_SYS_BUS_DEVICE, + .instance_size = sizeof(USBRedirServer), + .class_init = usbredir_server_class_init, + }, +}; +module_obj(TYPE_USB_REDIR_SERVER); +module_kconfig(USB); + +DEFINE_TYPES(usbredir_server_types) diff --git a/hw/usb/meson.build b/hw/usb/meson.build index d4ba60a91c..8e95cc7b74 100644 --- a/hw/usb/meson.build +++ b/hw/usb/meson.build @@ -77,7 +77,8 @@ endif if usbredir.found() usbredir_ss = ss.source_set() usbredir_ss.add(when: 'CONFIG_USB', - if_true: [usbredir, files('redirect.c', 'quirks.c')]) + if_true: [usbredir, files('redirect.c', 'quirks.c', + 'redirect-server.c')]) hw_usb_modules += {'redirect': usbredir_ss} endif -- 2.53.0
