diff options
Diffstat (limited to 'hw/usb/redirect.c')
-rw-r--r-- | hw/usb/redirect.c | 355 |
1 files changed, 337 insertions, 18 deletions
diff --git a/hw/usb/redirect.c b/hw/usb/redirect.c index f15dc914ab..f1bf84c987 100644 --- a/hw/usb/redirect.c +++ b/hw/usb/redirect.c @@ -52,11 +52,13 @@ typedef struct USBRedirDevice USBRedirDevice; -/* Struct to hold buffered packets (iso or int input packets) */ +/* Struct to hold buffered packets */ struct buf_packet { uint8_t *data; - int len; - int status; + void *free_on_destroy; + uint16_t len; + uint16_t offset; + uint8_t status; QTAILQ_ENTRY(buf_packet)next; }; @@ -70,11 +72,14 @@ struct endp_data { uint8_t iso_error; /* For reporting iso errors to the HC */ uint8_t interrupt_started; uint8_t interrupt_error; + uint8_t bulk_receiving_enabled; + uint8_t bulk_receiving_started; uint8_t bufpq_prefilled; uint8_t bufpq_dropping_packets; QTAILQ_HEAD(, buf_packet) bufpq; int32_t bufpq_size; int32_t bufpq_target_size; + USBPacket *pending_async_packet; }; struct PacketIdQueueEntry { @@ -108,6 +113,7 @@ struct USBRedirDevice { struct endp_data endpoint[MAX_ENDPOINTS]; struct PacketIdQueue cancelled; struct PacketIdQueue already_in_flight; + void (*buffered_bulk_in_complete)(USBRedirDevice *, USBPacket *, uint8_t); /* Data for device filtering */ struct usb_redir_device_connect_header device_info; struct usb_redir_interface_info_header interface_info; @@ -135,6 +141,8 @@ static void usbredir_interrupt_receiving_status(void *priv, uint64_t id, *interrupt_receiving_status); static void usbredir_bulk_streams_status(void *priv, uint64_t id, struct usb_redir_bulk_streams_status_header *bulk_streams_status); +static void usbredir_bulk_receiving_status(void *priv, uint64_t id, + struct usb_redir_bulk_receiving_status_header *bulk_receiving_status); static void usbredir_control_packet(void *priv, uint64_t id, struct usb_redir_control_packet_header *control_packet, uint8_t *data, int data_len); @@ -147,6 +155,9 @@ static void usbredir_iso_packet(void *priv, uint64_t id, static void usbredir_interrupt_packet(void *priv, uint64_t id, struct usb_redir_interrupt_packet_header *interrupt_header, uint8_t *data, int data_len); +static void usbredir_buffered_bulk_packet(void *priv, uint64_t id, + struct usb_redir_buffered_bulk_packet_header *buffered_bulk_packet, + uint8_t *data, int data_len); static void usbredir_handle_status(USBRedirDevice *dev, USBPacket *p, int status); @@ -320,12 +331,19 @@ static void packet_id_queue_empty(struct PacketIdQueue *q) static void usbredir_cancel_packet(USBDevice *udev, USBPacket *p) { USBRedirDevice *dev = DO_UPCAST(USBRedirDevice, dev, udev); + int i = USBEP2I(p->ep); if (p->combined) { usb_combined_packet_cancel(udev, p); return; } + if (dev->endpoint[i].pending_async_packet) { + assert(dev->endpoint[i].pending_async_packet == p); + dev->endpoint[i].pending_async_packet = NULL; + return; + } + packet_id_queue_add(&dev->cancelled, p->id); usbredirparser_send_cancel_data_packet(dev->parser, p->id); usbredirparser_do_write(dev->parser); @@ -344,6 +362,11 @@ static void usbredir_fill_already_in_flight_from_ep(USBRedirDevice *dev, { static USBPacket *p; + /* async handled packets for bulk receiving eps do not count as inflight */ + if (dev->endpoint[USBEP2I(ep)].bulk_receiving_started) { + return; + } + QTAILQ_FOREACH(p, &ep->queue, queue) { /* Skip combined packets, except for the first */ if (p->combined && p != p->combined->first) { @@ -391,8 +414,8 @@ static USBPacket *usbredir_find_packet_by_id(USBRedirDevice *dev, return p; } -static void bufp_alloc(USBRedirDevice *dev, - uint8_t *data, int len, int status, uint8_t ep) +static void bufp_alloc(USBRedirDevice *dev, uint8_t *data, uint16_t len, + uint8_t status, uint8_t ep, void *free_on_destroy) { struct buf_packet *bufp; @@ -416,7 +439,9 @@ static void bufp_alloc(USBRedirDevice *dev, bufp = g_malloc(sizeof(struct buf_packet)); bufp->data = data; bufp->len = len; + bufp->offset = 0; bufp->status = status; + bufp->free_on_destroy = free_on_destroy; QTAILQ_INSERT_TAIL(&dev->endpoint[EP2I(ep)].bufpq, bufp, next); dev->endpoint[EP2I(ep)].bufpq_size++; } @@ -426,7 +451,7 @@ static void bufp_free(USBRedirDevice *dev, struct buf_packet *bufp, { QTAILQ_REMOVE(&dev->endpoint[EP2I(ep)].bufpq, bufp, next); dev->endpoint[EP2I(ep)].bufpq_size--; - free(bufp->data); + free(bufp->free_on_destroy); g_free(bufp); } @@ -577,19 +602,162 @@ static void usbredir_stop_iso_stream(USBRedirDevice *dev, uint8_t ep) usbredir_free_bufpq(dev, ep); } +/* + * The usb-host may poll the endpoint faster then our guest, resulting in lots + * of smaller bulkp-s. The below buffered_bulk_in_complete* functions combine + * data from multiple bulkp-s into a single packet, avoiding bufpq overflows. + */ +static void usbredir_buffered_bulk_add_data_to_packet(USBRedirDevice *dev, + struct buf_packet *bulkp, int count, USBPacket *p, uint8_t ep) +{ + usb_packet_copy(p, bulkp->data + bulkp->offset, count); + bulkp->offset += count; + if (bulkp->offset == bulkp->len) { + /* Store status in the last packet with data from this bulkp */ + usbredir_handle_status(dev, p, bulkp->status); + bufp_free(dev, bulkp, ep); + } +} + +static void usbredir_buffered_bulk_in_complete_raw(USBRedirDevice *dev, + USBPacket *p, uint8_t ep) +{ + struct buf_packet *bulkp; + int count; + + while ((bulkp = QTAILQ_FIRST(&dev->endpoint[EP2I(ep)].bufpq)) && + p->actual_length < p->iov.size && p->status == USB_RET_SUCCESS) { + count = bulkp->len - bulkp->offset; + if (count > (p->iov.size - p->actual_length)) { + count = p->iov.size - p->actual_length; + } + usbredir_buffered_bulk_add_data_to_packet(dev, bulkp, count, p, ep); + } +} + +static void usbredir_buffered_bulk_in_complete_ftdi(USBRedirDevice *dev, + USBPacket *p, uint8_t ep) +{ + const int maxp = dev->endpoint[EP2I(ep)].max_packet_size; + uint8_t header[2] = { 0, 0 }; + struct buf_packet *bulkp; + int count; + + while ((bulkp = QTAILQ_FIRST(&dev->endpoint[EP2I(ep)].bufpq)) && + p->actual_length < p->iov.size && p->status == USB_RET_SUCCESS) { + if (bulkp->len < 2) { + WARNING("malformed ftdi bulk in packet\n"); + bufp_free(dev, bulkp, ep); + continue; + } + + if ((p->actual_length % maxp) == 0) { + usb_packet_copy(p, bulkp->data, 2); + memcpy(header, bulkp->data, 2); + } else { + if (bulkp->data[0] != header[0] || bulkp->data[1] != header[1]) { + break; /* Different header, add to next packet */ + } + } + + if (bulkp->offset == 0) { + bulkp->offset = 2; /* Skip header */ + } + count = bulkp->len - bulkp->offset; + /* Must repeat the header at maxp interval */ + if (count > (maxp - (p->actual_length % maxp))) { + count = maxp - (p->actual_length % maxp); + } + usbredir_buffered_bulk_add_data_to_packet(dev, bulkp, count, p, ep); + } +} + +static void usbredir_buffered_bulk_in_complete(USBRedirDevice *dev, + USBPacket *p, uint8_t ep) +{ + p->status = USB_RET_SUCCESS; /* Clear previous ASYNC status */ + dev->buffered_bulk_in_complete(dev, p, ep); + DPRINTF("bulk-token-in ep %02X status %d len %d id %"PRIu64"\n", + ep, p->status, p->actual_length, p->id); +} + +static void usbredir_handle_buffered_bulk_in_data(USBRedirDevice *dev, + USBPacket *p, uint8_t ep) +{ + /* Input bulk endpoint, buffered packet input */ + if (!dev->endpoint[EP2I(ep)].bulk_receiving_started) { + int bpt; + struct usb_redir_start_bulk_receiving_header start = { + .endpoint = ep, + .stream_id = 0, + .no_transfers = 5, + }; + /* Round bytes_per_transfer up to a multiple of max_packet_size */ + bpt = 512 + dev->endpoint[EP2I(ep)].max_packet_size - 1; + bpt /= dev->endpoint[EP2I(ep)].max_packet_size; + bpt *= dev->endpoint[EP2I(ep)].max_packet_size; + start.bytes_per_transfer = bpt; + /* No id, we look at the ep when receiving a status back */ + usbredirparser_send_start_bulk_receiving(dev->parser, 0, &start); + usbredirparser_do_write(dev->parser); + DPRINTF("bulk receiving started bytes/transfer %u count %d ep %02X\n", + start.bytes_per_transfer, start.no_transfers, ep); + dev->endpoint[EP2I(ep)].bulk_receiving_started = 1; + /* We don't really want to drop bulk packets ever, but + having some upper limit to how much we buffer is good. */ + dev->endpoint[EP2I(ep)].bufpq_target_size = 5000; + dev->endpoint[EP2I(ep)].bufpq_dropping_packets = 0; + } + + if (QTAILQ_EMPTY(&dev->endpoint[EP2I(ep)].bufpq)) { + DPRINTF("bulk-token-in ep %02X, no bulkp\n", ep); + assert(dev->endpoint[EP2I(ep)].pending_async_packet == NULL); + dev->endpoint[EP2I(ep)].pending_async_packet = p; + p->status = USB_RET_ASYNC; + return; + } + usbredir_buffered_bulk_in_complete(dev, p, ep); +} + +static void usbredir_stop_bulk_receiving(USBRedirDevice *dev, uint8_t ep) +{ + struct usb_redir_stop_bulk_receiving_header stop_bulk = { + .endpoint = ep, + .stream_id = 0, + }; + if (dev->endpoint[EP2I(ep)].bulk_receiving_started) { + usbredirparser_send_stop_bulk_receiving(dev->parser, 0, &stop_bulk); + DPRINTF("bulk receiving stopped ep %02X\n", ep); + dev->endpoint[EP2I(ep)].bulk_receiving_started = 0; + } + usbredir_free_bufpq(dev, ep); +} + static void usbredir_handle_bulk_data(USBRedirDevice *dev, USBPacket *p, uint8_t ep) { struct usb_redir_bulk_packet_header bulk_packet; size_t size = (p->combined) ? p->combined->iov.size : p->iov.size; - - DPRINTF("bulk-out ep %02X len %zd id %"PRIu64"\n", ep, size, p->id); + const int maxp = dev->endpoint[EP2I(ep)].max_packet_size; if (usbredir_already_in_flight(dev, p->id)) { p->status = USB_RET_ASYNC; return; } + if (dev->endpoint[EP2I(ep)].bulk_receiving_enabled) { + if (size != 0 && (size % maxp) == 0) { + usbredir_handle_buffered_bulk_in_data(dev, p, ep); + return; + } + WARNING("bulk recv invalid size %zd ep %02x, disabling\n", size, ep); + assert(dev->endpoint[EP2I(ep)].pending_async_packet == NULL); + usbredir_stop_bulk_receiving(dev, ep); + dev->endpoint[EP2I(ep)].bulk_receiving_enabled = 0; + } + + DPRINTF("bulk-out ep %02X len %zd id %"PRIu64"\n", ep, size, p->id); + bulk_packet.endpoint = ep; bulk_packet.length = size; bulk_packet.stream_id = 0; @@ -726,9 +894,6 @@ static void usbredir_handle_data(USBDevice *udev, USBPacket *p) ERROR("handle_data called for control transfer on ep %02X\n", ep); p->status = USB_RET_NAK; break; - case USB_ENDPOINT_XFER_ISOC: - usbredir_handle_iso_data(dev, p, ep); - break; case USB_ENDPOINT_XFER_BULK: if (p->state == USB_PACKET_SETUP && p->pid == USB_TOKEN_IN && p->ep->pipeline) { @@ -737,6 +902,9 @@ static void usbredir_handle_data(USBDevice *udev, USBPacket *p) } usbredir_handle_bulk_data(dev, p, ep); break; + case USB_ENDPOINT_XFER_ISOC: + usbredir_handle_iso_data(dev, p, ep); + break; case USB_ENDPOINT_XFER_INT: if (ep & USB_DIR_IN) { usbredir_handle_interrupt_in_data(dev, p, ep); @@ -763,6 +931,11 @@ static void usbredir_stop_ep(USBRedirDevice *dev, int i) uint8_t ep = I2EP(i); switch (dev->endpoint[i].type) { + case USB_ENDPOINT_XFER_BULK: + if (ep & USB_DIR_IN) { + usbredir_stop_bulk_receiving(dev, ep); + } + break; case USB_ENDPOINT_XFER_ISOC: usbredir_stop_iso_stream(dev, ep); break; @@ -942,10 +1115,12 @@ static void usbredir_create_parser(USBRedirDevice *dev) dev->parser->interrupt_receiving_status_func = usbredir_interrupt_receiving_status; dev->parser->bulk_streams_status_func = usbredir_bulk_streams_status; + dev->parser->bulk_receiving_status_func = usbredir_bulk_receiving_status; dev->parser->control_packet_func = usbredir_control_packet; dev->parser->bulk_packet_func = usbredir_bulk_packet; dev->parser->iso_packet_func = usbredir_iso_packet; dev->parser->interrupt_packet_func = usbredir_interrupt_packet; + dev->parser->buffered_bulk_packet_func = usbredir_buffered_bulk_packet; dev->read_buf = NULL; dev->read_buf_size = 0; @@ -954,6 +1129,7 @@ static void usbredir_create_parser(USBRedirDevice *dev) usbredirparser_caps_set_cap(caps, usb_redir_cap_ep_info_max_packet_size); usbredirparser_caps_set_cap(caps, usb_redir_cap_64bits_ids); usbredirparser_caps_set_cap(caps, usb_redir_cap_32bits_bulk_length); + usbredirparser_caps_set_cap(caps, usb_redir_cap_bulk_receiving); if (runstate_check(RUN_STATE_INMIGRATE)) { flags |= usbredirparser_fl_no_hello; @@ -1192,6 +1368,52 @@ error: return -1; } +static void usbredir_check_bulk_receiving(USBRedirDevice *dev) +{ + int i, j, quirks; + + if (!usbredirparser_peer_has_cap(dev->parser, + usb_redir_cap_bulk_receiving)) { + return; + } + + for (i = EP2I(USB_DIR_IN); i < MAX_ENDPOINTS; i++) { + dev->endpoint[i].bulk_receiving_enabled = 0; + } + for (i = 0; i < dev->interface_info.interface_count; i++) { + quirks = usb_get_quirks(dev->device_info.vendor_id, + dev->device_info.product_id, + dev->interface_info.interface_class[i], + dev->interface_info.interface_subclass[i], + dev->interface_info.interface_protocol[i]); + if (!(quirks & USB_QUIRK_BUFFER_BULK_IN)) { + continue; + } + if (quirks & USB_QUIRK_IS_FTDI) { + dev->buffered_bulk_in_complete = + usbredir_buffered_bulk_in_complete_ftdi; + } else { + dev->buffered_bulk_in_complete = + usbredir_buffered_bulk_in_complete_raw; + } + + for (j = EP2I(USB_DIR_IN); j < MAX_ENDPOINTS; j++) { + if (dev->endpoint[j].interface == + dev->interface_info.interface[i] && + dev->endpoint[j].type == USB_ENDPOINT_XFER_BULK && + dev->endpoint[j].max_packet_size != 0) { + dev->endpoint[j].bulk_receiving_enabled = 1; + /* + * With buffering pipelining is not necessary. Also packet + * combining and bulk in buffering don't play nice together! + */ + I2USBEP(dev, j)->pipeline = false; + break; /* Only buffer for the first ep of each intf */ + } + } + } +} + /* * usbredirparser packet complete callbacks */ @@ -1300,6 +1522,7 @@ static void usbredir_device_connect(void *priv, return; } + usbredir_check_bulk_receiving(dev); qemu_mod_timer(dev->attach_timer, dev->next_attach_time); } @@ -1338,9 +1561,10 @@ static void usbredir_interface_info(void *priv, /* * If we receive interface info after the device has already been - * connected (ie on a set_config), re-check the filter. + * connected (ie on a set_config), re-check interface dependent things. */ if (qemu_timer_pending(dev->attach_timer) || dev->dev.attached) { + usbredir_check_bulk_receiving(dev); if (usbredir_check_filter(dev)) { ERROR("Device no longer matches filter after interface info " "change, disconnecting!\n"); @@ -1441,6 +1665,7 @@ static void usbredir_ep_info(void *priv, return; } usbredir_setup_usb_eps(dev); + usbredir_check_bulk_receiving(dev); } static void usbredir_configuration_status(void *priv, uint64_t id, @@ -1531,6 +1756,25 @@ static void usbredir_bulk_streams_status(void *priv, uint64_t id, { } +static void usbredir_bulk_receiving_status(void *priv, uint64_t id, + struct usb_redir_bulk_receiving_status_header *bulk_receiving_status) +{ + USBRedirDevice *dev = priv; + uint8_t ep = bulk_receiving_status->endpoint; + + DPRINTF("bulk recv status %d ep %02X id %"PRIu64"\n", + bulk_receiving_status->status, ep, id); + + if (!dev->dev.attached || !dev->endpoint[EP2I(ep)].bulk_receiving_started) { + return; + } + + if (bulk_receiving_status->status == usb_redir_stall) { + DPRINTF("bulk receiving stopped by peer ep %02X\n", ep); + dev->endpoint[EP2I(ep)].bulk_receiving_started = 0; + } +} + static void usbredir_control_packet(void *priv, uint64_t id, struct usb_redir_control_packet_header *control_packet, uint8_t *data, int data_len) @@ -1636,7 +1880,7 @@ static void usbredir_iso_packet(void *priv, uint64_t id, } /* bufp_alloc also adds the packet to the ep queue */ - bufp_alloc(dev, data, data_len, iso_packet->status, ep); + bufp_alloc(dev, data, data_len, iso_packet->status, ep, data); } static void usbredir_interrupt_packet(void *priv, uint64_t id, @@ -1667,7 +1911,7 @@ static void usbredir_interrupt_packet(void *priv, uint64_t id, } /* bufp_alloc also adds the packet to the ep queue */ - bufp_alloc(dev, data, data_len, interrupt_packet->status, ep); + bufp_alloc(dev, data, data_len, interrupt_packet->status, ep, data); } else { /* * We report output interrupt packets as completed directly upon @@ -1680,6 +1924,52 @@ static void usbredir_interrupt_packet(void *priv, uint64_t id, } } +static void usbredir_buffered_bulk_packet(void *priv, uint64_t id, + struct usb_redir_buffered_bulk_packet_header *buffered_bulk_packet, + uint8_t *data, int data_len) +{ + USBRedirDevice *dev = priv; + uint8_t status, ep = buffered_bulk_packet->endpoint; + void *free_on_destroy; + int i, len; + + DPRINTF("buffered-bulk-in status %d ep %02X len %d id %"PRIu64"\n", + buffered_bulk_packet->status, ep, data_len, id); + + if (dev->endpoint[EP2I(ep)].type != USB_ENDPOINT_XFER_BULK) { + ERROR("received buffered-bulk packet for non bulk ep %02X\n", ep); + free(data); + return; + } + + if (dev->endpoint[EP2I(ep)].bulk_receiving_started == 0) { + DPRINTF("received buffered-bulk packet on not started ep %02X\n", ep); + free(data); + return; + } + + /* Data must be in maxp chunks for buffered_bulk_add_*_data_to_packet */ + len = dev->endpoint[EP2I(ep)].max_packet_size; + status = usb_redir_success; + free_on_destroy = NULL; + for (i = 0; i < data_len; i += len) { + if (len >= (data_len - i)) { + len = data_len - i; + status = buffered_bulk_packet->status; + free_on_destroy = data; + } + /* bufp_alloc also adds the packet to the ep queue */ + bufp_alloc(dev, data + i, len, status, ep, free_on_destroy); + } + + if (dev->endpoint[EP2I(ep)].pending_async_packet) { + USBPacket *p = dev->endpoint[EP2I(ep)].pending_async_packet; + dev->endpoint[EP2I(ep)].pending_async_packet = NULL; + usbredir_buffered_bulk_in_complete(dev, p, ep); + usb_packet_complete(&dev->dev, p); + } +} + /* * Migration code */ @@ -1714,6 +2004,7 @@ static int usbredir_post_load(void *priv, int version_id) dev->dev.speedmask = (1 << dev->dev.speed); usbredir_setup_usb_eps(dev); + usbredir_check_bulk_receiving(dev); return 0; } @@ -1787,15 +2078,16 @@ static void usbredir_put_bufpq(QEMUFile *f, void *priv, size_t unused) struct endp_data *endp = priv; USBRedirDevice *dev = endp->dev; struct buf_packet *bufp; - int i = 0; + int len, i = 0; qemu_put_be32(f, endp->bufpq_size); QTAILQ_FOREACH(bufp, &endp->bufpq, next) { + len = bufp->len - bufp->offset; DPRINTF("put_bufpq %d/%d len %d status %d\n", i + 1, endp->bufpq_size, - bufp->len, bufp->status); - qemu_put_be32(f, bufp->len); + len, bufp->status); + qemu_put_be32(f, len); qemu_put_be32(f, bufp->status); - qemu_put_buffer(f, bufp->data, bufp->len); + qemu_put_buffer(f, bufp->data + bufp->offset, len); i++; } assert(i == endp->bufpq_size); @@ -1813,7 +2105,9 @@ static int usbredir_get_bufpq(QEMUFile *f, void *priv, size_t unused) bufp = g_malloc(sizeof(struct buf_packet)); bufp->len = qemu_get_be32(f); bufp->status = qemu_get_be32(f); + bufp->offset = 0; bufp->data = qemu_oom_check(malloc(bufp->len)); /* regular malloc! */ + bufp->free_on_destroy = bufp->data; qemu_get_buffer(f, bufp->data, bufp->len); QTAILQ_INSERT_TAIL(&endp->bufpq, bufp, next); DPRINTF("get_bufpq %d/%d len %d status %d\n", i + 1, endp->bufpq_size, @@ -1830,6 +2124,23 @@ static const VMStateInfo usbredir_ep_bufpq_vmstate_info = { /* For endp_data migration */ +static const VMStateDescription usbredir_bulk_receiving_vmstate = { + .name = "usb-redir-ep/bulk-receiving", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT8(bulk_receiving_started, struct endp_data), + VMSTATE_END_OF_LIST() + } +}; + +static bool usbredir_bulk_receiving_needed(void *priv) +{ + struct endp_data *endp = priv; + + return endp->bulk_receiving_started; +} + static const VMStateDescription usbredir_ep_vmstate = { .name = "usb-redir-ep", .version_id = 1, @@ -1856,6 +2167,14 @@ static const VMStateDescription usbredir_ep_vmstate = { }, VMSTATE_INT32(bufpq_target_size, struct endp_data), VMSTATE_END_OF_LIST() + }, + .subsections = (VMStateSubsection[]) { + { + .vmsd = &usbredir_bulk_receiving_vmstate, + .needed = usbredir_bulk_receiving_needed, + }, { + /* empty */ + } } }; |