diff options
Diffstat (limited to 'hw/usb')
| -rw-r--r-- | hw/usb/core.c | 21 | ||||
| -rw-r--r-- | hw/usb/desc.c | 12 | ||||
| -rw-r--r-- | hw/usb/dev-hid.c | 13 | ||||
| -rw-r--r-- | hw/usb/dev-mtp.c | 28 | ||||
| -rw-r--r-- | hw/usb/hcd-ehci.c | 79 | ||||
| -rw-r--r-- | hw/usb/hcd-uhci.c | 36 | ||||
| -rw-r--r-- | hw/usb/hcd-xhci.c | 64 | ||||
| -rw-r--r-- | hw/usb/host-libusb.c | 149 | ||||
| -rw-r--r-- | hw/usb/redirect.c | 137 |
9 files changed, 374 insertions, 165 deletions
diff --git a/hw/usb/core.c b/hw/usb/core.c index 67ba7d6018..cf34755bba 100644 --- a/hw/usb/core.c +++ b/hw/usb/core.c @@ -28,6 +28,26 @@ #include "qemu/iov.h" #include "trace.h" +void usb_pick_speed(USBPort *port) +{ + static const int speeds[] = { + USB_SPEED_SUPER, + USB_SPEED_HIGH, + USB_SPEED_FULL, + USB_SPEED_LOW, + }; + USBDevice *udev = port->dev; + int i; + + for (i = 0; i < ARRAY_SIZE(speeds); i++) { + if ((udev->speedmask & (1 << speeds[i])) && + (port->speedmask & (1 << speeds[i]))) { + udev->speed = speeds[i]; + return; + } + } +} + void usb_attach(USBPort *port) { USBDevice *dev = port->dev; @@ -35,6 +55,7 @@ void usb_attach(USBPort *port) assert(dev != NULL); assert(dev->attached); assert(dev->state == USB_STATE_NOTATTACHED); + usb_pick_speed(port); port->ops->attach(port); dev->state = USB_STATE_ATTACHED; usb_device_handle_attach(dev); diff --git a/hw/usb/desc.c b/hw/usb/desc.c index ab48691363..b82c397ef9 100644 --- a/hw/usb/desc.c +++ b/hw/usb/desc.c @@ -518,18 +518,6 @@ void usb_desc_init(USBDevice *dev) void usb_desc_attach(USBDevice *dev) { - const USBDesc *desc = usb_device_get_usb_desc(dev); - - assert(desc != NULL); - if (desc->super && (dev->port->speedmask & USB_SPEED_MASK_SUPER)) { - dev->speed = USB_SPEED_SUPER; - } else if (desc->high && (dev->port->speedmask & USB_SPEED_MASK_HIGH)) { - dev->speed = USB_SPEED_HIGH; - } else if (desc->full && (dev->port->speedmask & USB_SPEED_MASK_FULL)) { - dev->speed = USB_SPEED_FULL; - } else { - return; - } usb_desc_setdefaults(dev); } diff --git a/hw/usb/dev-hid.c b/hw/usb/dev-hid.c index d097d937ea..67a57f1dcd 100644 --- a/hw/usb/dev-hid.c +++ b/hw/usb/dev-hid.c @@ -47,6 +47,8 @@ typedef struct USBHIDState { USBEndpoint *intr; HIDState hid; uint32_t usb_version; + char *display; + uint32_t head; } USBHIDState; enum { @@ -574,6 +576,9 @@ static int usb_hid_initfn(USBDevice *dev, int kind) usb_desc_init(dev); us->intr = usb_ep_get(dev, USB_TOKEN_IN, 1); hid_init(&us->hid, kind, usb_hid_changed); + if (us->display && us->hid.s) { + qemu_input_handler_bind(us->hid.s, us->display, us->head, NULL); + } return 0; } @@ -653,6 +658,8 @@ static void usb_hid_class_initfn(ObjectClass *klass, void *data) static Property usb_tablet_properties[] = { DEFINE_PROP_UINT32("usb_version", USBHIDState, usb_version, 2), + DEFINE_PROP_STRING("display", USBHIDState, display), + DEFINE_PROP_UINT32("head", USBHIDState, head, 0), DEFINE_PROP_END_OF_LIST(), }; @@ -696,6 +703,11 @@ static const TypeInfo usb_mouse_info = { .class_init = usb_mouse_class_initfn, }; +static Property usb_keyboard_properties[] = { + DEFINE_PROP_STRING("display", USBHIDState, display), + DEFINE_PROP_END_OF_LIST(), +}; + static void usb_keyboard_class_initfn(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); @@ -706,6 +718,7 @@ static void usb_keyboard_class_initfn(ObjectClass *klass, void *data) uc->product_desc = "QEMU USB Keyboard"; uc->usb_desc = &desc_keyboard; dc->vmsd = &vmstate_usb_kbd; + dc->props = usb_keyboard_properties; set_bit(DEVICE_CATEGORY_INPUT, dc->categories); } diff --git a/hw/usb/dev-mtp.c b/hw/usb/dev-mtp.c index 943f930404..380b465621 100644 --- a/hw/usb/dev-mtp.c +++ b/hw/usb/dev-mtp.c @@ -46,6 +46,7 @@ enum mtp_code { /* response codes */ RES_OK = 0x2001, + RES_GENERAL_ERROR = 0x2002, RES_SESSION_NOT_OPEN = 0x2003, RES_INVALID_TRANSACTION_ID = 0x2004, RES_OPERATION_NOT_SUPPORTED = 0x2005, @@ -109,7 +110,8 @@ struct MTPObject { struct stat stat; MTPObject *parent; MTPObject **children; - int32_t nchildren; + uint32_t nchildren; + bool have_children; QTAILQ_ENTRY(MTPObject) next; }; @@ -273,7 +275,6 @@ static MTPObject *usb_mtp_object_alloc(MTPState *s, uint32_t handle, o->handle = handle; o->parent = parent; o->name = g_strdup(name); - o->nchildren = -1; if (parent == NULL) { o->path = g_strdup(name); } else { @@ -340,7 +341,11 @@ static void usb_mtp_object_readdir(MTPState *s, MTPObject *o) struct dirent *entry; DIR *dir; - o->nchildren = 0; + if (o->have_children) { + return; + } + o->have_children = true; + dir = opendir(o->path); if (!dir) { return; @@ -698,7 +703,10 @@ static MTPData *usb_mtp_get_partial_object(MTPState *s, MTPControl *c, if (offset > o->stat.st_size) { offset = o->stat.st_size; } - lseek(d->fd, offset, SEEK_SET); + if (lseek(d->fd, offset, SEEK_SET) < 0) { + usb_mtp_data_free(d); + return NULL; + } d->length = c->argv[2]; if (d->length > o->stat.st_size - offset) { @@ -789,9 +797,7 @@ static void usb_mtp_command(MTPState *s, MTPControl *c) c->trans, 0, 0, 0); return; } - if (o->nchildren == -1) { - usb_mtp_object_readdir(s, o); - } + usb_mtp_object_readdir(s, o); if (c->code == CMD_GET_NUM_OBJECTS) { trace_usb_mtp_op_get_num_objects(s->dev.addr, o->handle, o->path); nres = 1; @@ -823,7 +829,9 @@ static void usb_mtp_command(MTPState *s, MTPControl *c) } data_in = usb_mtp_get_object(s, c, o); if (NULL == data_in) { - fprintf(stderr, "%s: TODO: handle error\n", __func__); + usb_mtp_queue_result(s, RES_GENERAL_ERROR, + c->trans, 0, 0, 0); + return; } break; case CMD_GET_PARTIAL_OBJECT: @@ -840,7 +848,9 @@ static void usb_mtp_command(MTPState *s, MTPControl *c) } data_in = usb_mtp_get_partial_object(s, c, o); if (NULL == data_in) { - fprintf(stderr, "%s: TODO: handle error\n", __func__); + usb_mtp_queue_result(s, RES_GENERAL_ERROR, + c->trans, 0, 0, 0); + return; } nres = 1; res0 = data_in->length; diff --git a/hw/usb/hcd-ehci.c b/hw/usb/hcd-ehci.c index a3ae9f260a..a00a93c3eb 100644 --- a/hw/usb/hcd-ehci.c +++ b/hw/usb/hcd-ehci.c @@ -27,87 +27,10 @@ * along with this program; if not, see <http://www.gnu.org/licenses/>. */ +#include "hw/usb/ehci-regs.h" #include "hw/usb/hcd-ehci.h" #include "trace.h" -/* Capability Registers Base Address - section 2.2 */ -#define CAPLENGTH 0x0000 /* 1-byte, 0x0001 reserved */ -#define HCIVERSION 0x0002 /* 2-bytes, i/f version # */ -#define HCSPARAMS 0x0004 /* 4-bytes, structural params */ -#define HCCPARAMS 0x0008 /* 4-bytes, capability params */ -#define EECP HCCPARAMS + 1 -#define HCSPPORTROUTE1 0x000c -#define HCSPPORTROUTE2 0x0010 - -#define USBCMD 0x0000 -#define USBCMD_RUNSTOP (1 << 0) // run / Stop -#define USBCMD_HCRESET (1 << 1) // HC Reset -#define USBCMD_FLS (3 << 2) // Frame List Size -#define USBCMD_FLS_SH 2 // Frame List Size Shift -#define USBCMD_PSE (1 << 4) // Periodic Schedule Enable -#define USBCMD_ASE (1 << 5) // Asynch Schedule Enable -#define USBCMD_IAAD (1 << 6) // Int Asynch Advance Doorbell -#define USBCMD_LHCR (1 << 7) // Light Host Controller Reset -#define USBCMD_ASPMC (3 << 8) // Async Sched Park Mode Count -#define USBCMD_ASPME (1 << 11) // Async Sched Park Mode Enable -#define USBCMD_ITC (0x7f << 16) // Int Threshold Control -#define USBCMD_ITC_SH 16 // Int Threshold Control Shift - -#define USBSTS 0x0004 -#define USBSTS_RO_MASK 0x0000003f -#define USBSTS_INT (1 << 0) // USB Interrupt -#define USBSTS_ERRINT (1 << 1) // Error Interrupt -#define USBSTS_PCD (1 << 2) // Port Change Detect -#define USBSTS_FLR (1 << 3) // Frame List Rollover -#define USBSTS_HSE (1 << 4) // Host System Error -#define USBSTS_IAA (1 << 5) // Interrupt on Async Advance -#define USBSTS_HALT (1 << 12) // HC Halted -#define USBSTS_REC (1 << 13) // Reclamation -#define USBSTS_PSS (1 << 14) // Periodic Schedule Status -#define USBSTS_ASS (1 << 15) // Asynchronous Schedule Status - -/* - * Interrupt enable bits correspond to the interrupt active bits in USBSTS - * so no need to redefine here. - */ -#define USBINTR 0x0008 -#define USBINTR_MASK 0x0000003f - -#define FRINDEX 0x000c -#define CTRLDSSEGMENT 0x0010 -#define PERIODICLISTBASE 0x0014 -#define ASYNCLISTADDR 0x0018 -#define ASYNCLISTADDR_MASK 0xffffffe0 - -#define CONFIGFLAG 0x0040 - -/* - * Bits that are reserved or are read-only are masked out of values - * written to us by software - */ -#define PORTSC_RO_MASK 0x007001c0 -#define PORTSC_RWC_MASK 0x0000002a -#define PORTSC_WKOC_E (1 << 22) // Wake on Over Current Enable -#define PORTSC_WKDS_E (1 << 21) // Wake on Disconnect Enable -#define PORTSC_WKCN_E (1 << 20) // Wake on Connect Enable -#define PORTSC_PTC (15 << 16) // Port Test Control -#define PORTSC_PTC_SH 16 // Port Test Control shift -#define PORTSC_PIC (3 << 14) // Port Indicator Control -#define PORTSC_PIC_SH 14 // Port Indicator Control Shift -#define PORTSC_POWNER (1 << 13) // Port Owner -#define PORTSC_PPOWER (1 << 12) // Port Power -#define PORTSC_LINESTAT (3 << 10) // Port Line Status -#define PORTSC_LINESTAT_SH 10 // Port Line Status Shift -#define PORTSC_PRESET (1 << 8) // Port Reset -#define PORTSC_SUSPEND (1 << 7) // Port Suspend -#define PORTSC_FPRES (1 << 6) // Force Port Resume -#define PORTSC_OCC (1 << 5) // Over Current Change -#define PORTSC_OCA (1 << 4) // Over Current Active -#define PORTSC_PEDC (1 << 3) // Port Enable/Disable Change -#define PORTSC_PED (1 << 2) // Port Enable/Disable -#define PORTSC_CSC (1 << 1) // Connect Status Change -#define PORTSC_CONNECT (1 << 0) // Current Connect Status - #define FRAME_TIMER_FREQ 1000 #define FRAME_TIMER_NS (1000000000 / FRAME_TIMER_FREQ) #define UFRAME_TIMER_NS (FRAME_TIMER_NS / 8) diff --git a/hw/usb/hcd-uhci.c b/hw/usb/hcd-uhci.c index 9b1166b2ef..c3bf72cc17 100644 --- a/hw/usb/hcd-uhci.c +++ b/hw/usb/hcd-uhci.c @@ -27,6 +27,7 @@ */ #include "hw/hw.h" #include "hw/usb.h" +#include "hw/usb/uhci-regs.h" #include "hw/pci/pci.h" #include "qemu/timer.h" #include "qemu/iov.h" @@ -37,41 +38,6 @@ //#define DEBUG //#define DEBUG_DUMP_DATA -#define UHCI_CMD_FGR (1 << 4) -#define UHCI_CMD_EGSM (1 << 3) -#define UHCI_CMD_GRESET (1 << 2) -#define UHCI_CMD_HCRESET (1 << 1) -#define UHCI_CMD_RS (1 << 0) - -#define UHCI_STS_HCHALTED (1 << 5) -#define UHCI_STS_HCPERR (1 << 4) -#define UHCI_STS_HSERR (1 << 3) -#define UHCI_STS_RD (1 << 2) -#define UHCI_STS_USBERR (1 << 1) -#define UHCI_STS_USBINT (1 << 0) - -#define TD_CTRL_SPD (1 << 29) -#define TD_CTRL_ERROR_SHIFT 27 -#define TD_CTRL_IOS (1 << 25) -#define TD_CTRL_IOC (1 << 24) -#define TD_CTRL_ACTIVE (1 << 23) -#define TD_CTRL_STALL (1 << 22) -#define TD_CTRL_BABBLE (1 << 20) -#define TD_CTRL_NAK (1 << 19) -#define TD_CTRL_TIMEOUT (1 << 18) - -#define UHCI_PORT_SUSPEND (1 << 12) -#define UHCI_PORT_RESET (1 << 9) -#define UHCI_PORT_LSDA (1 << 8) -#define UHCI_PORT_RD (1 << 6) -#define UHCI_PORT_ENC (1 << 3) -#define UHCI_PORT_EN (1 << 2) -#define UHCI_PORT_CSC (1 << 1) -#define UHCI_PORT_CCS (1 << 0) - -#define UHCI_PORT_READ_ONLY (0x1bb) -#define UHCI_PORT_WRITE_CLEAR (UHCI_PORT_CSC | UHCI_PORT_ENC) - #define FRAME_TIMER_FREQ 1000 #define FRAME_MAX_LOOPS 256 diff --git a/hw/usb/hcd-xhci.c b/hw/usb/hcd-xhci.c index ef3177aee9..7f2af8925f 100644 --- a/hw/usb/hcd-xhci.c +++ b/hw/usb/hcd-xhci.c @@ -498,6 +498,7 @@ typedef struct XHCIEvRingSeg { enum xhci_flags { XHCI_FLAG_USE_MSI = 1, XHCI_FLAG_USE_MSI_X, + XHCI_FLAG_SS_FIRST, }; static void xhci_kick_ep(XHCIState *xhci, unsigned int slotid, @@ -621,6 +622,11 @@ static const char *ep_state_name(uint32_t state) ARRAY_SIZE(ep_state_names)); } +static bool xhci_get_flag(XHCIState *xhci, enum xhci_flags bit) +{ + return xhci->flags & (1 << bit); +} + static uint64_t xhci_mfindex_get(XHCIState *xhci) { int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); @@ -709,10 +715,18 @@ static XHCIPort *xhci_lookup_port(XHCIState *xhci, struct USBPort *uport) case USB_SPEED_LOW: case USB_SPEED_FULL: case USB_SPEED_HIGH: - index = uport->index; + if (xhci_get_flag(xhci, XHCI_FLAG_SS_FIRST)) { + index = uport->index + xhci->numports_3; + } else { + index = uport->index; + } break; case USB_SPEED_SUPER: - index = uport->index + xhci->numports_2; + if (xhci_get_flag(xhci, XHCI_FLAG_SS_FIRST)) { + index = uport->index; + } else { + index = uport->index + xhci->numports_2; + } break; default: return NULL; @@ -2851,7 +2865,7 @@ static void xhci_port_update(XHCIPort *port, int is_detach) static void xhci_port_reset(XHCIPort *port, bool warm_reset) { - trace_usb_xhci_port_reset(port->portnr); + trace_usb_xhci_port_reset(port->portnr, warm_reset); if (!xhci_port_have_device(port)) { return; @@ -2967,7 +2981,11 @@ static uint64_t xhci_cap_read(void *ptr, hwaddr reg, unsigned size) ret = 0x20425355; /* "USB " */ break; case 0x28: /* Supported Protocol:08 */ - ret = 0x00000001 | (xhci->numports_2<<8); + if (xhci_get_flag(xhci, XHCI_FLAG_SS_FIRST)) { + ret = (xhci->numports_2<<8) | (xhci->numports_3+1); + } else { + ret = (xhci->numports_2<<8) | 1; + } break; case 0x2c: /* Supported Protocol:0c */ ret = 0x00000000; /* reserved */ @@ -2979,7 +2997,11 @@ static uint64_t xhci_cap_read(void *ptr, hwaddr reg, unsigned size) ret = 0x20425355; /* "USB " */ break; case 0x38: /* Supported Protocol:08 */ - ret = 0x00000000 | (xhci->numports_2+1) | (xhci->numports_3<<8); + if (xhci_get_flag(xhci, XHCI_FLAG_SS_FIRST)) { + ret = (xhci->numports_3<<8) | 1; + } else { + ret = (xhci->numports_3<<8) | (xhci->numports_2+1); + } break; case 0x3c: /* Supported Protocol:0c */ ret = 0x00000000; /* reserved */ @@ -3435,7 +3457,7 @@ static void xhci_child_detach(USBPort *uport, USBDevice *child) USBBus *bus = usb_bus_from_device(child); XHCIState *xhci = container_of(bus, XHCIState, bus); - xhci_detach_slot(xhci, uport); + xhci_detach_slot(xhci, child->port); } static USBPortOps xhci_uport_ops = { @@ -3512,8 +3534,13 @@ static void usb_xhci_init(XHCIState *xhci) for (i = 0; i < usbports; i++) { speedmask = 0; if (i < xhci->numports_2) { - port = &xhci->ports[i]; - port->portnr = i + 1; + if (xhci_get_flag(xhci, XHCI_FLAG_SS_FIRST)) { + port = &xhci->ports[i + xhci->numports_3]; + port->portnr = i + 1 + xhci->numports_3; + } else { + port = &xhci->ports[i]; + port->portnr = i + 1; + } port->uport = &xhci->uports[i]; port->speedmask = USB_SPEED_MASK_LOW | @@ -3523,8 +3550,13 @@ static void usb_xhci_init(XHCIState *xhci) speedmask |= port->speedmask; } if (i < xhci->numports_3) { - port = &xhci->ports[i + xhci->numports_2]; - port->portnr = i + 1 + xhci->numports_2; + if (xhci_get_flag(xhci, XHCI_FLAG_SS_FIRST)) { + port = &xhci->ports[i]; + port->portnr = i + 1; + } else { + port = &xhci->ports[i + xhci->numports_2]; + port->portnr = i + 1 + xhci->numports_2; + } port->uport = &xhci->uports[i]; port->speedmask = USB_SPEED_MASK_SUPER; snprintf(port->name, sizeof(port->name), "usb3 port #%d", i+1); @@ -3594,13 +3626,15 @@ static int usb_xhci_initfn(struct PCIDevice *dev) PCI_BASE_ADDRESS_SPACE_MEMORY|PCI_BASE_ADDRESS_MEM_TYPE_64, &xhci->mem); - ret = pcie_endpoint_cap_init(dev, 0xa0); - assert(ret >= 0); + if (pci_bus_is_express(dev->bus)) { + ret = pcie_endpoint_cap_init(dev, 0xa0); + assert(ret >= 0); + } - if (xhci->flags & (1 << XHCI_FLAG_USE_MSI)) { + if (xhci_get_flag(xhci, XHCI_FLAG_USE_MSI)) { msi_init(dev, 0x70, xhci->numintrs, true, false); } - if (xhci->flags & (1 << XHCI_FLAG_USE_MSI_X)) { + if (xhci_get_flag(xhci, XHCI_FLAG_USE_MSI_X)) { msix_init(dev, xhci->numintrs, &xhci->mem, 0, OFF_MSIX_TABLE, &xhci->mem, 0, OFF_MSIX_PBA, @@ -3781,6 +3815,8 @@ static const VMStateDescription vmstate_xhci = { static Property xhci_properties[] = { DEFINE_PROP_BIT("msi", XHCIState, flags, XHCI_FLAG_USE_MSI, true), DEFINE_PROP_BIT("msix", XHCIState, flags, XHCI_FLAG_USE_MSI_X, true), + DEFINE_PROP_BIT("superspeed-ports-first", + XHCIState, flags, XHCI_FLAG_SS_FIRST, true), DEFINE_PROP_UINT32("intrs", XHCIState, numintrs, MAXINTRS), DEFINE_PROP_UINT32("slots", XHCIState, numslots, MAXSLOTS), DEFINE_PROP_UINT32("p2", XHCIState, numports_2, 4), diff --git a/hw/usb/host-libusb.c b/hw/usb/host-libusb.c index 57bed09a1e..afbf1563f4 100644 --- a/hw/usb/host-libusb.c +++ b/hw/usb/host-libusb.c @@ -111,6 +111,7 @@ struct USBHostRequest { unsigned char *buffer; unsigned char *cbuf; unsigned int clen; + bool usb3ep0quirk; QTAILQ_ENTRY(USBHostRequest) next; }; @@ -146,6 +147,10 @@ static void usb_host_attach_kernel(USBHostDevice *s); #define BULK_TIMEOUT 0 /* unlimited */ #define INTR_TIMEOUT 0 /* unlimited */ +#if LIBUSBX_API_VERSION >= 0x01000103 +# define HAVE_STREAMS 1 +#endif + static const char *speed_name[] = { [LIBUSB_SPEED_UNKNOWN] = "?", [LIBUSB_SPEED_LOW] = "1.5", @@ -346,6 +351,13 @@ static void usb_host_req_complete_ctrl(struct libusb_transfer *xfer) r->p->actual_length = xfer->actual_length; if (r->in && xfer->actual_length) { memcpy(r->cbuf, r->buffer + 8, xfer->actual_length); + + /* Fix up USB-3 ep0 maxpacket size to allow superspeed connected devices + * to work redirected to a not superspeed capable hcd */ + if (r->usb3ep0quirk && xfer->actual_length >= 18 && + r->cbuf[7] == 9) { + r->cbuf[7] = 64; + } } trace_usb_host_req_complete(s->bus_num, s->addr, r->p, r->p->status, r->p->actual_length); @@ -672,11 +684,17 @@ static void usb_host_iso_data_out(USBHostDevice *s, USBPacket *p) /* ------------------------------------------------------------------------ */ -static bool usb_host_full_speed_compat(USBHostDevice *s) +static void usb_host_speed_compat(USBHostDevice *s) { + USBDevice *udev = USB_DEVICE(s); struct libusb_config_descriptor *conf; const struct libusb_interface_descriptor *intf; const struct libusb_endpoint_descriptor *endp; +#ifdef HAVE_STREAMS + struct libusb_ss_endpoint_companion_descriptor *endp_ss_comp; +#endif + bool compat_high = true; + bool compat_full = true; uint8_t type; int rc, c, i, a, e; @@ -693,10 +711,27 @@ static bool usb_host_full_speed_compat(USBHostDevice *s) type = endp->bmAttributes & 0x3; switch (type) { case 0x01: /* ISO */ - return false; + compat_full = false; + compat_high = false; + break; + case 0x02: /* BULK */ +#ifdef HAVE_STREAMS + rc = libusb_get_ss_endpoint_companion_descriptor + (ctx, endp, &endp_ss_comp); + if (rc == LIBUSB_SUCCESS) { + libusb_free_ss_endpoint_companion_descriptor + (endp_ss_comp); + compat_full = false; + compat_high = false; + } +#endif + break; case 0x03: /* INTERRUPT */ if (endp->wMaxPacketSize > 64) { - return false; + compat_full = false; + } + if (endp->wMaxPacketSize > 1024) { + compat_high = false; } break; } @@ -705,7 +740,17 @@ static bool usb_host_full_speed_compat(USBHostDevice *s) } libusb_free_config_descriptor(conf); } - return true; + + udev->speedmask = (1 << udev->speed); + if (udev->speed == USB_SPEED_SUPER && compat_high) { + udev->speedmask |= USB_SPEED_HIGH; + } + if (udev->speed == USB_SPEED_SUPER && compat_full) { + udev->speedmask |= USB_SPEED_FULL; + } + if (udev->speed == USB_SPEED_HIGH && compat_full) { + udev->speedmask |= USB_SPEED_FULL; + } } static void usb_host_ep_update(USBHostDevice *s) @@ -720,6 +765,9 @@ static void usb_host_ep_update(USBHostDevice *s) struct libusb_config_descriptor *conf; const struct libusb_interface_descriptor *intf; const struct libusb_endpoint_descriptor *endp; +#ifdef HAVE_STREAMS + struct libusb_ss_endpoint_companion_descriptor *endp_ss_comp; +#endif uint8_t devep, type; int pid, ep; int rc, i, e; @@ -765,6 +813,15 @@ static void usb_host_ep_update(USBHostDevice *s) usb_ep_set_type(udev, pid, ep, type); usb_ep_set_ifnum(udev, pid, ep, i); usb_ep_set_halted(udev, pid, ep, 0); +#ifdef HAVE_STREAMS + if (type == LIBUSB_TRANSFER_TYPE_BULK && + libusb_get_ss_endpoint_companion_descriptor(ctx, endp, + &endp_ss_comp) == LIBUSB_SUCCESS) { + usb_ep_set_max_streams(udev, pid, ep, + endp_ss_comp->bmAttributes); + libusb_free_ss_endpoint_companion_descriptor(endp_ss_comp); + } +#endif } } @@ -801,10 +858,7 @@ static int usb_host_open(USBHostDevice *s, libusb_device *dev) usb_host_ep_update(s); udev->speed = speed_map[libusb_get_device_speed(dev)]; - udev->speedmask = (1 << udev->speed); - if (udev->speed == USB_SPEED_HIGH && usb_host_full_speed_compat(s)) { - udev->speedmask |= USB_SPEED_MASK_FULL; - } + usb_host_speed_compat(s); if (s->ddesc.iProduct) { libusb_get_string_descriptor_ascii(s->dh, s->ddesc.iProduct, @@ -1150,6 +1204,14 @@ static void usb_host_handle_control(USBDevice *udev, USBPacket *p, memcpy(r->buffer + 8, r->cbuf, r->clen); } + /* Fix up USB-3 ep0 maxpacket size to allow superspeed connected devices + * to work redirected to a not superspeed capable hcd */ + if (udev->speed == USB_SPEED_SUPER && + !((udev->port->speedmask & USB_SPEED_MASK_SUPER)) && + request == 0x8006 && value == 0x100 && index == 0) { + r->usb3ep0quirk = true; + } + libusb_fill_control_transfer(r->xfer, s->dh, r->buffer, usb_host_req_complete_ctrl, r, CONTROL_TIMEOUT); @@ -1202,10 +1264,23 @@ static void usb_host_handle_data(USBDevice *udev, USBPacket *p) usb_packet_copy(p, r->buffer, size); } ep = p->ep->nr | (r->in ? USB_DIR_IN : 0); - libusb_fill_bulk_transfer(r->xfer, s->dh, ep, - r->buffer, size, - usb_host_req_complete_data, r, - BULK_TIMEOUT); + if (p->stream) { +#ifdef HAVE_STREAMS + libusb_fill_bulk_stream_transfer(r->xfer, s->dh, ep, p->stream, + r->buffer, size, + usb_host_req_complete_data, r, + BULK_TIMEOUT); +#else + usb_host_req_free(r); + p->status = USB_RET_STALL; + return; +#endif + } else { + libusb_fill_bulk_transfer(r->xfer, s->dh, ep, + r->buffer, size, + usb_host_req_complete_data, r, + BULK_TIMEOUT); + } break; case USB_ENDPOINT_XFER_INT: r = usb_host_req_alloc(s, p, p->pid == USB_TOKEN_IN, p->iov.size); @@ -1268,6 +1343,54 @@ static void usb_host_handle_reset(USBDevice *udev) } } +static int usb_host_alloc_streams(USBDevice *udev, USBEndpoint **eps, + int nr_eps, int streams) +{ +#ifdef HAVE_STREAMS + USBHostDevice *s = USB_HOST_DEVICE(udev); + unsigned char endpoints[30]; + int i, rc; + + for (i = 0; i < nr_eps; i++) { + endpoints[i] = eps[i]->nr; + if (eps[i]->pid == USB_TOKEN_IN) { + endpoints[i] |= 0x80; + } + } + rc = libusb_alloc_streams(s->dh, streams, endpoints, nr_eps); + if (rc < 0) { + usb_host_libusb_error("libusb_alloc_streams", rc); + } else if (rc != streams) { + fprintf(stderr, + "libusb_alloc_streams: got less streams then requested %d < %d\n", + rc, streams); + } + + return (rc == streams) ? 0 : -1; +#else + fprintf(stderr, "libusb_alloc_streams: error not implemented\n"); + return -1; +#endif +} + +static void usb_host_free_streams(USBDevice *udev, USBEndpoint **eps, + int nr_eps) +{ +#ifdef HAVE_STREAMS + USBHostDevice *s = USB_HOST_DEVICE(udev); + unsigned char endpoints[30]; + int i; + + for (i = 0; i < nr_eps; i++) { + endpoints[i] = eps[i]->nr; + if (eps[i]->pid == USB_TOKEN_IN) { + endpoints[i] |= 0x80; + } + } + libusb_free_streams(s->dh, endpoints, nr_eps); +#endif +} + /* * This is *NOT* about restoring state. We have absolutely no idea * what state the host device is in at the moment and whenever it is @@ -1349,6 +1472,8 @@ static void usb_host_class_initfn(ObjectClass *klass, void *data) uc->handle_reset = usb_host_handle_reset; uc->handle_destroy = usb_host_handle_destroy; uc->flush_ep_queue = usb_host_flush_ep_queue; + uc->alloc_streams = usb_host_alloc_streams; + uc->free_streams = usb_host_free_streams; dc->vmsd = &vmstate_usb_host; dc->props = usb_host_dev_properties; set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); diff --git a/hw/usb/redirect.c b/hw/usb/redirect.c index 287a505b48..4c6187bebd 100644 --- a/hw/usb/redirect.c +++ b/hw/usb/redirect.c @@ -50,6 +50,10 @@ ((i) & 0x10) ? USB_TOKEN_IN : USB_TOKEN_OUT, \ (i) & 0x0f)) +#ifndef USBREDIR_VERSION /* This is not defined in older usbredir versions */ +#define USBREDIR_VERSION 0 +#endif + typedef struct USBRedirDevice USBRedirDevice; /* Struct to hold buffered packets */ @@ -68,6 +72,7 @@ struct endp_data { uint8_t interval; uint8_t interface; /* bInterfaceNumber this ep belongs to */ uint16_t max_packet_size; /* In bytes, not wMaxPacketSize format !! */ + uint32_t max_streams; uint8_t iso_started; uint8_t iso_error; /* For reporting iso errors to the HC */ uint8_t interrupt_started; @@ -106,8 +111,9 @@ struct USBRedirDevice { int read_buf_size; /* Active chardev-watch-tag */ guint watch; - /* For async handling of close */ + /* For async handling of close / reject */ QEMUBH *chardev_close_bh; + QEMUBH *device_reject_bh; /* To delay the usb attach in case of quick chardev close + open */ QEMUTimer *attach_timer; int64_t next_attach_time; @@ -780,11 +786,12 @@ static void usbredir_handle_bulk_data(USBRedirDevice *dev, USBPacket *p, dev->endpoint[EP2I(ep)].bulk_receiving_enabled = 0; } - DPRINTF("bulk-out ep %02X len %zd id %"PRIu64"\n", ep, size, p->id); + DPRINTF("bulk-out ep %02X stream %u len %zd id %"PRIu64"\n", + ep, p->stream, size, p->id); bulk_packet.endpoint = ep; bulk_packet.length = size; - bulk_packet.stream_id = 0; + bulk_packet.stream_id = p->stream; bulk_packet.length_high = size >> 16; assert(bulk_packet.length_high == 0 || usbredirparser_peer_has_cap(dev->parser, @@ -1091,6 +1098,66 @@ static void usbredir_handle_control(USBDevice *udev, USBPacket *p, p->status = USB_RET_ASYNC; } +static int usbredir_alloc_streams(USBDevice *udev, USBEndpoint **eps, + int nr_eps, int streams) +{ + USBRedirDevice *dev = DO_UPCAST(USBRedirDevice, dev, udev); +#if USBREDIR_VERSION >= 0x000700 + struct usb_redir_alloc_bulk_streams_header alloc_streams; + int i; + + if (!usbredirparser_peer_has_cap(dev->parser, + usb_redir_cap_bulk_streams)) { + ERROR("peer does not support streams\n"); + goto reject; + } + + if (streams == 0) { + ERROR("request to allocate 0 streams\n"); + return -1; + } + + alloc_streams.no_streams = streams; + alloc_streams.endpoints = 0; + for (i = 0; i < nr_eps; i++) { + alloc_streams.endpoints |= 1 << USBEP2I(eps[i]); + } + usbredirparser_send_alloc_bulk_streams(dev->parser, 0, &alloc_streams); + usbredirparser_do_write(dev->parser); + + return 0; +#else + ERROR("usbredir_alloc_streams not implemented\n"); + goto reject; +#endif +reject: + ERROR("streams are not available, disconnecting\n"); + qemu_bh_schedule(dev->device_reject_bh); + return -1; +} + +static void usbredir_free_streams(USBDevice *udev, USBEndpoint **eps, + int nr_eps) +{ +#if USBREDIR_VERSION >= 0x000700 + USBRedirDevice *dev = DO_UPCAST(USBRedirDevice, dev, udev); + struct usb_redir_free_bulk_streams_header free_streams; + int i; + + if (!usbredirparser_peer_has_cap(dev->parser, + usb_redir_cap_bulk_streams)) { + return; + } + + free_streams.endpoints = 0; + for (i = 0; i < nr_eps; i++) { + free_streams.endpoints |= 1 << USBEP2I(eps[i]); + } + usbredirparser_send_free_bulk_streams(dev->parser, 0, &free_streams); + usbredirparser_do_write(dev->parser); +#endif +} + /* * Close events can be triggered by usbredirparser_do_write which gets called * from within the USBDevice data / control packet callbacks and doing a @@ -1102,6 +1169,7 @@ static void usbredir_chardev_close_bh(void *opaque) { USBRedirDevice *dev = opaque; + qemu_bh_cancel(dev->device_reject_bh); usbredir_device_disconnect(dev); if (dev->parser) { @@ -1153,6 +1221,9 @@ static void usbredir_create_parser(USBRedirDevice *dev) 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 USBREDIR_VERSION >= 0x000700 + usbredirparser_caps_set_cap(caps, usb_redir_cap_bulk_streams); +#endif if (runstate_check(RUN_STATE_INMIGRATE)) { flags |= usbredirparser_fl_no_hello; @@ -1171,6 +1242,17 @@ static void usbredir_reject_device(USBRedirDevice *dev) } } +/* + * We may need to reject the device when the hcd calls alloc_streams, doing + * an usb_detach from within a hcd call is not a good idea, hence this bh. + */ +static void usbredir_device_reject_bh(void *opaque) +{ + USBRedirDevice *dev = opaque; + + usbredir_reject_device(dev); +} + static void usbredir_do_attach(void *opaque) { USBRedirDevice *dev = opaque; @@ -1297,6 +1379,7 @@ static int usbredir_initfn(USBDevice *udev) } dev->chardev_close_bh = qemu_bh_new(usbredir_chardev_close_bh, dev); + dev->device_reject_bh = qemu_bh_new(usbredir_device_reject_bh, dev); dev->attach_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, usbredir_do_attach, dev); packet_id_queue_init(&dev->cancelled, dev, "cancelled"); @@ -1337,6 +1420,7 @@ static void usbredir_handle_destroy(USBDevice *udev) dev->cs = NULL; /* Note must be done after qemu_chr_close, as that causes a close event */ qemu_bh_delete(dev->chardev_close_bh); + qemu_bh_delete(dev->device_reject_bh); timer_del(dev->attach_timer); timer_free(dev->attach_timer); @@ -1628,6 +1712,7 @@ static void usbredir_setup_usb_eps(USBRedirDevice *dev) usb_ep->type = dev->endpoint[i].type; usb_ep->ifnum = dev->endpoint[i].interface; usb_ep->max_packet_size = dev->endpoint[i].max_packet_size; + usb_ep->max_streams = dev->endpoint[i].max_streams; usbredir_set_pipeline(dev, usb_ep); } } @@ -1646,6 +1731,12 @@ static void usbredir_ep_info(void *priv, usb_redir_cap_ep_info_max_packet_size)) { dev->endpoint[i].max_packet_size = ep_info->max_packet_size[i]; } +#if USBREDIR_VERSION >= 0x000700 + if (usbredirparser_peer_has_cap(dev->parser, + usb_redir_cap_bulk_streams)) { + dev->endpoint[i].max_streams = ep_info->max_streams[i]; + } +#endif switch (dev->endpoint[i].type) { case usb_redir_type_invalid: break; @@ -1779,6 +1870,20 @@ static void usbredir_interrupt_receiving_status(void *priv, uint64_t id, static void usbredir_bulk_streams_status(void *priv, uint64_t id, struct usb_redir_bulk_streams_status_header *bulk_streams_status) { +#if USBREDIR_VERSION >= 0x000700 + USBRedirDevice *dev = priv; + + if (bulk_streams_status->status == usb_redir_success) { + DPRINTF("bulk streams status %d eps %08x\n", + bulk_streams_status->status, bulk_streams_status->endpoints); + } else { + ERROR("bulk streams %s failed status %d eps %08x\n", + (bulk_streams_status->no_streams == 0) ? "free" : "alloc", + bulk_streams_status->status, bulk_streams_status->endpoints); + ERROR("usb-redir-host does not provide streams, disconnecting\n"); + usbredir_reject_device(dev); + } +#endif } static void usbredir_bulk_receiving_status(void *priv, uint64_t id, @@ -1850,8 +1955,8 @@ static void usbredir_bulk_packet(void *priv, uint64_t id, int len = (bulk_packet->length_high << 16) | bulk_packet->length; USBPacket *p; - DPRINTF("bulk-in status %d ep %02X len %d id %"PRIu64"\n", - bulk_packet->status, ep, len, id); + DPRINTF("bulk-in status %d ep %02X stream %u len %d id %"PRIu64"\n", + bulk_packet->status, ep, bulk_packet->stream_id, len, id); p = usbredir_find_packet_by_id(dev, ep, id); if (p) { @@ -2165,6 +2270,23 @@ static bool usbredir_bulk_receiving_needed(void *priv) return endp->bulk_receiving_started; } +static const VMStateDescription usbredir_stream_vmstate = { + .name = "usb-redir-ep/stream-state", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(max_streams, struct endp_data), + VMSTATE_END_OF_LIST() + } +}; + +static bool usbredir_stream_needed(void *priv) +{ + struct endp_data *endp = priv; + + return endp->max_streams; +} + static const VMStateDescription usbredir_ep_vmstate = { .name = "usb-redir-ep", .version_id = 1, @@ -2197,6 +2319,9 @@ static const VMStateDescription usbredir_ep_vmstate = { .vmsd = &usbredir_bulk_receiving_vmstate, .needed = usbredir_bulk_receiving_needed, }, { + .vmsd = &usbredir_stream_vmstate, + .needed = usbredir_stream_needed, + }, { /* empty */ } } @@ -2361,6 +2486,8 @@ static void usbredir_class_initfn(ObjectClass *klass, void *data) uc->handle_control = usbredir_handle_control; uc->flush_ep_queue = usbredir_flush_ep_queue; uc->ep_stopped = usbredir_ep_stopped; + uc->alloc_streams = usbredir_alloc_streams; + uc->free_streams = usbredir_free_streams; dc->vmsd = &usbredir_vmstate; dc->props = usbredir_properties; set_bit(DEVICE_CATEGORY_MISC, dc->categories); |