/* * 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_nodev; QEMUBH *bh_postld; 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) { 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; } 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 bool usb_host_full_speed_compat(USBHostDevice *s) { struct libusb_config_descriptor *conf; const struct libusb_interface_descriptor *intf; const struct libusb_endpoint_descriptor *endp; uint8_t type; int rc, c, i, a, e; for (c = 0;; c++) { rc = libusb_get_config_descriptor(s->dev, c, &conf); if (rc != 0) { break; } for (i = 0; i < conf->bNumInterfaces; i++) { for (a = 0; a < conf->interface[i].num_altsetting; a++) { intf = &conf->interface[i].altsetting[a]; for (e = 0; e < intf->bNumEndpoints; e++) { endp = &intf->endpoint[e]; type = endp->bmAttributes & 0x3; switch (type) { case 0x01: /* ISO */ return false; case 0x03: /* INTERRUPT */ if (endp->wMaxPacketSize > 64) { return false; } break; } } } } libusb_free_config_descriptor(conf); } return true; } 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 (udev->speed == USB_SPEED_HIGH && usb_host_full_speed_compat(s)) { udev->speedmask |= USB_SPEED_MASK_FULL; } 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_nodev) { s->bh_nodev = qemu_bh_new(usb_host_nodev_bh, s); } qemu_bh_schedule(s->bh_nodev); } 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); } /* * 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 * still present in the first place. Attemping to contine where we * left off is impossible. * * What we are going to to to here is emulate a surprise removal of * the usb device passed through, then kick host scan so the device * will get re-attached (and re-initialized by the guest) in case it * is still present. * * As the device removal will change the state of other devices (usb * host controller, most likely interrupt controller too) we have to * wait with it until *all* vmstate is loaded. Thus post_load just * kicks a bottom half which then does the actual work. */ static void usb_host_post_load_bh(void *opaque) { USBHostDevice *dev = opaque; USBDevice *udev = USB_DEVICE(dev); if (dev->dh != NULL) { usb_host_close(dev); } if (udev->attached) { usb_device_detach(udev); } usb_host_auto_check(NULL); } static int usb_host_post_load(void *opaque, int version_id) { USBHostDevice *dev = opaque; if (!dev->bh_postld) { dev->bh_postld = qemu_bh_new(usb_host_post_load_bh, dev); } qemu_bh_schedule(dev->bh_postld); return 0; } static const VMStateDescription vmstate_usb_host = { .name = "usb-host", .version_id = 1, .minimum_version_id = 1, .post_load = usb_host_post_load, .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); }