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 <[email protected]> --- 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
