diff options
-rwxr-xr-x | configure | 36 | ||||
-rw-r--r-- | hw/usb/dev-serial.c | 9 | ||||
-rw-r--r-- | hw/usb/hcd-xhci.c | 79 | ||||
-rw-r--r-- | hw/usb/host-libusb.c | 1449 | ||||
-rw-r--r-- | hw/usb/host-linux.c | 14 | ||||
-rw-r--r-- | trace-events | 5 |
6 files changed, 1551 insertions, 41 deletions
@@ -226,6 +226,7 @@ trace_file="trace" spice="" rbd="" smartcard_nss="" +libusb="" usb_redir="" glx="" zlib="yes" @@ -890,6 +891,10 @@ for opt do ;; --enable-smartcard-nss) smartcard_nss="yes" ;; + --disable-libusb) libusb="no" + ;; + --enable-libusb) libusb="yes" + ;; --disable-usb-redir) usb_redir="no" ;; --enable-usb-redir) usb_redir="yes" @@ -1175,6 +1180,8 @@ echo " --disable-libiscsi disable iscsi support" echo " --enable-libiscsi enable iscsi support" echo " --disable-smartcard-nss disable smartcard nss support" echo " --enable-smartcard-nss enable smartcard nss support" +echo " --disable-libusb disable libusb (for usb passthrough)" +echo " --enable-libusb enable libusb (for usb passthrough)" echo " --disable-usb-redir disable usb network redirection support" echo " --enable-usb-redir enable usb network redirection support" echo " --disable-guest-agent disable building of the QEMU Guest Agent" @@ -3005,6 +3012,23 @@ EOF fi fi +# check for libusb +if test "$libusb" != "no" ; then + if $pkg_config libusb-1.0 >/dev/null 2>&1 ; then + libusb="yes" + usb="libusb" + libusb_cflags=$($pkg_config --cflags libusb-1.0 2>/dev/null) + libusb_libs=$($pkg_config --libs libusb-1.0 2>/dev/null) + QEMU_CFLAGS="$QEMU_CFLAGS $libusb_cflags" + libs_softmmu="$libs_softmmu $libusb_libs" + else + if test "$libusb" = "yes"; then + feature_not_found "libusb" + fi + libusb="no" + fi +fi + # check for usbredirparser for usb network redirection support if test "$usb_redir" != "no" ; then if $pkg_config --atleast-version=0.6 libusbredirparser-0.5 >/dev/null 2>&1 ; then @@ -3516,6 +3540,7 @@ echo "spice support $spice ($spice_protocol_version/$spice_server_version)" echo "rbd support $rbd" echo "xfsctl support $xfs" echo "nss used $smartcard_nss" +echo "libusb $libusb" echo "usb net redir $usb_redir" echo "GLX support $glx" echo "libiscsi support $libiscsi" @@ -3823,6 +3848,10 @@ if test "$smartcard_nss" = "yes" ; then echo "libcacard_cflags=$libcacard_cflags" >> $config_host_mak fi +if test "$libusb" = "yes" ; then + echo "CONFIG_USB_LIBUSB=y" >> $config_host_mak +fi + if test "$usb_redir" = "yes" ; then echo "CONFIG_USB_REDIR=y" >> $config_host_mak fi @@ -3907,6 +3936,13 @@ linux) bsd) echo "HOST_USB=bsd" >> $config_host_mak ;; +libusb) + if test "$linux" = "yes"; then + echo "HOST_USB=libusb linux legacy" >> $config_host_mak + else + echo "HOST_USB=libusb legacy" >> $config_host_mak + fi +;; *) echo "HOST_USB=stub" >> $config_host_mak ;; diff --git a/hw/usb/dev-serial.c b/hw/usb/dev-serial.c index dd0a608..2fc8a3b 100644 --- a/hw/usb/dev-serial.c +++ b/hw/usb/dev-serial.c @@ -410,13 +410,6 @@ static void usb_serial_handle_data(USBDevice *dev, USBPacket *p) } } -static void usb_serial_handle_destroy(USBDevice *dev) -{ - USBSerialState *s = (USBSerialState *)dev; - - qemu_chr_add_handlers(s->cs, NULL, NULL, NULL, NULL); -} - static int usb_serial_can_read(void *opaque) { USBSerialState *s = opaque; @@ -595,7 +588,6 @@ static void usb_serial_class_initfn(ObjectClass *klass, void *data) uc->handle_reset = usb_serial_handle_reset; uc->handle_control = usb_serial_handle_control; uc->handle_data = usb_serial_handle_data; - uc->handle_destroy = usb_serial_handle_destroy; dc->vmsd = &vmstate_usb_serial; dc->props = serial_properties; } @@ -623,7 +615,6 @@ static void usb_braille_class_initfn(ObjectClass *klass, void *data) uc->handle_reset = usb_serial_handle_reset; uc->handle_control = usb_serial_handle_control; uc->handle_data = usb_serial_handle_data; - uc->handle_destroy = usb_serial_handle_destroy; dc->vmsd = &vmstate_usb_serial; dc->props = braille_properties; } diff --git a/hw/usb/hcd-xhci.c b/hw/usb/hcd-xhci.c index efd4b0d..a26b78e 100644 --- a/hw/usb/hcd-xhci.c +++ b/hw/usb/hcd-xhci.c @@ -408,7 +408,6 @@ typedef struct XHCISlot { bool enabled; dma_addr_t ctx; USBPort *uport; - unsigned int devaddr; XHCIEPContext * eps[31]; } XHCISlot; @@ -452,7 +451,6 @@ struct XHCIState { MemoryRegion mem_oper; MemoryRegion mem_runtime; MemoryRegion mem_doorbell; - unsigned int devaddr; /* properties */ uint32_t numports_2; @@ -2141,16 +2139,18 @@ static TRBCCode xhci_address_slot(XHCIState *xhci, unsigned int slotid, slot_ctx[3] = SLOT_DEFAULT << SLOT_STATE_SHIFT; } else { USBPacket p; - slot->devaddr = xhci->devaddr++; - slot_ctx[3] = (SLOT_ADDRESSED << SLOT_STATE_SHIFT) | slot->devaddr; - DPRINTF("xhci: device address is %d\n", slot->devaddr); + uint8_t buf[1]; + + slot_ctx[3] = (SLOT_ADDRESSED << SLOT_STATE_SHIFT) | slotid; usb_device_reset(dev); + memset(&p, 0, sizeof(p)); + usb_packet_addbuf(&p, buf, sizeof(buf)); usb_packet_setup(&p, USB_TOKEN_OUT, usb_ep_get(dev, USB_TOKEN_OUT, 0), 0, 0, false, false); usb_device_handle_control(dev, &p, DeviceOutRequest | USB_REQ_SET_ADDRESS, - slot->devaddr, 0, 0, NULL); + slotid, 0, 0, NULL); assert(p.status != USB_RET_ASYNC); } @@ -2526,7 +2526,6 @@ static void xhci_process_commands(XHCIState *xhci) } break; case CR_SET_TR_DEQUEUE: - fprintf(stderr, "%s: CR_SET_TR_DEQUEUE\n", __func__); slotid = xhci_get_slot(xhci, &event, &trb); if (slotid) { unsigned int epid = (trb.control >> TRB_CR_EPID_SHIFT) @@ -2593,6 +2592,7 @@ static void xhci_port_notify(XHCIPort *port, uint32_t bits) if ((port->portsc & bits) == bits) { return; } + trace_usb_xhci_port_notify(port->portnr, bits); port->portsc |= bits; if (!xhci_running(port->xhci)) { return; @@ -2674,7 +2674,6 @@ static void xhci_reset(DeviceState *dev) xhci->dcbaap_low = 0; xhci->dcbaap_high = 0; xhci->config = 0; - xhci->devaddr = 2; for (i = 0; i < xhci->numslots; i++) { xhci_disable_slot(xhci, i+1); @@ -2799,29 +2798,56 @@ static void xhci_port_write(void *ptr, hwaddr reg, uint64_t val, unsigned size) { XHCIPort *port = ptr; - uint32_t portsc; + uint32_t portsc, notify; trace_usb_xhci_port_write(port->portnr, reg, val); switch (reg) { case 0x00: /* PORTSC */ + /* write-1-to-start bits */ + if (val & PORTSC_PR) { + xhci_port_reset(port); + break; + } + portsc = port->portsc; + notify = 0; /* write-1-to-clear bits*/ portsc &= ~(val & (PORTSC_CSC|PORTSC_PEC|PORTSC_WRC|PORTSC_OCC| PORTSC_PRC|PORTSC_PLC|PORTSC_CEC)); if (val & PORTSC_LWS) { /* overwrite PLS only when LWS=1 */ - uint32_t pls = get_field(val, PORTSC_PLS); - set_field(&portsc, pls, PORTSC_PLS); - trace_usb_xhci_port_link(port->portnr, pls); + uint32_t old_pls = get_field(port->portsc, PORTSC_PLS); + uint32_t new_pls = get_field(val, PORTSC_PLS); + switch (new_pls) { + case PLS_U0: + if (old_pls != PLS_U0) { + set_field(&portsc, new_pls, PORTSC_PLS); + trace_usb_xhci_port_link(port->portnr, new_pls); + notify = PORTSC_PLC; + } + break; + case PLS_U3: + if (old_pls < PLS_U3) { + set_field(&portsc, new_pls, PORTSC_PLS); + trace_usb_xhci_port_link(port->portnr, new_pls); + } + break; + case PLS_RESUME: + /* windows does this for some reason, don't spam stderr */ + break; + default: + fprintf(stderr, "%s: ignore pls write (old %d, new %d)\n", + __func__, old_pls, new_pls); + break; + } } /* read/write bits */ portsc &= ~(PORTSC_PP|PORTSC_WCE|PORTSC_WDE|PORTSC_WOE); portsc |= (val & (PORTSC_PP|PORTSC_WCE|PORTSC_WDE|PORTSC_WOE)); port->portsc = portsc; - /* write-1-to-start bits */ - if (val & PORTSC_PR) { - xhci_port_reset(port); + if (notify) { + xhci_port_notify(port, notify); } break; case 0x04: /* PORTPMSC */ @@ -3080,8 +3106,15 @@ static void xhci_doorbell_write(void *ptr, hwaddr reg, } } +static void xhci_cap_write(void *opaque, hwaddr addr, uint64_t val, + unsigned width) +{ + /* nothing */ +} + static const MemoryRegionOps xhci_cap_ops = { .read = xhci_cap_read, + .write = xhci_cap_write, .valid.min_access_size = 1, .valid.max_access_size = 4, .impl.min_access_size = 4, @@ -3178,20 +3211,6 @@ static USBPortOps xhci_uport_ops = { .child_detach = xhci_child_detach, }; -static int xhci_find_slotid(XHCIState *xhci, USBDevice *dev) -{ - XHCISlot *slot; - int slotid; - - for (slotid = 1; slotid <= xhci->numslots; slotid++) { - slot = &xhci->slots[slotid-1]; - if (slot->devaddr == dev->addr) { - return slotid; - } - } - return 0; -} - static int xhci_find_epid(USBEndpoint *ep) { if (ep->nr == 0) { @@ -3211,7 +3230,7 @@ static void xhci_wakeup_endpoint(USBBus *bus, USBEndpoint *ep, int slotid; DPRINTF("%s\n", __func__); - slotid = xhci_find_slotid(xhci, ep->dev); + slotid = ep->dev->addr; if (slotid == 0 || !xhci->slots[slotid-1].enabled) { DPRINTF("%s: oops, no slot for dev %d\n", __func__, ep->dev->addr); return; diff --git a/hw/usb/host-libusb.c b/hw/usb/host-libusb.c new file mode 100644 index 0000000..29f35b3 --- /dev/null +++ b/hw/usb/host-libusb.c @@ -0,0 +1,1449 @@ +/* + * Linux host USB redirector + * + * Copyright (c) 2005 Fabrice Bellard + * + * Copyright (c) 2008 Max Krasnyansky + * Support for host device auto connect & disconnect + * Major rewrite to support fully async operation + * + * Copyright 2008 TJ <linux@tjworld.net> + * Added flexible support for /dev/bus/usb /sys/bus/usb/devices in addition + * to the legacy /proc/bus/usb USB device discovery and handling + * + * (c) 2012 Gerd Hoffmann <kraxel@redhat.com> + * Completely rewritten to use libusb instead of usbfs ioctls. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include <poll.h> +#include <libusb.h> + +#include "qemu-common.h" +#include "monitor/monitor.h" +#include "sysemu/sysemu.h" +#include "trace.h" + +#include "hw/usb.h" + +/* ------------------------------------------------------------------------ */ + +#define TYPE_USB_HOST_DEVICE "usb-host" +#define USB_HOST_DEVICE(obj) \ + OBJECT_CHECK(USBHostDevice, (obj), TYPE_USB_HOST_DEVICE) + +typedef struct USBHostDevice USBHostDevice; +typedef struct USBHostRequest USBHostRequest; +typedef struct USBHostIsoXfer USBHostIsoXfer; +typedef struct USBHostIsoRing USBHostIsoRing; + +struct USBAutoFilter { + uint32_t bus_num; + uint32_t addr; + char *port; + uint32_t vendor_id; + uint32_t product_id; +}; + +enum USBHostDeviceOptions { + USB_HOST_OPT_PIPELINE, +}; + +struct USBHostDevice { + USBDevice parent_obj; + + /* properties */ + struct USBAutoFilter match; + int32_t bootindex; + uint32_t iso_urb_count; + uint32_t iso_urb_frames; + uint32_t options; + uint32_t loglevel; + + /* state */ + QTAILQ_ENTRY(USBHostDevice) next; + int seen, errcount; + int bus_num; + int addr; + char port[16]; + + libusb_device *dev; + libusb_device_handle *dh; + struct libusb_device_descriptor ddesc; + + struct { + bool detached; + bool claimed; + } ifs[USB_MAX_INTERFACES]; + + /* callbacks & friends */ + QEMUBH *bh; + Notifier exit; + + /* request queues */ + QTAILQ_HEAD(, USBHostRequest) requests; + QTAILQ_HEAD(, USBHostIsoRing) isorings; +}; + +struct USBHostRequest { + USBHostDevice *host; + USBPacket *p; + bool in; + struct libusb_transfer *xfer; + unsigned char *buffer; + unsigned char *cbuf; + unsigned int clen; + QTAILQ_ENTRY(USBHostRequest) next; +}; + +struct USBHostIsoXfer { + USBHostIsoRing *ring; + struct libusb_transfer *xfer; + bool copy_complete; + unsigned int packet; + QTAILQ_ENTRY(USBHostIsoXfer) next; +}; + +struct USBHostIsoRing { + USBHostDevice *host; + USBEndpoint *ep; + QTAILQ_HEAD(, USBHostIsoXfer) unused; + QTAILQ_HEAD(, USBHostIsoXfer) inflight; + QTAILQ_HEAD(, USBHostIsoXfer) copy; + QTAILQ_ENTRY(USBHostIsoRing) next; +}; + +static QTAILQ_HEAD(, USBHostDevice) hostdevs = + QTAILQ_HEAD_INITIALIZER(hostdevs); + +static void usb_host_auto_check(void *unused); +static void usb_host_release_interfaces(USBHostDevice *s); +static void usb_host_nodev(USBHostDevice *s); +static void usb_host_attach_kernel(USBHostDevice *s); + +/* ------------------------------------------------------------------------ */ + +#define CONTROL_TIMEOUT 10000 /* 10 sec */ +#define BULK_TIMEOUT 0 /* unlimited */ +#define INTR_TIMEOUT 0 /* unlimited */ + +static const char *speed_name[] = { + [LIBUSB_SPEED_UNKNOWN] = "?", + [LIBUSB_SPEED_LOW] = "1.5", + [LIBUSB_SPEED_FULL] = "12", + [LIBUSB_SPEED_HIGH] = "480", + [LIBUSB_SPEED_SUPER] = "5000", +}; + +static const unsigned int speed_map[] = { + [LIBUSB_SPEED_LOW] = USB_SPEED_LOW, + [LIBUSB_SPEED_FULL] = USB_SPEED_FULL, + [LIBUSB_SPEED_HIGH] = USB_SPEED_HIGH, + [LIBUSB_SPEED_SUPER] = USB_SPEED_SUPER, +}; + +static const unsigned int status_map[] = { + [LIBUSB_TRANSFER_COMPLETED] = USB_RET_SUCCESS, + [LIBUSB_TRANSFER_ERROR] = USB_RET_IOERROR, + [LIBUSB_TRANSFER_TIMED_OUT] = USB_RET_IOERROR, + [LIBUSB_TRANSFER_CANCELLED] = USB_RET_IOERROR, + [LIBUSB_TRANSFER_STALL] = USB_RET_STALL, + [LIBUSB_TRANSFER_NO_DEVICE] = USB_RET_NODEV, + [LIBUSB_TRANSFER_OVERFLOW] = USB_RET_BABBLE, +}; + +static const char *err_names[] = { + [-LIBUSB_ERROR_IO] = "IO", + [-LIBUSB_ERROR_INVALID_PARAM] = "INVALID_PARAM", + [-LIBUSB_ERROR_ACCESS] = "ACCESS", + [-LIBUSB_ERROR_NO_DEVICE] = "NO_DEVICE", + [-LIBUSB_ERROR_NOT_FOUND] = "NOT_FOUND", + [-LIBUSB_ERROR_BUSY] = "BUSY", + [-LIBUSB_ERROR_TIMEOUT] = "TIMEOUT", + [-LIBUSB_ERROR_OVERFLOW] = "OVERFLOW", + [-LIBUSB_ERROR_PIPE] = "PIPE", + [-LIBUSB_ERROR_INTERRUPTED] = "INTERRUPTED", + [-LIBUSB_ERROR_NO_MEM] = "NO_MEM", + [-LIBUSB_ERROR_NOT_SUPPORTED] = "NOT_SUPPORTED", + [-LIBUSB_ERROR_OTHER] = "OTHER", +}; + +static libusb_context *ctx; +static uint32_t loglevel; + +static void usb_host_handle_fd(void *opaque) +{ + struct timeval tv = { 0, 0 }; + libusb_handle_events_timeout(ctx, &tv); +} + +static void usb_host_add_fd(int fd, short events, void *user_data) +{ + qemu_set_fd_handler(fd, + (events & POLLIN) ? usb_host_handle_fd : NULL, + (events & POLLOUT) ? usb_host_handle_fd : NULL, + ctx); +} + +static void usb_host_del_fd(int fd, void *user_data) +{ + qemu_set_fd_handler(fd, NULL, NULL, NULL); +} + +static int usb_host_init(void) +{ + const struct libusb_pollfd **poll; + int i, rc; + + if (ctx) { + return 0; + } + rc = libusb_init(&ctx); + if (rc != 0) { + return -1; + } + libusb_set_debug(ctx, loglevel); + + libusb_set_pollfd_notifiers(ctx, usb_host_add_fd, + usb_host_del_fd, + ctx); + poll = libusb_get_pollfds(ctx); + if (poll) { + for (i = 0; poll[i] != NULL; i++) { + usb_host_add_fd(poll[i]->fd, poll[i]->events, ctx); + } + } + free(poll); + return 0; +} + +static int usb_host_get_port(libusb_device *dev, char *port, size_t len) +{ +#if defined(LIBUSBX_API_VERSION) && (LIBUSBX_API_VERSION >= 0x010000ff) + /* have libusb_get_port_path() */ + uint8_t path[7]; + size_t off; + int rc, i; + + rc = libusb_get_port_path(ctx, dev, path, 7); + if (rc < 0) { + return 0; + } + off = snprintf(port, len, "%d", path[0]); + for (i = 1; i < rc; i++) { + off += snprintf(port+off, len-off, ".%d", path[i]); + } + return off; +#else + return snprintf(port, len, "FIXME"); +#endif +} + +static void usb_host_libusb_error(const char *func, int rc) +{ + const char *errname; + + if (rc >= 0) { + return; + } + + if (-rc < ARRAY_SIZE(err_names) && err_names[-rc]) { + errname = err_names[-rc]; + } else { + errname = "?"; + } + fprintf(stderr, "%s: %d [%s]\n", func, rc, errname); +} + +/* ------------------------------------------------------------------------ */ + +static bool usb_host_use_combining(USBEndpoint *ep) +{ + int type; + + if (!ep->pipeline) { + return false; + } + if (ep->pid != USB_TOKEN_IN) { + return false; + } + type = usb_ep_get_type(ep->dev, ep->pid, ep->nr); + if (type != USB_ENDPOINT_XFER_BULK) { + return false; + } + return true; +} + +/* ------------------------------------------------------------------------ */ + +static USBHostRequest *usb_host_req_alloc(USBHostDevice *s, USBPacket *p, + bool in, size_t bufsize) +{ + USBHostRequest *r = g_new0(USBHostRequest, 1); + + r->host = s; + r->p = p; + r->in = in; + r->xfer = libusb_alloc_transfer(0); + if (bufsize) { + r->buffer = g_malloc(bufsize); + } + QTAILQ_INSERT_TAIL(&s->requests, r, next); + return r; +} + +static void usb_host_req_free(USBHostRequest *r) +{ + if (r->host) { + QTAILQ_REMOVE(&r->host->requests, r, next); + } + libusb_free_transfer(r->xfer); + g_free(r->buffer); + g_free(r); +} + +static USBHostRequest *usb_host_req_find(USBHostDevice *s, USBPacket *p) +{ + USBHostRequest *r; + + QTAILQ_FOREACH(r, &s->requests, next) { + if (r->p == p) { + return r; + } + } + return NULL; +} + +static void usb_host_req_complete_ctrl(struct libusb_transfer *xfer) +{ + USBHostRequest *r = xfer->user_data; + USBHostDevice *s = r->host; + bool disconnect = (xfer->status == LIBUSB_TRANSFER_NO_DEVICE); + + if (r->p == NULL) { + goto out; /* request was canceled */ + } + + r->p->status = status_map[xfer->status]; + r->p->actual_length = xfer->actual_length; + if (r->in && xfer->actual_length) { + memcpy(r->cbuf, r->buffer + 8, xfer->actual_length); + } + trace_usb_host_req_complete(s->bus_num, s->addr, r->p, + r->p->status, r->p->actual_length); + usb_generic_async_ctrl_complete(USB_DEVICE(s), r->p); + +out: + usb_host_req_free(r); + if (disconnect) { + usb_host_nodev(s); + } +} + +static void usb_host_req_complete_data(struct libusb_transfer *xfer) +{ + USBHostRequest *r = xfer->user_data; + USBHostDevice *s = r->host; + bool disconnect = (xfer->status == LIBUSB_TRANSFER_NO_DEVICE); + + if (r->p == NULL) { + goto out; /* request was canceled */ + } + + r->p->status = status_map[xfer->status]; + if (r->in && xfer->actual_length) { + usb_packet_copy(r->p, r->buffer, xfer->actual_length); + } + trace_usb_host_req_complete(s->bus_num, s->addr, r->p, + r->p->status, r->p->actual_length); + if (usb_host_use_combining(r->p->ep)) { + usb_combined_input_packet_complete(USB_DEVICE(s), r->p); + } else { + usb_packet_complete(USB_DEVICE(s), r->p); + } + +out: + usb_host_req_free(r); + if (disconnect) { + usb_host_nodev(s); + } +} + +static void usb_host_req_abort(USBHostRequest *r) +{ + USBHostDevice *s = r->host; + bool inflight = (r->p && r->p->state == USB_RET_ASYNC); + + if (inflight) { + r->p->status = USB_RET_NODEV; + trace_usb_host_req_complete(s->bus_num, s->addr, r->p, + r->p->status, r->p->actual_length); + if (r->p->ep->nr == 0) { + usb_generic_async_ctrl_complete(USB_DEVICE(s), r->p); + } else { + usb_packet_complete(USB_DEVICE(s), r->p); + } + r->p = NULL; + } + + QTAILQ_REMOVE(&r->host->requests, r, next); + r->host = NULL; + + if (inflight) { + libusb_cancel_transfer(r->xfer); + } +} + +/* ------------------------------------------------------------------------ */ + +static void usb_host_req_complete_iso(struct libusb_transfer *transfer) +{ + USBHostIsoXfer *xfer = transfer->user_data; + + if (!xfer) { + /* USBHostIsoXfer released while inflight */ + g_free(transfer->buffer); + libusb_free_transfer(transfer); + return; + } + + QTAILQ_REMOVE(&xfer->ring->inflight, xfer, next); + if (QTAILQ_EMPTY(&xfer->ring->inflight)) { + USBHostDevice *s = xfer->ring->host; + trace_usb_host_iso_stop(s->bus_num, s->addr, xfer->ring->ep->nr); + } + if (xfer->ring->ep->pid == USB_TOKEN_IN) { + QTAILQ_INSERT_TAIL(&xfer->ring->copy, xfer, next); + } else { + QTAILQ_INSERT_TAIL(&xfer->ring->unused, xfer, next); + } +} + +static USBHostIsoRing *usb_host_iso_alloc(USBHostDevice *s, USBEndpoint *ep) +{ + USBHostIsoRing *ring = g_new0(USBHostIsoRing, 1); + USBHostIsoXfer *xfer; + /* FIXME: check interval (for now assume one xfer per frame) */ + int packets = s->iso_urb_frames; + int i; + + ring->host = s; + ring->ep = ep; + QTAILQ_INIT(&ring->unused); + QTAILQ_INIT(&ring->inflight); + QTAILQ_INIT(&ring->copy); + QTAILQ_INSERT_TAIL(&s->isorings, ring, next); + + for (i = 0; i < s->iso_urb_count; i++) { + xfer = g_new0(USBHostIsoXfer, 1); + xfer->ring = ring; + xfer->xfer = libusb_alloc_transfer(packets); + xfer->xfer->dev_handle = s->dh; + xfer->xfer->type = LIBUSB_TRANSFER_TYPE_ISOCHRONOUS; + + xfer->xfer->endpoint = ring->ep->nr; + if (ring->ep->pid == USB_TOKEN_IN) { + xfer->xfer->endpoint |= USB_DIR_IN; + } + xfer->xfer->callback = usb_host_req_complete_iso; + xfer->xfer->user_data = xfer; + + xfer->xfer->num_iso_packets = packets; + xfer->xfer->length = ring->ep->max_packet_size * packets; + xfer->xfer->buffer = g_malloc0(xfer->xfer->length); + + QTAILQ_INSERT_TAIL(&ring->unused, xfer, next); + } + + return ring; +} + +static USBHostIsoRing *usb_host_iso_find(USBHostDevice *s, USBEndpoint *ep) +{ + USBHostIsoRing *ring; + + QTAILQ_FOREACH(ring, &s->isorings, next) { + if (ring->ep == ep) { + return ring; + } + } + return NULL; +} + +static void usb_host_iso_reset_xfer(USBHostIsoXfer *xfer) +{ + libusb_set_iso_packet_lengths(xfer->xfer, + xfer->ring->ep->max_packet_size); + xfer->packet = 0; + xfer->copy_complete = false; +} + +static void usb_host_iso_free_xfer(USBHostIsoXfer *xfer, bool inflight) +{ + if (inflight) { + xfer->xfer->user_data = NULL; + } else { + g_free(xfer->xfer->buffer); + libusb_free_transfer(xfer->xfer); + } + g_free(xfer); +} + +static void usb_host_iso_free(USBHostIsoRing *ring) +{ + USBHostIsoXfer *xfer; + + while ((xfer = QTAILQ_FIRST(&ring->inflight)) != NULL) { + QTAILQ_REMOVE(&ring->inflight, xfer, next); + usb_host_iso_free_xfer(xfer, true); + } + while ((xfer = QTAILQ_FIRST(&ring->unused)) != NULL) { + QTAILQ_REMOVE(&ring->unused, xfer, next); + usb_host_iso_free_xfer(xfer, false); + } + while ((xfer = QTAILQ_FIRST(&ring->copy)) != NULL) { + QTAILQ_REMOVE(&ring->copy, xfer, next); + usb_host_iso_free_xfer(xfer, false); + } + + QTAILQ_REMOVE(&ring->host->isorings, ring, next); + g_free(ring); +} + +static void usb_host_iso_free_all(USBHostDevice *s) +{ + USBHostIsoRing *ring; + + while ((ring = QTAILQ_FIRST(&s->isorings)) != NULL) { + usb_host_iso_free(ring); + } +} + +static bool usb_host_iso_data_copy(USBHostIsoXfer *xfer, USBPacket *p) +{ + unsigned int psize; + unsigned char *buf; + + buf = libusb_get_iso_packet_buffer_simple(xfer->xfer, xfer->packet); + if (p->pid == USB_TOKEN_OUT) { + psize = p->iov.size; + if (psize > xfer->ring->ep->max_packet_size) { + /* should not happen (guest bug) */ + psize = xfer->ring->ep->max_packet_size; + } + xfer->xfer->iso_packet_desc[xfer->packet].length = psize; + } else { + psize = xfer->xfer->iso_packet_desc[xfer->packet].actual_length; + if (psize > p->iov.size) { + /* should not happen (guest bug) */ + psize = p->iov.size; + } + } + usb_packet_copy(p, buf, psize); + xfer->packet++; + xfer->copy_complete = (xfer->packet == xfer->xfer->num_iso_packets); + return xfer->copy_complete; +} + +static void usb_host_iso_data_in(USBHostDevice *s, USBPacket *p) +{ + USBHostIsoRing *ring; + USBHostIsoXfer *xfer; + bool disconnect = false; + int rc; + + ring = usb_host_iso_find(s, p->ep); + if (ring == NULL) { + ring = usb_host_iso_alloc(s, p->ep); + } + + /* copy data to guest */ + xfer = QTAILQ_FIRST(&ring->copy); + if (xfer != NULL) { + if (usb_host_iso_data_copy(xfer, p)) { + QTAILQ_REMOVE(&ring->copy, xfer, next); + QTAILQ_INSERT_TAIL(&ring->unused, xfer, next); + } + } + + /* submit empty bufs to host */ + while ((xfer = QTAILQ_FIRST(&ring->unused)) != NULL) { + QTAILQ_REMOVE(&ring->unused, xfer, next); + usb_host_iso_reset_xfer(xfer); + rc = libusb_submit_transfer(xfer->xfer); + if (rc != 0) { + usb_host_libusb_error("libusb_submit_transfer [iso]", rc); + QTAILQ_INSERT_TAIL(&ring->unused, xfer, next); + if (rc == LIBUSB_ERROR_NO_DEVICE) { + disconnect = true; + } + break; + } + if (QTAILQ_EMPTY(&ring->inflight)) { + trace_usb_host_iso_start(s->bus_num, s->addr, p->ep->nr); + } + QTAILQ_INSERT_TAIL(&ring->inflight, xfer, next); + } + + if (disconnect) { + usb_host_nodev(s); + } +} + +static void usb_host_iso_data_out(USBHostDevice *s, USBPacket *p) +{ + USBHostIsoRing *ring; + USBHostIsoXfer *xfer; + bool disconnect = false; + int rc, filled = 0; + + ring = usb_host_iso_find(s, p->ep); + if (ring == NULL) { + ring = usb_host_iso_alloc(s, p->ep); + } + + /* copy data from guest */ + xfer = QTAILQ_FIRST(&ring->copy); + while (xfer != NULL && xfer->copy_complete) { + filled++; + xfer = QTAILQ_NEXT(xfer, next); + } + if (xfer == NULL) { + xfer = QTAILQ_FIRST(&ring->unused); + if (xfer == NULL) { + trace_usb_host_iso_out_of_bufs(s->bus_num, s->addr, p->ep->nr); + return; + } + QTAILQ_REMOVE(&ring->unused, xfer, next); + usb_host_iso_reset_xfer(xfer); + QTAILQ_INSERT_TAIL(&ring->copy, xfer, next); + } + usb_host_iso_data_copy(xfer, p); + + if (QTAILQ_EMPTY(&ring->inflight)) { + /* wait until half of our buffers are filled + before kicking the iso out stream */ + if (filled*2 < s->iso_urb_count) { + return; + } + } + + /* submit filled bufs to host */ + while ((xfer = QTAILQ_FIRST(&ring->copy)) != NULL && + xfer->copy_complete) { + QTAILQ_REMOVE(&ring->copy, xfer, next); + rc = libusb_submit_transfer(xfer->xfer); + if (rc != 0) { + usb_host_libusb_error("libusb_submit_transfer [iso]", rc); + QTAILQ_INSERT_TAIL(&ring->unused, xfer, next); + if (rc == LIBUSB_ERROR_NO_DEVICE) { + disconnect = true; + } + break; + } + if (QTAILQ_EMPTY(&ring->inflight)) { + trace_usb_host_iso_start(s->bus_num, s->addr, p->ep->nr); + } + QTAILQ_INSERT_TAIL(&ring->inflight, xfer, next); + } + + if (disconnect) { + usb_host_nodev(s); + } +} + +/* ------------------------------------------------------------------------ */ + +static void usb_host_ep_update(USBHostDevice *s) +{ + static const char *tname[] = { + [USB_ENDPOINT_XFER_CONTROL] = "control", + [USB_ENDPOINT_XFER_ISOC] = "isoc", + [USB_ENDPOINT_XFER_BULK] = "bulk", + [USB_ENDPOINT_XFER_INT] = "int", + }; + USBDevice *udev = USB_DEVICE(s); + struct libusb_config_descriptor *conf; + const struct libusb_interface_descriptor *intf; + const struct libusb_endpoint_descriptor *endp; + uint8_t devep, type; + int pid, ep; + int rc, i, e; + + usb_ep_reset(udev); + rc = libusb_get_active_config_descriptor(s->dev, &conf); + if (rc != 0) { + return; + } + trace_usb_host_parse_config(s->bus_num, s->addr, + conf->bConfigurationValue, true); + + for (i = 0; i < conf->bNumInterfaces; i++) { + assert(udev->altsetting[i] < conf->interface[i].num_altsetting); + intf = &conf->interface[i].altsetting[udev->altsetting[i]]; + trace_usb_host_parse_interface(s->bus_num, s->addr, + intf->bInterfaceNumber, + intf->bAlternateSetting, true); + for (e = 0; e < intf->bNumEndpoints; e++) { + endp = &intf->endpoint[e]; + + devep = endp->bEndpointAddress; + pid = (devep & USB_DIR_IN) ? USB_TOKEN_IN : USB_TOKEN_OUT; + ep = devep & 0xf; + type = endp->bmAttributes & 0x3; + + if (ep == 0) { + trace_usb_host_parse_error(s->bus_num, s->addr, + "invalid endpoint address"); + return; + } + if (usb_ep_get_type(udev, pid, ep) != USB_ENDPOINT_XFER_INVALID) { + trace_usb_host_parse_error(s->bus_num, s->addr, + "duplicate endpoint address"); + return; + } + + trace_usb_host_parse_endpoint(s->bus_num, s->addr, ep, + (devep & USB_DIR_IN) ? "in" : "out", + tname[type], true); + usb_ep_set_max_packet_size(udev, pid, ep, + endp->wMaxPacketSize); + usb_ep_set_type(udev, pid, ep, type); + usb_ep_set_ifnum(udev, pid, ep, i); + usb_ep_set_halted(udev, pid, ep, 0); + } + } + + libusb_free_config_descriptor(conf); +} + +static int usb_host_open(USBHostDevice *s, libusb_device *dev) +{ + USBDevice *udev = USB_DEVICE(s); + int bus_num = libusb_get_bus_number(dev); + int addr = libusb_get_device_address(dev); + int rc; + + trace_usb_host_open_started(bus_num, addr); + + if (s->dh != NULL) { + goto fail; + } + rc = libusb_open(dev, &s->dh); + if (rc != 0) { + goto fail; + } + + libusb_get_device_descriptor(dev, &s->ddesc); + s->dev = dev; + s->bus_num = bus_num; + s->addr = addr; + usb_host_get_port(s->dev, s->port, sizeof(s->port)); + + usb_ep_init(udev); + usb_host_ep_update(s); + + udev->speed = speed_map[libusb_get_device_speed(dev)]; + udev->speedmask = (1 << udev->speed); +#if 0 + if (udev->speed == USB_SPEED_HIGH && usb_linux_full_speed_compat(dev)) { + udev->speedmask |= USB_SPEED_MASK_FULL; + } +#endif + + if (s->ddesc.iProduct) { + libusb_get_string_descriptor_ascii(s->dh, s->ddesc.iProduct, + (unsigned char *)udev->product_desc, + sizeof(udev->product_desc)); + } else { + snprintf(udev->product_desc, sizeof(udev->product_desc), + "host:%d.%d", bus_num, addr); + } + + rc = usb_device_attach(udev); + if (rc) { + goto fail; + } + + trace_usb_host_open_success(bus_num, addr); + return 0; + +fail: + trace_usb_host_open_failure(bus_num, addr); + if (s->dh != NULL) { + libusb_close(s->dh); + s->dh = NULL; + s->dev = NULL; + } + return -1; +} + +static void usb_host_abort_xfers(USBHostDevice *s) +{ + USBHostRequest *r, *rtmp; + + QTAILQ_FOREACH_SAFE(r, &s->requests, next, rtmp) { + usb_host_req_abort(r); + } +} + +static int usb_host_close(USBHostDevice *s) +{ + USBDevice *udev = USB_DEVICE(s); + + if (s->dh == NULL) { + return -1; + } + + trace_usb_host_close(s->bus_num, s->addr); + + usb_host_abort_xfers(s); + usb_host_iso_free_all(s); + + if (udev->attached) { + usb_device_detach(udev); + } + + usb_host_release_interfaces(s); + libusb_reset_device(s->dh); + usb_host_attach_kernel(s); + libusb_close(s->dh); + s->dh = NULL; + s->dev = NULL; + + usb_host_auto_check(NULL); + return 0; +} + +static void usb_host_nodev_bh(void *opaque) +{ + USBHostDevice *s = opaque; + usb_host_close(s); +} + +static void usb_host_nodev(USBHostDevice *s) +{ + if (!s->bh) { + s->bh = qemu_bh_new(usb_host_nodev_bh, s); + } + qemu_bh_schedule(s->bh); +} + +static void usb_host_exit_notifier(struct Notifier *n, void *data) +{ + USBHostDevice *s = container_of(n, USBHostDevice, exit); + + if (s->dh) { + usb_host_release_interfaces(s); + usb_host_attach_kernel(s); + } +} + +static int usb_host_initfn(USBDevice *udev) +{ + USBHostDevice *s = USB_HOST_DEVICE(udev); + + loglevel = s->loglevel; + udev->auto_attach = 0; + QTAILQ_INIT(&s->requests); + QTAILQ_INIT(&s->isorings); + + s->exit.notify = usb_host_exit_notifier; + qemu_add_exit_notifier(&s->exit); + + QTAILQ_INSERT_TAIL(&hostdevs, s, next); + add_boot_device_path(s->bootindex, &udev->qdev, NULL); + usb_host_auto_check(NULL); + return 0; +} + +static void usb_host_handle_destroy(USBDevice *udev) +{ + USBHostDevice *s = USB_HOST_DEVICE(udev); + + qemu_remove_exit_notifier(&s->exit); + QTAILQ_REMOVE(&hostdevs, s, next); + usb_host_close(s); +} + +static void usb_host_cancel_packet(USBDevice *udev, USBPacket *p) +{ + USBHostDevice *s = USB_HOST_DEVICE(udev); + USBHostRequest *r; + + if (p->combined) { + usb_combined_packet_cancel(udev, p); + return; + } + + trace_usb_host_req_canceled(s->bus_num, s->addr, p); + + r = usb_host_req_find(s, p); + if (r && r->p) { + r->p = NULL; /* mark as dead */ + libusb_cancel_transfer(r->xfer); + } +} + +static void usb_host_detach_kernel(USBHostDevice *s) +{ + struct libusb_config_descriptor *conf; + int rc, i; + + rc = libusb_get_active_config_descriptor(s->dev, &conf); + if (rc != 0) { + return; + } + for (i = 0; i < conf->bNumInterfaces; i++) { + rc = libusb_kernel_driver_active(s->dh, i); + usb_host_libusb_error("libusb_kernel_driver_active", rc); + if (rc != 1) { + continue; + } + trace_usb_host_detach_kernel(s->bus_num, s->addr, i); + rc = libusb_detach_kernel_driver(s->dh, i); + usb_host_libusb_error("libusb_detach_kernel_driver", rc); + s->ifs[i].detached = true; + } + libusb_free_config_descriptor(conf); +} + +static void usb_host_attach_kernel(USBHostDevice *s) +{ + struct libusb_config_descriptor *conf; + int rc, i; + + rc = libusb_get_active_config_descriptor(s->dev, &conf); + if (rc != 0) { + return; + } + for (i = 0; i < conf->bNumInterfaces; i++) { + if (!s->ifs[i].detached) { + continue; + } + trace_usb_host_attach_kernel(s->bus_num, s->addr, i); + libusb_attach_kernel_driver(s->dh, i); + s->ifs[i].detached = false; + } + libusb_free_config_descriptor(conf); +} + +static int usb_host_claim_interfaces(USBHostDevice *s, int configuration) +{ + USBDevice *udev = USB_DEVICE(s); + struct libusb_config_descriptor *conf; + int rc, i; + + for (i = 0; i < USB_MAX_INTERFACES; i++) { + udev->altsetting[i] = 0; + } + udev->ninterfaces = 0; + udev->configuration = 0; + + if (configuration == 0) { + /* address state - ignore */ + return USB_RET_SUCCESS; + } + + usb_host_detach_kernel(s); + + rc = libusb_get_active_config_descriptor(s->dev, &conf); + if (rc != 0) { + return USB_RET_STALL; + } + + for (i = 0; i < conf->bNumInterfaces; i++) { + trace_usb_host_claim_interface(s->bus_num, s->addr, configuration, i); + rc = libusb_claim_interface(s->dh, i); + usb_host_libusb_error("libusb_claim_interface", rc); + if (rc != 0) { + return USB_RET_STALL; + } + s->ifs[i].claimed = true; + } + + udev->ninterfaces = conf->bNumInterfaces; + udev->configuration = configuration; + + libusb_free_config_descriptor(conf); + return USB_RET_SUCCESS; +} + +static void usb_host_release_interfaces(USBHostDevice *s) +{ + USBDevice *udev = USB_DEVICE(s); + int i, rc; + + for (i = 0; i < udev->ninterfaces; i++) { + if (!s->ifs[i].claimed) { + continue; + } + trace_usb_host_release_interface(s->bus_num, s->addr, i); + rc = libusb_release_interface(s->dh, i); + usb_host_libusb_error("libusb_release_interface", rc); + s->ifs[i].claimed = false; + } +} + +static void usb_host_set_address(USBHostDevice *s, int addr) +{ + USBDevice *udev = USB_DEVICE(s); + + trace_usb_host_set_address(s->bus_num, s->addr, addr); + udev->addr = addr; +} + +static void usb_host_set_config(USBHostDevice *s, int config, USBPacket *p) +{ + int rc; + + trace_usb_host_set_config(s->bus_num, s->addr, config); + + usb_host_release_interfaces(s); + usb_host_detach_kernel(s); + rc = libusb_set_configuration(s->dh, config); + if (rc != 0) { + usb_host_libusb_error("libusb_set_configuration", rc); + p->status = USB_RET_STALL; + if (rc == LIBUSB_ERROR_NO_DEVICE) { + usb_host_nodev(s); + } + return; + } + p->status = usb_host_claim_interfaces(s, config); + if (p->status != USB_RET_SUCCESS) { + return; + } + usb_host_ep_update(s); +} + +static void usb_host_set_interface(USBHostDevice *s, int iface, int alt, + USBPacket *p) +{ + USBDevice *udev = USB_DEVICE(s); + int rc; + + trace_usb_host_set_interface(s->bus_num, s->addr, iface, alt); + + usb_host_iso_free_all(s); + + if (iface >= USB_MAX_INTERFACES) { + p->status = USB_RET_STALL; + return; + } + + rc = libusb_set_interface_alt_setting(s->dh, iface, alt); + if (rc != 0) { + usb_host_libusb_error("libusb_set_interface_alt_setting", rc); + p->status = USB_RET_STALL; + if (rc == LIBUSB_ERROR_NO_DEVICE) { + usb_host_nodev(s); + } + return; + } + + udev->altsetting[iface] = alt; + usb_host_ep_update(s); +} + +static void usb_host_handle_control(USBDevice *udev, USBPacket *p, + int request, int value, int index, + int length, uint8_t *data) +{ + USBHostDevice *s = USB_HOST_DEVICE(udev); + USBHostRequest *r; + int rc; + + trace_usb_host_req_control(s->bus_num, s->addr, p, request, value, index); + + if (s->dh == NULL) { + p->status = USB_RET_NODEV; + trace_usb_host_req_emulated(s->bus_num, s->addr, p, p->status); + return; + } + + switch (request) { + case DeviceOutRequest | USB_REQ_SET_ADDRESS: + usb_host_set_address(s, value); + trace_usb_host_req_emulated(s->bus_num, s->addr, p, p->status); + return; + + case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: + usb_host_set_config(s, value & 0xff, p); + trace_usb_host_req_emulated(s->bus_num, s->addr, p, p->status); + return; + + case InterfaceOutRequest | USB_REQ_SET_INTERFACE: + usb_host_set_interface(s, index, value, p); + trace_usb_host_req_emulated(s->bus_num, s->addr, p, p->status); + return; + + case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: + if (value == 0) { /* clear halt */ + int pid = (index & USB_DIR_IN) ? USB_TOKEN_IN : USB_TOKEN_OUT; + libusb_clear_halt(s->dh, index); + usb_ep_set_halted(udev, pid, index & 0x0f, 0); + trace_usb_host_req_emulated(s->bus_num, s->addr, p, p->status); + return; + } + } + + r = usb_host_req_alloc(s, p, (request >> 8) & USB_DIR_IN, length + 8); + r->cbuf = data; + r->clen = length; + memcpy(r->buffer, udev->setup_buf, 8); + if (!r->in) { + memcpy(r->buffer + 8, r->cbuf, r->clen); + } + + libusb_fill_control_transfer(r->xfer, s->dh, r->buffer, + usb_host_req_complete_ctrl, r, + CONTROL_TIMEOUT); + rc = libusb_submit_transfer(r->xfer); + if (rc != 0) { + p->status = USB_RET_NODEV; + trace_usb_host_req_complete(s->bus_num, s->addr, p, + p->status, p->actual_length); + if (rc == LIBUSB_ERROR_NO_DEVICE) { + usb_host_nodev(s); + } + return; + } + + p->status = USB_RET_ASYNC; +} + +static void usb_host_handle_data(USBDevice *udev, USBPacket *p) +{ + USBHostDevice *s = USB_HOST_DEVICE(udev); + USBHostRequest *r; + size_t size; + int ep, rc; + + if (usb_host_use_combining(p->ep) && p->state == USB_PACKET_SETUP) { + p->status = USB_RET_ADD_TO_QUEUE; + return; + } + + trace_usb_host_req_data(s->bus_num, s->addr, p, + p->pid == USB_TOKEN_IN, + p->ep->nr, p->iov.size); + + if (s->dh == NULL) { + p->status = USB_RET_NODEV; + trace_usb_host_req_emulated(s->bus_num, s->addr, p, p->status); + return; + } + if (p->ep->halted) { + p->status = USB_RET_STALL; + trace_usb_host_req_emulated(s->bus_num, s->addr, p, p->status); + return; + } + + switch (usb_ep_get_type(udev, p->pid, p->ep->nr)) { + case USB_ENDPOINT_XFER_BULK: + size = usb_packet_size(p); + r = usb_host_req_alloc(s, p, p->pid == USB_TOKEN_IN, size); + if (!r->in) { + 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); + break; + case USB_ENDPOINT_XFER_INT: + r = usb_host_req_alloc(s, p, p->pid == USB_TOKEN_IN, p->iov.size); + if (!r->in) { + usb_packet_copy(p, r->buffer, p->iov.size); + } + ep = p->ep->nr | (r->in ? USB_DIR_IN : 0); + libusb_fill_interrupt_transfer(r->xfer, s->dh, ep, + r->buffer, p->iov.size, + usb_host_req_complete_data, r, + INTR_TIMEOUT); + break; + case USB_ENDPOINT_XFER_ISOC: + if (p->pid == USB_TOKEN_IN) { + usb_host_iso_data_in(s, p); + } else { + usb_host_iso_data_out(s, p); + } + trace_usb_host_req_complete(s->bus_num, s->addr, p, + p->status, p->actual_length); + return; + default: + p->status = USB_RET_STALL; + trace_usb_host_req_complete(s->bus_num, s->addr, p, + p->status, p->actual_length); + return; + } + + rc = libusb_submit_transfer(r->xfer); + if (rc != 0) { + p->status = USB_RET_NODEV; + trace_usb_host_req_complete(s->bus_num, s->addr, p, + p->status, p->actual_length); + if (rc == LIBUSB_ERROR_NO_DEVICE) { + usb_host_nodev(s); + } + return; + } + + p->status = USB_RET_ASYNC; +} + +static void usb_host_flush_ep_queue(USBDevice *dev, USBEndpoint *ep) +{ + if (usb_host_use_combining(ep)) { + usb_ep_combine_input_packets(ep); + } +} + +static void usb_host_handle_reset(USBDevice *udev) +{ + USBHostDevice *s = USB_HOST_DEVICE(udev); + + trace_usb_host_reset(s->bus_num, s->addr); + + if (udev->configuration == 0) { + return; + } + usb_host_release_interfaces(s); + libusb_reset_device(s->dh); + usb_host_claim_interfaces(s, 0); + usb_host_ep_update(s); +} + +static const VMStateDescription vmstate_usb_host = { + .name = "usb-host", + .unmigratable = 1, + .fields = (VMStateField[]) { + VMSTATE_USB_DEVICE(parent_obj, USBHostDevice), + VMSTATE_END_OF_LIST() + } +}; + +static Property usb_host_dev_properties[] = { + DEFINE_PROP_UINT32("hostbus", USBHostDevice, match.bus_num, 0), + DEFINE_PROP_UINT32("hostaddr", USBHostDevice, match.addr, 0), + DEFINE_PROP_STRING("hostport", USBHostDevice, match.port), + DEFINE_PROP_HEX32("vendorid", USBHostDevice, match.vendor_id, 0), + DEFINE_PROP_HEX32("productid", USBHostDevice, match.product_id, 0), + DEFINE_PROP_UINT32("isobufs", USBHostDevice, iso_urb_count, 4), + DEFINE_PROP_UINT32("isobsize", USBHostDevice, iso_urb_frames, 32), + DEFINE_PROP_INT32("bootindex", USBHostDevice, bootindex, -1), + DEFINE_PROP_UINT32("loglevel", USBHostDevice, loglevel, + LIBUSB_LOG_LEVEL_WARNING), + DEFINE_PROP_BIT("pipeline", USBHostDevice, options, + USB_HOST_OPT_PIPELINE, true), + DEFINE_PROP_END_OF_LIST(), +}; + +static void usb_host_class_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->init = usb_host_initfn; + uc->product_desc = "USB Host Device"; + uc->cancel_packet = usb_host_cancel_packet; + uc->handle_data = usb_host_handle_data; + uc->handle_control = usb_host_handle_control; + uc->handle_reset = usb_host_handle_reset; + uc->handle_destroy = usb_host_handle_destroy; + uc->flush_ep_queue = usb_host_flush_ep_queue; + dc->vmsd = &vmstate_usb_host; + dc->props = usb_host_dev_properties; +} + +static TypeInfo usb_host_dev_info = { + .name = TYPE_USB_HOST_DEVICE, + .parent = TYPE_USB_DEVICE, + .instance_size = sizeof(USBHostDevice), + .class_init = usb_host_class_initfn, +}; + +static void usb_host_register_types(void) +{ + type_register_static(&usb_host_dev_info); +} + +type_init(usb_host_register_types) + +/* ------------------------------------------------------------------------ */ + +static QEMUTimer *usb_auto_timer; +static VMChangeStateEntry *usb_vmstate; + +static void usb_host_vm_state(void *unused, int running, RunState state) +{ + if (running) { + usb_host_auto_check(unused); + } +} + +static void usb_host_auto_check(void *unused) +{ + struct USBHostDevice *s; + struct USBAutoFilter *f; + libusb_device **devs; + struct libusb_device_descriptor ddesc; + int unconnected = 0; + int i, n; + + if (usb_host_init() != 0) { + return; + } + + if (runstate_is_running()) { + n = libusb_get_device_list(ctx, &devs); + for (i = 0; i < n; i++) { + if (libusb_get_device_descriptor(devs[i], &ddesc) != 0) { + continue; + } + if (ddesc.bDeviceClass == LIBUSB_CLASS_HUB) { + continue; + } + QTAILQ_FOREACH(s, &hostdevs, next) { + f = &s->match; + if (f->bus_num > 0 && + f->bus_num != libusb_get_bus_number(devs[i])) { + continue; + } + if (f->addr > 0 && + f->addr != libusb_get_device_address(devs[i])) { + continue; + } + if (f->port != NULL) { + char port[16] = "-"; + usb_host_get_port(devs[i], port, sizeof(port)); + if (strcmp(f->port, port) != 0) { + continue; + } + } + if (f->vendor_id > 0 && + f->vendor_id != ddesc.idVendor) { + continue; + } + if (f->product_id > 0 && + f->product_id != ddesc.idProduct) { + continue; + } + + /* We got a match */ + s->seen++; + if (s->errcount >= 3) { + continue; + } + if (s->dh != NULL) { + continue; + } + if (usb_host_open(s, devs[i]) < 0) { + s->errcount++; + continue; + } + break; + } + } + libusb_free_device_list(devs, 1); + + QTAILQ_FOREACH(s, &hostdevs, next) { + if (s->dh == NULL) { + unconnected++; + } + if (s->seen == 0) { + if (s->dh) { + usb_host_close(s); + } + s->errcount = 0; + } + s->seen = 0; + } + +#if 0 + if (unconnected == 0) { + /* nothing to watch */ + if (usb_auto_timer) { + qemu_del_timer(usb_auto_timer); + trace_usb_host_auto_scan_disabled(); + } + return; + } +#endif + } + + if (!usb_vmstate) { + usb_vmstate = qemu_add_vm_change_state_handler(usb_host_vm_state, NULL); + } + if (!usb_auto_timer) { + usb_auto_timer = qemu_new_timer_ms(rt_clock, usb_host_auto_check, NULL); + if (!usb_auto_timer) { + return; + } + trace_usb_host_auto_scan_enabled(); + } + qemu_mod_timer(usb_auto_timer, qemu_get_clock_ms(rt_clock) + 2000); +} + +void usb_host_info(Monitor *mon, const QDict *qdict) +{ + libusb_device **devs; + struct libusb_device_descriptor ddesc; + char port[16]; + int i, n; + + if (usb_host_init() != 0) { + return; + } + + n = libusb_get_device_list(ctx, &devs); + for (i = 0; i < n; i++) { + if (libusb_get_device_descriptor(devs[i], &ddesc) != 0) { + continue; + } + if (ddesc.bDeviceClass == LIBUSB_CLASS_HUB) { + continue; + } + usb_host_get_port(devs[i], port, sizeof(port)); + monitor_printf(mon, " Bus %d, Addr %d, Port %s, Speed %s Mb/s\n", + libusb_get_bus_number(devs[i]), + libusb_get_device_address(devs[i]), + port, + speed_name[libusb_get_device_speed(devs[i])]); + monitor_printf(mon, " Class %02x:", ddesc.bDeviceClass); + monitor_printf(mon, " USB device %04x:%04x", + ddesc.idVendor, ddesc.idProduct); + if (ddesc.iProduct) { + libusb_device_handle *handle; + if (libusb_open(devs[i], &handle) == 0) { + unsigned char name[64] = ""; + libusb_get_string_descriptor_ascii(handle, + ddesc.iProduct, + name, sizeof(name)); + libusb_close(handle); + monitor_printf(mon, ", %s", name); + } + } + monitor_printf(mon, "\n"); + } + libusb_free_device_list(devs, 1); +} diff --git a/hw/usb/host-linux.c b/hw/usb/host-linux.c index b67aeba..8994668 100644 --- a/hw/usb/host-linux.c +++ b/hw/usb/host-linux.c @@ -45,6 +45,12 @@ #include "hw/usb/desc.h" #include "hw/usb/host.h" +#ifdef CONFIG_USB_LIBUSB +# define DEVNAME "usb-host-linux" +#else +# define DEVNAME "usb-host" +#endif + /* We redefine it to avoid version problems */ struct usb_ctrltransfer { uint8_t bRequestType; @@ -1487,7 +1493,7 @@ static int usb_host_initfn(USBDevice *dev) } static const VMStateDescription vmstate_usb_host = { - .name = "usb-host", + .name = DEVNAME, .version_id = 1, .minimum_version_id = 1, .post_load = usb_host_post_load, @@ -1527,7 +1533,7 @@ static void usb_host_class_initfn(ObjectClass *klass, void *data) } static const TypeInfo usb_host_dev_info = { - .name = "usb-host", + .name = DEVNAME, .parent = TYPE_USB_DEVICE, .instance_size = sizeof(USBHostDevice), .class_init = usb_host_class_initfn, @@ -1767,6 +1773,8 @@ static void usb_host_auto_check(void *unused) qemu_mod_timer(usb_auto_timer, qemu_get_clock_ms(rt_clock) + 2000); } +#ifndef CONFIG_USB_LIBUSB + /**********************/ /* USB host device info */ @@ -1898,3 +1906,5 @@ void usb_host_info(Monitor *mon, const QDict *qdict) bus, addr, f->port ? f->port : "*", vid, pid); } } + +#endif diff --git a/trace-events b/trace-events index 968edb6..581d67a 100644 --- a/trace-events +++ b/trace-events @@ -362,6 +362,7 @@ usb_xhci_queue_event(uint32_t vector, uint32_t idx, const char *trb, const char usb_xhci_fetch_trb(uint64_t addr, const char *name, uint64_t param, uint32_t status, uint32_t control) "addr %016" PRIx64 ", %s, p %016" PRIx64 ", s %08x, c 0x%08x" usb_xhci_port_reset(uint32_t port) "port %d" usb_xhci_port_link(uint32_t port, uint32_t pls) "port %d, pls %d" +usb_xhci_port_notify(uint32_t port, uint32_t pls) "port %d, bits %x" usb_xhci_slot_enable(uint32_t slotid) "slotid %d" usb_xhci_slot_disable(uint32_t slotid) "slotid %d" usb_xhci_slot_address(uint32_t slotid) "slotid %d" @@ -424,11 +425,15 @@ usb_host_open_success(int bus, int addr) "dev %d:%d" usb_host_open_failure(int bus, int addr) "dev %d:%d" usb_host_disconnect(int bus, int addr) "dev %d:%d" usb_host_close(int bus, int addr) "dev %d:%d" +usb_host_attach_kernel(int bus, int addr, int interface) "dev %d:%d, if %d" +usb_host_detach_kernel(int bus, int addr, int interface) "dev %d:%d, if %d" usb_host_set_address(int bus, int addr, int config) "dev %d:%d, address %d" usb_host_set_config(int bus, int addr, int config) "dev %d:%d, config %d" usb_host_set_interface(int bus, int addr, int interface, int alt) "dev %d:%d, interface %d, alt %d" usb_host_claim_interfaces(int bus, int addr, int config, int nif) "dev %d:%d, config %d, nif %d" +usb_host_claim_interface(int bus, int addr, int config, int interface) "dev %d:%d, config %d, if %d" usb_host_release_interfaces(int bus, int addr) "dev %d:%d" +usb_host_release_interface(int bus, int addr, int interface) "dev %d:%d, if %d" usb_host_req_control(int bus, int addr, void *p, int req, int value, int index) "dev %d:%d, packet %p, req 0x%x, value %d, index %d" usb_host_req_data(int bus, int addr, void *p, int in, int ep, int size) "dev %d:%d, packet %p, in %d, ep %d, size %d" usb_host_req_complete(int bus, int addr, void *p, int status, int length) "dev %d:%d, packet %p, status %d, length %d" |