> 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. If it is no longer on the > list we already answered it, so we send nothing. > > 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, > how big its packets are and which interface it belongs to. 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. > > A device can offer several configurations, and an interface several > alternate settings, each with its own endpoints. Keep every > configuration descriptor, build the table from the one the host > selected, and send a fresh ep_info when that selection changes, so > ep_info never describes an endpoint that is not there. > > Signed-off-by: Jamin Lin <[email protected]> > Reviewed-by: Marc-André Lureau <[email protected]>
Can you drop my reviewed-by, we are undergoing still significant changes. Thanks > Message-ID: <[email protected]> > > diff --git a/hw/usb/redirect-server.c b/hw/usb/redirect-server.c > index dc4bb533837e..29a9fbc94178 100644 > --- a/hw/usb/redirect-server.c > +++ b/hw/usb/redirect-server.c > @@ -63,9 +63,11 @@ > #include "qom/object.h" > > #include <usbredirparser.h> > +#include <usbredirproto.h> > > #include "hw/core/qdev-properties.h" > #include "hw/core/qdev-properties-system.h" > +#include "desc.h" > #include "trace.h" > > #define TYPE_USB_REDIR_SERVER "x-usb-redir-server" > @@ -73,6 +75,41 @@ OBJECT_DECLARE_SIMPLE_TYPE(USBRedirServer, > USB_REDIR_SERVER) > > #define USBREDIR_SERVER_VERSION "qemu " TYPE_USB_REDIR_SERVER " " > QEMU_VERSION > > +/* > + * 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; > > @@ -92,8 +129,27 @@ 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]; > + > + uint8_t alt_setting[USB_MAX_INTERFACES]; > + GHashTable *config_descs; > + uint8_t config_value; > }; > > +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. > @@ -104,9 +160,171 @@ 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; > + > + 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) { > + trace_usbredir_server_endpoint_dropped(addr); > + 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; > + > + /* > + * 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 store 1 instead. > + */ > + if (s->ep_interval[idx] == 0 && > + (s->ep_type[idx] == USB_ENDPOINT_XFER_INT || > + s->ep_type[idx] == USB_ENDPOINT_XFER_ISOC)) { > + s->ep_interval[idx] = 1; > + } > +} > + > +static void usbredir_server_save_config_desc(USBRedirServer *s, > + const uint8_t *data, int len) > +{ > + const USBDescriptor *conf = (const USBDescriptor *)data; > + uint16_t total; > + > + if (len < 4) { > + return; > + } > + total = lduw_le_p(data + 2); > + > + /* > + * The host reads this twice. The first read is a few bytes, just to > + * learn the length from wTotalLength. The second read has the whole > + * descriptor. > + * > + * A configuration descriptor is 9 bytes, so a shorter one is broken. > + */ > + if (total < 9 || len < total) { > + return; > + } > + > + g_hash_table_insert(s->config_descs, > + GUINT_TO_POINTER(conf->u.config.bConfigurationValue), > + g_memdup2(data, total)); > +} > + > +/* > + * Some devices only forward transfers. They never read their own > + * descriptors, so the USB core leaves their endpoint types at > + * USB_ENDPOINT_XFER_INVALID. Sending that in ep_info does not work: the > + * host sees type 255 and will not move data. So read the configuration > + * descriptor and fill the tables here. > + * > + * Only what the host selected counts. A device can offer several > + * configurations, and an interface several alternate settings. The > + * endpoints of the ones not chosen are not there. > + */ > +static void usbredir_server_build_ep_tables(USBRedirServer *s) > +{ > + const USBDescriptor *desc; > + const uint8_t *config; > + bool active = false; > + uint8_t iface = 0; > + uint8_t alt; > + int i = 0; > + int total; > + > + 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)); > + > + config = g_hash_table_lookup(s->config_descs, > + GUINT_TO_POINTER(s->config_value)); > + if (!config) { > + trace_usbredir_server_config_not_found( > + s->config_value, g_hash_table_size(s->config_descs)); > + return; > + } > + total = lduw_le_p(config + 2); > + > + while (i + 2 <= total) { > + desc = (const USBDescriptor *)(config + i); > + > + if (desc->bLength < 2) { > + trace_usbredir_server_bad_descriptor(i, desc->bLength, > + desc->bDescriptorType); > + break; > + } > + if (i + desc->bLength > total) { > + trace_usbredir_server_descriptor_overrun(i, desc->bLength, > total); > + break; > + } > + > + switch (desc->bDescriptorType) { > + case USB_DT_INTERFACE: > + if (desc->bLength < 9) { > + trace_usbredir_server_bad_descriptor(i, desc->bLength, > + desc->bDescriptorType); > + break; > + } > + iface = desc->u.interface.bInterfaceNumber; > + alt = desc->u.interface.bAlternateSetting; > + active = iface < USB_MAX_INTERFACES && > + alt == s->alt_setting[iface]; > + break; > + case USB_DT_ENDPOINT: > + if (desc->bLength < 7) { > + trace_usbredir_server_bad_descriptor(i, desc->bLength, > + desc->bDescriptorType); > + break; > + } > + if (active) { > + usbredir_server_record_endpoint(s, desc, iface); > + } > + break; > + } > + > + i += desc->bLength; > + } > +} > + > +static void usbredir_server_send_ep_info(USBRedirServer *s) > +{ > + struct usb_redir_ep_info_header ep_info = {}; > + int i; > + > + usbredir_server_build_ep_tables(s); > + > + 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]; > + ep_info.interval[i] = s->ep_interval[i]; > + 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) > { > switch (device->speed) { > @@ -145,6 +363,165 @@ 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_save_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); > + > + if (status != usb_redir_success) { > + return; > + } > + > + /* > + * Two requests change which endpoints exist. SET_CONFIGURATION picks > + * a configuration, SET_INTERFACE picks an alternate setting. Record > + * what the host chose, then send a fresh ep_info. > + */ > + switch ((rp->ctrl_hdr.requesttype << 8) | rp->ctrl_hdr.request) { > + case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: > + /* A new configuration puts every interface back on alternate 0. */ > + s->config_value = rp->ctrl_hdr.value; > + memset(s->alt_setting, 0, sizeof(s->alt_setting)); > + break; > + case InterfaceOutRequest | USB_REQ_SET_INTERFACE: > + if (rp->ctrl_hdr.index < USB_MAX_INTERFACES) { > + s->alt_setting[rp->ctrl_hdr.index] = rp->ctrl_hdr.value; > + } > + break; > + default: > + return; > + } > + > + usbredir_server_send_ep_info(s); > } > > /* > @@ -165,12 +542,23 @@ static void usbredir_server_port_detach(USBPort *port) > > trace_usbredir_server_detach(s->device_announced); > > + 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)); > + memset(s->alt_setting, 0, sizeof(s->alt_setting)); > + g_hash_table_remove_all(s->config_descs); > + s->config_value = 0; > } > > static void usbredir_server_port_child_detach(USBPort *port, USBDevice > *child) > @@ -183,11 +571,46 @@ 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); > + > + if (p->status == USB_RET_REMOVE_FROM_QUEUE) { > + usb_cancel_packet(p); > + } > + > + 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, > }; > > /* > @@ -197,6 +620,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 > */ > @@ -312,9 +783,202 @@ 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); This assumes ep0's queue is empty. If an earlier control transfer is still async on ep0 (usb-host answers USB_RET_ASYNC for control requests), usb_handle_packet() takes the usb_queue_one() branch, leaves the packet QUEUED, and usb_packet_cleanup() aborts on !usb_packet_is_inflight(). The same applies to the data and status stages run from usbredir_server_ctrl_setup_complete() The server could keep at most one control transfer on the device, and queue the others locally? -- Marc-André Lureau <[email protected]>
