The host sends a control_packet. Turn it into the SETUP, DATA and STATUS tokens the QEMU USB core wants, run them on the device, and send the answer back.
The device may answer later, so we have to remember the packets we sent. Each one stays on a list until it comes back. We drop the whole list on a bus reset or a chardev close. When the host asks us to cancel a packet, we answer it and drop it. usbredir has its own messages for set_configuration, get_configuration, set_alt_setting and get_alt_setting. Each one is a control transfer, so run it like one and send back the status message. usbredir wants an ep_info message. It says what kind each endpoint is: bulk, interrupt or control. QEMU cannot tell us. It only knows after it has read the device's descriptors, and it never reads them for a device that just forwards transfers. So we read them ourselves: the configuration descriptor passes through us on its way to the host. Signed-off-by: Jamin Lin <[email protected]> --- include/hw/usb/redirect-server.h | 46 +++ hw/usb/redirect-server.c | 603 ++++++++++++++++++++++++++++++- hw/usb/trace-events | 5 + 3 files changed, 653 insertions(+), 1 deletion(-) diff --git a/include/hw/usb/redirect-server.h b/include/hw/usb/redirect-server.h index a3d5084424..83c4524040 100644 --- a/include/hw/usb/redirect-server.h +++ b/include/hw/usb/redirect-server.h @@ -15,10 +15,46 @@ #include "qom/object.h" #include <usbredirparser.h> +#include <usbredirproto.h> #define TYPE_USB_REDIR_SERVER "usb-redir-server" OBJECT_DECLARE_SIMPLE_TYPE(USBRedirServer, USB_REDIR_SERVER) +/* + * The usbredir ep_info message has one slot per endpoint and direction. + * Index 0-15 are the OUT endpoints, 16-31 the IN ones. + */ +#define USBREDIR_SERVER_MAX_EP 32 +#define USBREDIR_SERVER_EP_IN_BASE 16 + +#define USBREDIR_SERVER_CTRL_SETUP 0 +#define USBREDIR_SERVER_CTRL_STATUS 1 + +/* Which message answers the host when a control transfer ends. */ +typedef enum { + USBREDIR_SERVER_REPLY_CONTROL, + USBREDIR_SERVER_REPLY_CONFIG, + USBREDIR_SERVER_REPLY_ALT, +} USBRedirServerReply; + +typedef struct USBRedirServerPkt { + USBPacket pkt; + + /* Saved headers for the usbredir response */ + struct usb_redir_control_packet_header ctrl_hdr; + + /* + * The IOV points here. IN data lands in it, OUT data is copied in. + * Allocated and freed with the packet; data_size is how big it is. + */ + uint8_t *data; + int data_size; + QTAILQ_ENTRY(USBRedirServerPkt) next; + USBRedirServerReply reply; + uint64_t redir_id; + int type; +} USBRedirServerPkt; + struct USBRedirServer { SysBusDevice parent_obj; @@ -39,6 +75,16 @@ struct USBRedirServer { guint watch; bool host_connected; bool device_announced; + + /* In-flight packet tracking */ + QTAILQ_HEAD(, USBRedirServerPkt) inflight; + uint64_t next_id; + + /* Endpoint tables for the usbredir ep_info message. */ + uint8_t ep_type[USBREDIR_SERVER_MAX_EP]; + uint16_t ep_max_packet[USBREDIR_SERVER_MAX_EP]; + uint8_t ep_interval[USBREDIR_SERVER_MAX_EP]; + uint8_t ep_iface[USBREDIR_SERVER_MAX_EP]; }; #endif /* HW_USB_REDIRECT_SERVER_H */ diff --git a/hw/usb/redirect-server.c b/hw/usb/redirect-server.c index 824cd274b8..5005ea5f8a 100644 --- a/hw/usb/redirect-server.c +++ b/hw/usb/redirect-server.c @@ -61,6 +61,7 @@ #include "hw/usb/redirect-server.h" #include "hw/core/qdev-properties.h" #include "hw/core/qdev-properties-system.h" +#include "desc.h" #include "trace.h" #define USBREDIR_SERVER_VERSION "qemu " TYPE_USB_REDIR_SERVER " " QEMU_VERSION @@ -68,6 +69,11 @@ /* Wait this long after attach before we announce the device. */ #define USBREDIR_SERVER_ANNOUNCE_DEBOUNCE_MS 10 +static void usbredir_server_pkt_free(USBRedirServerPkt *rp); +static void usbredir_server_stop_transfers(USBRedirServer *s); +static void usbredir_server_send_cancelled(USBRedirServer *s, + USBRedirServerPkt *rp); + /* * The device is whatever USBDevice the user plugged into our port with * "-device <device>,bus=<id>.0". NULL until then. @@ -78,8 +84,97 @@ static USBDevice *usbredir_server_device(USBRedirServer *s) } /* - * Device announcement + * Descriptor snooping and device announcement + */ + +static void usbredir_server_record_endpoint(USBRedirServer *s, + const USBDescriptor *desc, + uint8_t iface) +{ + uint8_t addr; + int ep_nr; + int idx; + + if (desc->bLength < 7) { + return; + } + + addr = desc->u.endpoint.bEndpointAddress; + ep_nr = addr & 0x0f; + idx = (addr & USB_DIR_IN) ? ep_nr + USBREDIR_SERVER_EP_IN_BASE : ep_nr; + if (ep_nr < 1 || idx >= USBREDIR_SERVER_MAX_EP) { + return; + } + + s->ep_type[idx] = desc->u.endpoint.bmAttributes & 0x03; + s->ep_max_packet[idx] = (desc->u.endpoint.wMaxPacketSize_hi << 8) | + desc->u.endpoint.wMaxPacketSize_lo; + s->ep_interval[idx] = desc->u.endpoint.bInterval; + s->ep_iface[idx] = iface; +} + +/* + * A device that only passes transfers through never gets its endpoint + * types filled in: they stay INVALID and there is nothing to put in + * ep_info. Take them from the configuration descriptor as it goes past. + * Without them the host sees type 255 and refuses to move data. */ +static void usbredir_server_snoop_config_desc(USBRedirServer *s, + const uint8_t *data, int len) +{ + const USBDescriptor *desc; + uint8_t iface = 0; + int i = 0; + + while (i + 2 <= len) { + desc = (const USBDescriptor *)(data + i); + + if (desc->bLength < 2 || i + desc->bLength > len) { + break; + } + + if (desc->bDescriptorType == USB_DT_INTERFACE && desc->bLength >= 3) { + iface = desc->u.interface.bInterfaceNumber; + } else if (desc->bDescriptorType == USB_DT_ENDPOINT) { + usbredir_server_record_endpoint(s, desc, iface); + } + + i += desc->bLength; + } +} + +static void usbredir_server_send_ep_info(USBRedirServer *s) +{ + struct usb_redir_ep_info_header ep_info = {}; + int i; + + for (i = 0; i < USBREDIR_SERVER_MAX_EP; i++) { + ep_info.type[i] = s->ep_type[i]; + ep_info.max_packet_size[i] = s->ep_max_packet[i]; + ep_info.interface[i] = s->ep_iface[i]; + /* + * bInterval says how often to poll this endpoint. Pass on what + * the descriptor said, but never 0 for an interrupt or isochronous + * endpoint: redirect.c throws the whole device away when it sees + * 0, so send 1 instead. + */ + ep_info.interval[i] = s->ep_interval[i]; + if (ep_info.interval[i] == 0 && + (s->ep_type[i] == USB_ENDPOINT_XFER_INT || + s->ep_type[i] == USB_ENDPOINT_XFER_ISOC)) { + ep_info.interval[i] = 1; + } + if (s->ep_type[i] != USB_ENDPOINT_XFER_INVALID) { + trace_usbredir_server_ep_info(i, ep_info.type[i], + ep_info.max_packet_size[i], + ep_info.interval[i], + ep_info.interface[i]); + } + } + + usbredirparser_send_ep_info(s->parser, &ep_info); + usbredirparser_do_write(s->parser); +} static uint8_t usbredir_server_speed(USBDevice *device) { @@ -119,6 +214,146 @@ static void usbredir_server_announce_device(USBRedirServer *s) trace_usbredir_server_announce(conn.speed); usbredirparser_send_device_connect(s->parser, &conn); usbredirparser_do_write(s->parser); + + usbredir_server_send_ep_info(s); +} + +/* + * Packet completion + */ + +static uint8_t usbredir_server_status(int status) +{ + switch (status) { + case USB_RET_SUCCESS: + return usb_redir_success; + case USB_RET_STALL: + return usb_redir_stall; + case USB_RET_BABBLE: + return usb_redir_babble; + default: + return usb_redir_ioerror; + } +} + +/* + * The device answered the setup token of an IN control request. What it + * wants to send is now in device->data_buf, so run the data and status + * stages and pass the answer to the host. + * + * Each stage reuses rp->pkt, and usb_packet_setup() asserts iov->iov is + * not NULL, so call qemu_iovec_init() before every stage. + */ +static void usbredir_server_ctrl_setup_complete(USBRedirServer *s, + USBRedirServerPkt *rp) +{ + struct usb_redir_control_packet_header resp = rp->ctrl_hdr; + struct usb_redir_configuration_status_header cfg = {}; + struct usb_redir_alt_setting_status_header alt = { + .interface = rp->ctrl_hdr.index, + }; + USBDevice *device = usbredir_server_device(s); + USBEndpoint *ep_out = usb_ep_get(device, USB_TOKEN_OUT, 0); + USBEndpoint *ep_in = usb_ep_get(device, USB_TOKEN_IN, 0); + uint8_t status = usbredir_server_status(rp->pkt.status); + int actual = 0; + + if (rp->pkt.status == USB_RET_SUCCESS) { + /* Data stage: the core copies device->data_buf into our buffer. */ + qemu_iovec_init(&rp->pkt.iov, 1); + usb_packet_setup(&rp->pkt, USB_TOKEN_IN, ep_in, + 0, s->next_id++, false, false); + usb_packet_addbuf(&rp->pkt, rp->data, rp->data_size); + usb_handle_packet(device, &rp->pkt); + actual = rp->pkt.actual_length; + usb_packet_cleanup(&rp->pkt); + + /* Status stage: tell the device we got it. Its EP0 goes idle. */ + qemu_iovec_init(&rp->pkt.iov, 1); + usb_packet_setup(&rp->pkt, USB_TOKEN_OUT, ep_out, + 0, s->next_id++, false, false); + usb_handle_packet(device, &rp->pkt); + usb_packet_cleanup(&rp->pkt); + + if (rp->ctrl_hdr.request == USB_REQ_GET_DESCRIPTOR && + (rp->ctrl_hdr.value >> 8) == USB_DT_CONFIG && actual > 0) { + usbredir_server_snoop_config_desc(s, rp->data, actual); + } + } + + trace_usbredir_server_ctrl_setup_complete(rp->redir_id, status, + actual); + + switch (rp->reply) { + case USBREDIR_SERVER_REPLY_CONTROL: + resp.status = status; + resp.length = actual; + usbredirparser_send_control_packet(s->parser, rp->redir_id, &resp, + actual > 0 ? rp->data : NULL, + actual); + break; + case USBREDIR_SERVER_REPLY_CONFIG: + cfg.status = status; + cfg.configuration = actual > 0 ? rp->data[0] : 0; + usbredirparser_send_configuration_status(s->parser, rp->redir_id, + &cfg); + break; + case USBREDIR_SERVER_REPLY_ALT: + alt.status = status; + alt.alt = actual > 0 ? rp->data[0] : 0; + usbredirparser_send_alt_setting_status(s->parser, rp->redir_id, + &alt); + break; + } + usbredirparser_do_write(s->parser); +} + +/* + * The status stage of an OUT control request finished, so the device is + * done. Tell the host how it went, with whichever message it is waiting + * for. + */ +static void usbredir_server_ctrl_status_complete(USBRedirServer *s, + USBRedirServerPkt *rp) +{ + struct usb_redir_control_packet_header resp = rp->ctrl_hdr; + uint8_t status = usbredir_server_status(rp->pkt.status); + struct usb_redir_configuration_status_header cfg = { + .configuration = rp->ctrl_hdr.value, + }; + struct usb_redir_alt_setting_status_header alt = { + .interface = rp->ctrl_hdr.index, + .alt = rp->ctrl_hdr.value, + }; + + trace_usbredir_server_ctrl_status_complete(rp->redir_id, status); + + switch (rp->reply) { + case USBREDIR_SERVER_REPLY_CONTROL: + resp.status = status; + resp.length = 0; + usbredirparser_send_control_packet(s->parser, rp->redir_id, + &resp, NULL, 0); + break; + case USBREDIR_SERVER_REPLY_CONFIG: + cfg.status = status; + usbredirparser_send_configuration_status(s->parser, rp->redir_id, + &cfg); + break; + case USBREDIR_SERVER_REPLY_ALT: + alt.status = status; + usbredirparser_send_alt_setting_status(s->parser, rp->redir_id, + &alt); + break; + } + usbredirparser_do_write(s->parser); + + /* The endpoint list changed. Send the new one. */ + if (status == usb_redir_success && + (rp->ctrl_hdr.request == USB_REQ_SET_CONFIGURATION || + rp->ctrl_hdr.request == USB_REQ_SET_INTERFACE)) { + usbredir_server_send_ep_info(s); + } } /* @@ -166,12 +401,20 @@ static void usbredir_server_port_detach(USBPort *port) timer_del(s->announce_timer); + usbredir_server_stop_transfers(s); + 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; + + /* Clear the endpoint tables: they belong to the device that is leaving. */ + memset(s->ep_type, USB_ENDPOINT_XFER_INVALID, sizeof(s->ep_type)); + memset(s->ep_max_packet, 0, sizeof(s->ep_max_packet)); + memset(s->ep_interval, 0, sizeof(s->ep_interval)); + memset(s->ep_iface, 0, sizeof(s->ep_iface)); } static void usbredir_server_port_child_detach(USBPort *port, USBDevice *child) @@ -184,11 +427,42 @@ static void usbredir_server_port_wakeup(USBPort *port) /* We do not pass remote wakeup to the host. Nothing to do. */ } +/* + * The core calls this for a packet the device answered with + * USB_RET_ASYNC. usbredir_server_submit_to_device() calls it for the rest. + */ +static void usbredir_server_packet_complete(USBPort *port, USBPacket *p) +{ + USBRedirServer *s = port->opaque; + USBRedirServerPkt *rp = container_of(p, USBRedirServerPkt, pkt); + USBDevice *device = usbredir_server_device(s); + + QTAILQ_REMOVE(&s->inflight, rp, next); + usb_packet_cleanup(&rp->pkt); + + if (!s->parser || !device) { + usbredir_server_pkt_free(rp); + return; + } + + switch (rp->type) { + case USBREDIR_SERVER_CTRL_SETUP: + usbredir_server_ctrl_setup_complete(s, rp); + break; + case USBREDIR_SERVER_CTRL_STATUS: + usbredir_server_ctrl_status_complete(s, rp); + break; + } + + usbredir_server_pkt_free(rp); +} + 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, + .complete = usbredir_server_packet_complete, }; /* @@ -198,6 +472,54 @@ static USBPortOps usbredir_server_port_ops = { static USBBusOps usbredir_server_bus_ops = { }; +/* + * Submit a packet to the device. The core only calls our completion + * callback when the device answers USB_RET_ASYNC, so call it here for + * the rest. + */ +static void usbredir_server_submit_to_device(USBRedirServer *s, + USBRedirServerPkt *rp) +{ + QTAILQ_INSERT_TAIL(&s->inflight, rp, next); + usb_handle_packet(usbredir_server_device(s), &rp->pkt); + if (rp->pkt.status != USB_RET_ASYNC) { + usbredir_server_packet_complete(&s->port, &rp->pkt); + } +} + +static USBRedirServerPkt *usbredir_server_pkt_alloc(int size) +{ + USBRedirServerPkt *rp = g_new0(USBRedirServerPkt, 1); + + qemu_iovec_init(&rp->pkt.iov, 1); + rp->data = g_malloc0(size); + rp->data_size = size; + return rp; +} + +static void usbredir_server_pkt_free(USBRedirServerPkt *rp) +{ + g_free(rp->data); + g_free(rp); +} + +/* + * Take @rp off the list and free it. Do not use usb_packet_complete(): + * it calls back into usbredir_server_packet_complete(), which would + * remove the entry a second time and free it. usb_cancel_packet() only + * tells the device to let go. + */ +static void usbredir_server_drop_pkt(USBRedirServer *s, + USBRedirServerPkt *rp) +{ + QTAILQ_REMOVE(&s->inflight, rp, next); + if (usb_packet_is_inflight(&rp->pkt)) { + usb_cancel_packet(&rp->pkt); + } + usb_packet_cleanup(&rp->pkt); + usbredir_server_pkt_free(rp); +} + /* * usbredirparser I/O and logging callbacks */ @@ -313,9 +635,199 @@ static void usbredir_server_reset(void *priv) USBDevice *device = usbredir_server_device(s); trace_usbredir_server_bus_reset(device && device->attached); + if (!device || !device->attached) { + return; + } + + usbredir_server_stop_transfers(s); usb_device_reset(device); } +/* + * Run one control transfer on the device. @reply says which message + * answers the host when the transfer finishes. + */ +static void usbredir_server_do_control(USBRedirServer *s, uint64_t id, + struct usb_redir_control_packet_header *hdr, + uint8_t *data, int data_len, USBRedirServerReply reply) +{ + USBDevice *device = usbredir_server_device(s); + USBRedirServerPkt *rp; + USBEndpoint *ep_out; + USBEndpoint *ep_in; + USBEndpoint *ep0; + bool is_in; + int size; + + if (!s->host_connected || !device || !device->attached) { + return; + } + + trace_usbredir_server_control(id, hdr->requesttype, hdr->request, + hdr->value, hdr->index, hdr->length); + + is_in = !!(hdr->requesttype & USB_DIR_IN); + + ep0 = usb_ep_get(device, USB_TOKEN_SETUP, 0); + + /* Room for the setup bytes and for the data of either direction. */ + size = MAX(hdr->length, data_len); + size = MAX(size, (int)sizeof(device->setup_buf)); + + rp = usbredir_server_pkt_alloc(size); + rp->redir_id = id; + rp->reply = reply; + rp->ctrl_hdr = *hdr; + + /* + * Build the raw 8-byte SETUP packet in rp->data. The IN path + * overwrites it later with the answer from the device. + */ + rp->data[0] = hdr->requesttype; + rp->data[1] = hdr->request; + rp->data[2] = hdr->value & 0xff; + rp->data[3] = (hdr->value >> 8) & 0xff; + rp->data[4] = hdr->index & 0xff; + rp->data[5] = (hdr->index >> 8) & 0xff; + rp->data[6] = hdr->length & 0xff; + rp->data[7] = (hdr->length >> 8) & 0xff; + + if (is_in) { + /* + * An IN request. The device starts the work on the setup token + * and may take its time, so send the token and pick the rest up + * in usbredir_server_ctrl_setup_complete(). + */ + rp->type = USBREDIR_SERVER_CTRL_SETUP; + usb_packet_setup(&rp->pkt, USB_TOKEN_SETUP, ep0, + 0, s->next_id++, false, false); + usb_packet_addbuf(&rp->pkt, rp->data, sizeof(device->setup_buf)); + usbredir_server_submit_to_device(s, rp); + } else { + /* + * An OUT request. The device only stores the setup bytes now and + * does the work on the status stage, so run the first two stages + * here and wait on the last one. + */ + ep_in = usb_ep_get(device, USB_TOKEN_IN, 0); + ep_out = usb_ep_get(device, USB_TOKEN_OUT, 0); + + /* Setup stage: hand the device the 8 setup bytes. */ + usb_packet_setup(&rp->pkt, USB_TOKEN_SETUP, ep0, + 0, s->next_id++, false, false); + usb_packet_addbuf(&rp->pkt, rp->data, sizeof(device->setup_buf)); + usb_handle_packet(device, &rp->pkt); + usb_packet_cleanup(&rp->pkt); + + /* Data stage: send the bytes that came with the request. */ + if (data_len > 0) { + memcpy(rp->data, data, data_len); + qemu_iovec_init(&rp->pkt.iov, 1); + usb_packet_setup(&rp->pkt, USB_TOKEN_OUT, ep_out, + 0, s->next_id++, false, false); + usb_packet_addbuf(&rp->pkt, rp->data, data_len); + /* The core copies our bytes into device->data_buf. */ + usb_handle_packet(device, &rp->pkt); + usb_packet_cleanup(&rp->pkt); + } + + /* Status stage: the device does the work here, so wait for it. */ + rp->type = USBREDIR_SERVER_CTRL_STATUS; + qemu_iovec_init(&rp->pkt.iov, 1); + usb_packet_setup(&rp->pkt, USB_TOKEN_IN, ep_in, + 0, s->next_id++, false, false); + /* async; the answer comes in usbredir_server_ctrl_status_complete() */ + usbredir_server_submit_to_device(s, rp); + } +} + +static void usbredir_server_control_packet(void *priv, uint64_t id, + struct usb_redir_control_packet_header *hdr, + uint8_t *data, int data_len) +{ + usbredir_server_do_control(priv, id, hdr, data, data_len, + USBREDIR_SERVER_REPLY_CONTROL); +} + +static void usbredir_server_set_configuration(void *priv, uint64_t id, + struct usb_redir_set_configuration_header *hdr) +{ + struct usb_redir_control_packet_header ctrl = { + .endpoint = 0, + .request = USB_REQ_SET_CONFIGURATION, + /* Host->Device, Standard, Device */ + .requesttype = 0x00, + .status = 0, + .value = hdr->configuration, + .index = 0, + .length = 0, + }; + USBRedirServer *s = priv; + + /* The host gets a configuration_status when the device answers. */ + usbredir_server_do_control(s, id, &ctrl, NULL, 0, + USBREDIR_SERVER_REPLY_CONFIG); +} + +static void usbredir_server_get_configuration(void *priv, uint64_t id) +{ + struct usb_redir_control_packet_header ctrl = { + .endpoint = 0, + .request = USB_REQ_GET_CONFIGURATION, + /* Device->Host, Standard, Device */ + .requesttype = 0x80, + .status = 0, + .value = 0, + .index = 0, + .length = 1, + }; + USBRedirServer *s = priv; + + /* The host gets a configuration_status when the device answers. */ + usbredir_server_do_control(s, id, &ctrl, NULL, 0, + USBREDIR_SERVER_REPLY_CONFIG); +} + +static void usbredir_server_set_alt_setting(void *priv, uint64_t id, + struct usb_redir_set_alt_setting_header *hdr) +{ + struct usb_redir_control_packet_header ctrl = { + .endpoint = 0, + .request = USB_REQ_SET_INTERFACE, + /* Host->Device, Standard, Interface */ + .requesttype = 0x01, + .status = 0, + .value = hdr->alt, + .index = hdr->interface, + .length = 0, + }; + USBRedirServer *s = priv; + + /* The host gets an alt_setting_status when the device answers. */ + usbredir_server_do_control(s, id, &ctrl, NULL, 0, + USBREDIR_SERVER_REPLY_ALT); +} + +static void usbredir_server_get_alt_setting(void *priv, uint64_t id, + struct usb_redir_get_alt_setting_header *hdr) +{ + struct usb_redir_control_packet_header ctrl = { + .endpoint = 0, + .request = USB_REQ_GET_INTERFACE, + /* Device->Host, Standard, Interface */ + .requesttype = 0x81, + .status = 0, + .value = 0, + .index = hdr->interface, + .length = 1, + }; + USBRedirServer *s = priv; + + /* The host gets an alt_setting_status when the device answers. */ + usbredir_server_do_control(s, id, &ctrl, NULL, 0, + USBREDIR_SERVER_REPLY_ALT); +} + static void usbredir_server_filter_reject(void *priv) { trace_usbredir_server_filter_reject(); @@ -339,6 +851,84 @@ static void usbredir_server_interface_info(void *priv, /* The host should not send this to a device. Nothing to do. */ } +static void usbredir_server_cancel_data_packet(void *priv, uint64_t id) +{ + struct usb_redir_control_packet_header resp = { + .endpoint = 0, + .status = usb_redir_cancelled, + .length = 0, + }; + USBRedirServer *s = priv; + USBRedirServerPkt *rp; + + /* + * The host has put this id in its cancelled queue and waits for one + * answer carrying it. The device may have answered already, so the + * request may no longer be on our list. Answer in both cases: the + * host reuses ids, and a leftover entry would eat a later answer. + */ + QTAILQ_FOREACH(rp, &s->inflight, next) { + if (rp->redir_id == id) { + trace_usbredir_server_cancel(id, true); + usbredir_server_send_cancelled(s, rp); + usbredir_server_drop_pkt(s, rp); + return; + } + } + + /* + * Already finished, so we no longer know what kind of transfer it + * was. The host retires the entry on the id alone, and its control + * handler ignores the endpoint field, so a control packet always + * works. + */ + trace_usbredir_server_cancel(id, false); + usbredirparser_send_control_packet(s->parser, id, &resp, NULL, 0); + usbredirparser_do_write(s->parser); +} + +/* + * Cancelled and in-flight packets + */ + +/* + * Every request must get one answer carrying its id, even an aborted one. + * A dropped id stays in the host's cancelled queue. The host reuses ids, + * so a later answer would be thrown away as a stale one. + */ +static void usbredir_server_send_cancelled(USBRedirServer *s, + USBRedirServerPkt *rp) +{ + struct usb_redir_control_packet_header ctrl; + + switch (rp->type) { + case USBREDIR_SERVER_CTRL_SETUP: + case USBREDIR_SERVER_CTRL_STATUS: + ctrl = rp->ctrl_hdr; + ctrl.status = usb_redir_cancelled; + ctrl.length = 0; + usbredirparser_send_control_packet(s->parser, rp->redir_id, + &ctrl, NULL, 0); + break; + default: + return; + } + usbredirparser_do_write(s->parser); +} + +static void usbredir_server_stop_transfers(USBRedirServer *s) +{ + USBRedirServerPkt *rp; + + /* + * No "cancelled" response here. This runs on a bus reset, a detach or + * a closed chardev, and the host has dropped its own queues already. + */ + while ((rp = QTAILQ_FIRST(&s->inflight)) != NULL) { + usbredir_server_drop_pkt(s, rp); + } +} + /* * Parser setup and teardown */ @@ -361,13 +951,19 @@ 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; + s->parser->control_packet_func = usbredir_server_control_packet; + s->parser->set_configuration_func = usbredir_server_set_configuration; /* The parser calls these directly, so they must not be NULL. */ + s->parser->get_configuration_func = usbredir_server_get_configuration; + s->parser->set_alt_setting_func = usbredir_server_set_alt_setting; + s->parser->get_alt_setting_func = usbredir_server_get_alt_setting; 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; + s->parser->cancel_data_packet_func = usbredir_server_cancel_data_packet; /* Capabilities: 64-bit IDs, connect_device_version, ep_info sizes */ usbredirparser_caps_set_cap(caps, usb_redir_cap_connect_device_version); @@ -401,6 +997,8 @@ static void usbredir_server_destroy_parser(USBRedirServer *s) timer_del(s->announce_timer); g_clear_handle_id(&s->watch, g_source_remove); + usbredir_server_stop_transfers(s); + if (s->parser) { usbredirparser_destroy(s->parser); s->parser = NULL; @@ -489,6 +1087,9 @@ static void usbredir_server_realize(DeviceState *dev, Error **errp) return; } + QTAILQ_INIT(&s->inflight); + memset(s->ep_type, USB_ENDPOINT_XFER_INVALID, sizeof(s->ep_type)); + /* 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; diff --git a/hw/usb/trace-events b/hw/usb/trace-events index 2cc6a244b9..25219a018a 100644 --- a/hw/usb/trace-events +++ b/hw/usb/trace-events @@ -406,3 +406,8 @@ 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" +usbredir_server_cancel(uint64_t id, bool found) "id %" PRIu64 " still in flight %d" +usbredir_server_ep_info(unsigned idx, uint8_t type, uint16_t mps, uint8_t interval, uint8_t iface) "ep_info[%u] type %u mps %u interval %u iface %u" +usbredir_server_control(uint64_t id, uint8_t requesttype, uint8_t request, uint16_t value, uint16_t index, uint16_t length) "id %" PRIu64 " type 0x%02x request 0x%02x value 0x%04x index 0x%04x length %u" +usbredir_server_ctrl_setup_complete(uint64_t id, int status, int actual) "id %" PRIu64 " status %d actual %d" +usbredir_server_ctrl_status_complete(uint64_t id, int status) "id %" PRIu64 " status %d" -- 2.53.0
