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