diff --git a/usr.sbin/bhyve/Makefile b/usr.sbin/bhyve/Makefile --- a/usr.sbin/bhyve/Makefile +++ b/usr.sbin/bhyve/Makefile @@ -67,6 +67,7 @@ uart_emul.c \ usb_emul.c \ usb_mouse.c \ + usb_passthru.c \ virtio.c \ vmexit.c \ vmgenc.c @@ -95,7 +96,7 @@ -I${.CURDIR}/../../contrib/lib9p \ -I${SRCTOP}/sys -LIBADD+= vmmapi md nv pthread z util sbuf cam 9p +LIBADD+= vmmapi md nv pthread z util sbuf cam 9p usb .if ${MK_BHYVE_SNAPSHOT} != "no" LIBADD+= ucl xo diff --git a/usr.sbin/bhyve/Makefile.depend b/usr.sbin/bhyve/Makefile.depend --- a/usr.sbin/bhyve/Makefile.depend +++ b/usr.sbin/bhyve/Makefile.depend @@ -16,6 +16,7 @@ lib/libsbuf \ lib/libthr \ lib/libutil \ + lib/libusb \ lib/libvmmapi \ lib/libz \ diff --git a/usr.sbin/bhyve/pci_xhci.c b/usr.sbin/bhyve/pci_xhci.c --- a/usr.sbin/bhyve/pci_xhci.c +++ b/usr.sbin/bhyve/pci_xhci.c @@ -406,7 +406,7 @@ * XHCI 4.19.3 USB2 RxDetect->Polling, * USB3 Polling->U0 */ - if (dev->hci.hci_usbver == 2) + if (dev->hci.hci_usbver <= 2) port->portsc |= XHCI_PS_PLS_SET(UPS_PORT_LS_POLL); else @@ -625,6 +625,7 @@ static void pci_xhci_assert_interrupt(struct pci_xhci_softc *sc) { + DPRINTF(("%s", __func__)); sc->rtsregs.intrreg.erdp |= XHCI_ERDP_LO_BUSY; sc->rtsregs.intrreg.iman |= XHCI_IMAN_INTR_PEND; @@ -1163,8 +1164,9 @@ uint32_t type; epid = XHCI_TRB_3_EP_GET(trb->dwTrb3); + type = XHCI_TRB_3_TYPE_GET(trb->dwTrb3); - DPRINTF(("pci_xhci: reset ep %u: slot %u", epid, slot)); + DPRINTF(("pci_xhci: reset ep %u: slot %u: type %u", epid, slot, type)); cmderr = pci_xhci_validate_slot(slot); if (cmderr != XHCI_TRB_ERROR_SUCCESS) @@ -1173,8 +1175,6 @@ dev = XHCI_SLOTDEV_PTR(sc, slot); assert(dev != NULL); - type = XHCI_TRB_3_TYPE_GET(trb->dwTrb3); - if (type == XHCI_TRB_TYPE_STOP_EP && (trb->dwTrb3 & XHCI_TRB_3_SUSP_EP_BIT) != 0) { /* XXX suspend endpoint for 10ms */ @@ -1187,8 +1187,6 @@ } devep = &dev->eps[epid]; - if (devep->ep_xfer != NULL) - USB_DATA_XFER_RESET(devep->ep_xfer); dev_ctx = dev->dev_ctx; assert(dev_ctx != NULL); @@ -1212,6 +1210,9 @@ goto done; } + if (devep->ep_xfer != NULL) + USB_DATA_XFER_RESET(devep->ep_xfer); + done: return (cmderr); } @@ -1622,10 +1623,12 @@ if (!xfer->data[i].processed) { xfer->head = i; + err = XHCI_TRB_ERROR_INVALID; break; } xfer->ndata--; + xfer->head = (xfer->head + 1) % USB_MAX_XFER_BLOCKS; edtla += xfer->data[i].bdone; trb->dwTrb3 = (trb->dwTrb3 & ~0x1) | (xfer->data[i].ccs); @@ -1633,6 +1636,12 @@ pci_xhci_update_ep_ring(sc, dev, devep, ep_ctx, xfer->data[i].streamid, xfer->data[i].trbnext, xfer->data[i].ccs); + if (xfer->data[i].blen > 0) + err = XHCI_TRB_ERROR_SHORT_PKT; + if (xfer->data[i].status == USB_NEXT_DATA) { + i = (i + 1) % USB_MAX_XFER_BLOCKS; + continue; + } /* Only interrupt if IOC or short packet */ if (!(trb->dwTrb3 & XHCI_TRB_3_IOC_BIT) && @@ -1742,13 +1751,11 @@ } else { err = pci_xhci_xfer_complete(sc, xfer, slot, epid, &do_intr); - if (err == XHCI_TRB_ERROR_SUCCESS && do_intr) { - pci_xhci_assert_interrupt(sc); + if (err == XHCI_TRB_ERROR_SUCCESS) { + if (do_intr) + pci_xhci_assert_interrupt(sc); + USB_DATA_XFER_RESET(xfer); } - - - /* XXX should not do it if error? */ - USB_DATA_XFER_RESET(xfer); } } @@ -1768,6 +1775,7 @@ struct xhci_trb *setup_trb; struct usb_data_xfer *xfer; struct usb_data_xfer_block *xfer_block; + struct usb_data_xfer_block *prev_xfer_block = NULL; uint64_t val; uint32_t trbflags; int do_intr, err; @@ -1810,6 +1818,8 @@ xfer_block = usb_data_xfer_append(xfer, NULL, 0, (void *)addr, ccs); xfer_block->processed = 1; + if (trb->dwTrb3 & XHCI_TRB_3_TC_BIT) + xfer_block->ccs = ccs; break; case XHCI_TRB_TYPE_SETUP_STAGE: @@ -1829,8 +1839,8 @@ sizeof(struct usb_device_request)); xfer_block = usb_data_xfer_append(xfer, NULL, 0, - (void *)addr, ccs); - xfer_block->processed = 1; + (void *)addr, ccs); + xfer_block->status = USB_NO_DATA; break; case XHCI_TRB_TYPE_NORMAL: @@ -1848,6 +1858,14 @@ (void *)(trbflags & XHCI_TRB_3_IDT_BIT ? &trb->qwTrb0 : XHCI_GADDR(sc, trb->qwTrb0)), trb->dwTrb2 & 0x1FFFF, (void *)addr, ccs); + if (xfer_block == NULL) { + err = USB_ERR_STALLED; + break; + } + xfer_block->status = trbflags & XHCI_TRB_3_CHAIN_BIT ? + USB_NEXT_DATA : + USB_LAST_DATA; + prev_xfer_block = xfer_block; break; case XHCI_TRB_TYPE_STATUS_STAGE: @@ -1867,6 +1885,11 @@ if ((epid > 1) && (trbflags & XHCI_TRB_3_IOC_BIT)) { xfer_block->processed = 1; } + if (prev_xfer_block != NULL && + prev_xfer_block->status == USB_NEXT_DATA) { + prev_xfer_block->status = USB_LAST_DATA; + prev_xfer_block = NULL; + } break; default: @@ -1886,16 +1909,21 @@ xfer_block->streamid = streamid; } + if (trbflags & XHCI_TRB_3_BEI_BIT) + continue; + if (!setup_trb && !(trbflags & XHCI_TRB_3_CHAIN_BIT) && XHCI_TRB_3_TYPE_GET(trbflags) != XHCI_TRB_TYPE_LINK) { break; } + if (XHCI_TRB_3_TYPE_GET(trbflags) == XHCI_TRB_TYPE_EVENT_DATA) + continue; + /* handle current batch that requires interrupt on complete */ if (trbflags & XHCI_TRB_3_IOC_BIT) { DPRINTF(("pci_xhci: trb IOC bit set")); - if (epid == 1) - do_retry = 1; + do_retry = 1; break; } } @@ -2598,6 +2626,7 @@ port = XHCI_PORTREG_PTR(sc, portn); dev = XHCI_DEVINST_PTR(sc, portn); if (dev) { + dev->dev_ue->ue_reset(dev->dev_sc); port->portsc &= ~(XHCI_PS_PLS_MASK | XHCI_PS_PR | XHCI_PS_PRC); port->portsc |= XHCI_PS_PED | XHCI_PS_SPEED_SET(dev->hci.hci_speed); @@ -2632,7 +2661,7 @@ port->portsc = XHCI_PS_CCS | /* connected */ XHCI_PS_PP; /* port power */ - if (dev->hci.hci_usbver == 2) { + if (dev->hci.hci_usbver <= 2) { port->portsc |= XHCI_PS_PLS_SET(UPS_PORT_LS_POLL) | XHCI_PS_SPEED_SET(dev->hci.hci_speed); } else { @@ -2648,6 +2677,34 @@ } } +static void +pci_xhci_deinit_port(struct pci_xhci_softc *sc, int portn) +{ + struct pci_xhci_portregs *port; + struct pci_xhci_dev_emu *dev; + + port = XHCI_PORTREG_PTR(sc, portn); + dev = XHCI_DEVINST_PTR(sc, portn); + if (dev) { + port->portsc &= ~(XHCI_PS_CCS | /* connected */ + XHCI_PS_PP); /* port power */ + + if (dev->hci.hci_usbver <= 2) { + port->portsc &= ~(XHCI_PS_PLS_SET(UPS_PORT_LS_POLL) | + XHCI_PS_SPEED_SET(dev->hci.hci_speed)); + } else { + port->portsc &= ~(XHCI_PS_PLS_SET(UPS_PORT_LS_U0) | + XHCI_PS_PED | /* enabled */ + XHCI_PS_SPEED_SET(dev->hci.hci_speed)); + } + + DPRINTF(("Deinit port %d 0x%x", portn, port->portsc)); + } else { + port->portsc = XHCI_PS_PLS_SET(UPS_PORT_LS_RX_DET) | XHCI_PS_PP; + DPRINTF(("Deinit empty port %d 0x%x", portn, port->portsc)); + } +} + static int pci_xhci_dev_intr(struct usb_hci *hci, int epctx) { @@ -2706,7 +2763,7 @@ DPRINTF(("xhci device interrupt on endpoint %d", epid)); - pci_xhci_device_doorbell(sc, hci->hci_port, epid, 0); + pci_xhci_device_doorbell(sc, hci->hci_slot, epid, 0); done: return (error); @@ -2716,7 +2773,34 @@ pci_xhci_dev_event(struct usb_hci *hci, enum hci_usbev evid __unused, void *param __unused) { + struct xhci_trb evtrb; + struct pci_xhci_dev_emu *dev = hci->hci_sc; + struct pci_xhci_softc *xsc = dev->xsc; + struct pci_xhci_portregs *port; + + port = XHCI_PORTREG_PTR(xsc, hci->hci_port); DPRINTF(("xhci device event port %d", hci->hci_port)); + + switch (evid) { + case USBDEV_ATTACH: + pci_xhci_init_port(xsc, hci->hci_port); + port->portsc |= XHCI_PS_CSC; + pci_xhci_set_evtrb(&evtrb, hci->hci_port, + XHCI_TRB_ERROR_SUCCESS, XHCI_TRB_EVENT_PORT_STS_CHANGE); + return (pci_xhci_insert_event(xsc, &evtrb, 1) != + XHCI_TRB_ERROR_SUCCESS); + break; + case USBDEV_REMOVE: + pci_xhci_deinit_port(xsc, hci->hci_port); + port->portsc |= XHCI_PS_CSC; + pci_xhci_set_evtrb(&evtrb, hci->hci_port, + XHCI_TRB_ERROR_SUCCESS, XHCI_TRB_EVENT_PORT_STS_CHANGE); + return (pci_xhci_insert_event(xsc, &evtrb, 1) != + XHCI_TRB_ERROR_SUCCESS); + break; + default: + break; + } return (0); } @@ -2738,7 +2822,7 @@ { char node_name[16]; nvlist_t *slots_nvl, *slot_nvl; - char *cp, *opt, *str, *tofree; + char *cp, *opt, *str, *tofree, *subopt; int slot; if (opts == NULL) @@ -2758,7 +2842,16 @@ snprintf(node_name, sizeof(node_name), "%d", slot); slot++; slot_nvl = create_relative_config_node(slots_nvl, node_name); - set_config_value_node(slot_nvl, "device", opt); + subopt = strsep(&opt, "."); + set_config_value_node(slot_nvl, "device", subopt); + subopt = strsep(&opt, "."); + if (subopt == NULL) + continue; + set_config_value_node(slot_nvl, "param1", subopt); + subopt = strsep(&opt, "."); + if (subopt == NULL) + continue; + set_config_value_node(slot_nvl, "param2", subopt); /* * NB: Given that we split on commas above, the legacy @@ -2846,9 +2939,10 @@ dev->hci.hci_intr = pci_xhci_dev_intr; dev->hci.hci_event = pci_xhci_dev_event; dev->hci.hci_speed = USB_SPEED_MAX; + dev->hci.hci_slot = slot; dev->hci.hci_usbver = -1; - devsc = ue->ue_probe(&dev->hci, nvl); + devsc = ue->ue_probe(&dev->hci, slot_nvl); if (devsc == NULL) { free(dev); goto bad; @@ -2858,7 +2952,7 @@ if (dev->hci.hci_usbver == -1) dev->hci.hci_usbver = ue->ue_usbver; - if (dev->hci.hci_usbver == 2) { + if (dev->hci.hci_usbver <= 2) { if (usb2_port == sc->usb2_port_start + XHCI_MAX_DEVS / 2) { WPRINTF(("pci_xhci max number of USB 2 devices " @@ -3258,6 +3352,7 @@ SNAPSHOT_VAR_OR_LEAVE(dev->hci.hci_address, meta, ret, done); SNAPSHOT_VAR_OR_LEAVE(dev->hci.hci_port, meta, ret, done); SNAPSHOT_VAR_OR_LEAVE(dev->hci.hci_speed, meta, ret, done); + SNAPSHOT_VAR_OR_LEAVE(dev->hci.hci_slot, meta, ret, done); SNAPSHOT_VAR_OR_LEAVE(dev->hci.hci_usbver, meta, ret, done); } diff --git a/usr.sbin/bhyve/usb_emul.h b/usr.sbin/bhyve/usb_emul.h --- a/usr.sbin/bhyve/usb_emul.h +++ b/usr.sbin/bhyve/usb_emul.h @@ -52,7 +52,7 @@ int ue_usbspeed; /* usb device speed */ /* instance creation */ - void *(*ue_probe)(struct usb_hci *hci, nvlist_t *nvl); + void *(*ue_probe)(struct usb_hci *hci, const nvlist_t *nvl); int (*ue_init)(void *sc); /* handlers */ @@ -77,6 +77,15 @@ USBDEV_REMOVE, }; +/* + * USB data status for TRB + */ +enum usb_xfer_data_status { + USB_NO_DATA, + USB_NEXT_DATA, + USB_LAST_DATA, +}; + /* usb controller, ie xhci, ehci */ struct usb_hci { int (*hci_intr)(struct usb_hci *hci, int epctx); @@ -88,7 +97,8 @@ int hci_address; int hci_port; int hci_speed; - int hci_usbver; + int hci_slot; + int hci_usbver; /* Can be modified by the backend in the probe step */ }; /* @@ -106,6 +116,7 @@ int ccs; uint32_t streamid; uint64_t trbnext; /* next TRB guest address */ + enum usb_xfer_data_status status; }; struct usb_data_xfer { @@ -114,6 +125,7 @@ int ndata; /* # of data items */ int head; int tail; + void *tr_softc; pthread_mutex_t mtx; }; diff --git a/usr.sbin/bhyve/usb_emul.c b/usr.sbin/bhyve/usb_emul.c --- a/usr.sbin/bhyve/usb_emul.c +++ b/usr.sbin/bhyve/usb_emul.c @@ -69,6 +69,7 @@ xb->ccs = ccs; xb->processed = 0; xb->bdone = 0; + xb->status = USB_NO_DATA; xfer->ndata++; xfer->tail = (xfer->tail + 1) % USB_MAX_XFER_BLOCKS; return (xb); diff --git a/usr.sbin/bhyve/usb_mouse.c b/usr.sbin/bhyve/usb_mouse.c --- a/usr.sbin/bhyve/usb_mouse.c +++ b/usr.sbin/bhyve/usb_mouse.c @@ -295,7 +295,7 @@ } static void * -umouse_probe(struct usb_hci *hci, nvlist_t *nvl __unused) +umouse_probe(struct usb_hci *hci, const nvlist_t *nvl __unused) { struct umouse_softc *sc; diff --git a/usr.sbin/bhyve/usb_passthru.c b/usr.sbin/bhyve/usb_passthru.c new file mode 100644 --- /dev/null +++ b/usr.sbin/bhyve/usb_passthru.c @@ -0,0 +1,854 @@ +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "debug.h" + +#include "config.h" +#include "usb_emul.h" + +static int upassthru_debug = 0; +static bool libusb_is_init = false; +#define DPRINTF(params) if (upassthru_debug) PRINTLN params +#define WPRINTF(params) PRINTLN params + +#define OUT 0 +#define IN 1 + +struct upassthru_libusb_xfer; + +struct upassthru_softc { + struct usb_hci *hci; /* hci structure for issue xhci interrupt */ + pthread_mutex_t mtx; /* mutex for locking */ + unsigned long vid; /* product vid */ + unsigned long pid; /* product pid */ + struct libusb_device_handle *handle; /* libusb handler */ + libusb_hotplug_callback_handle cb; /* callback handler */ + int *active_altsettings; /* alternative settings for record total number + of interfaces */ + int num_interfaces; /* number of interfaces */ + uint8_t *endpoint_types[2]; /* type of endpoints[in, out] */ + uint8_t ep_cnts[2]; /* number of endpoints for each type[in,out] */ + LIST_ENTRY(upassthru_softc) next; +}; + +struct upassthru_libusb_xfer { + struct libusb_transfer *lusb_xfer; + struct usb_data_xfer *usb_xfer; + struct upassthru_softc *sc; + uint8_t *buffer; + bool in; + int epid; + int size; +}; + +static LIST_HEAD(, upassthru_softc) devices; + +static int upassthru_remove(void *scarg); +static pthread_t libusb_thread = NULL; +static int event_thread_exit = false; + +static int +libusb_error_to_usb_error(enum libusb_error error) +{ + switch (error) { + case LIBUSB_SUCCESS: + return (USB_ERR_NORMAL_COMPLETION); + case LIBUSB_ERROR_IO: + return (USB_ERR_IOERROR); + case LIBUSB_ERROR_INVALID_PARAM: + return (USB_ERR_INVAL); + case LIBUSB_ERROR_ACCESS: + return (USB_ERR_INVAL); + case LIBUSB_ERROR_NO_DEVICE: + return (USB_ERR_NOT_CONFIGURED); + case LIBUSB_ERROR_NOT_FOUND: + return (USB_ERR_INVAL); + case LIBUSB_ERROR_BUSY: + return (USB_ERR_IN_USE); + case LIBUSB_ERROR_TIMEOUT: + return (USB_ERR_TIMEOUT); + case LIBUSB_ERROR_OVERFLOW: + return (USB_ERR_BAD_BUFSIZE); + case LIBUSB_ERROR_PIPE: + return (USB_ERR_NO_PIPE); + case LIBUSB_ERROR_INTERRUPTED: + return (USB_ERR_INTERRUPTED); + case LIBUSB_ERROR_NO_MEM: + return (USB_ERR_NOMEM); + case LIBUSB_ERROR_NOT_SUPPORTED: + return (USB_ERR_INVAL); + case LIBUSB_ERROR_OTHER: + return (USB_ERR_INVAL); + } +} + +static void * +libusb_pull_thread(void *arg __unused) +{ + struct timeval tv; + int err; + + DPRINTF(("start libusb pullthread")); + while (!event_thread_exit) { + tv.tv_sec = 1; + tv.tv_usec = 0; + err = libusb_handle_events_timeout(NULL, &tv); + if (err && err != LIBUSB_ERROR_TIMEOUT) + break; + } + + DPRINTF(("stop libusb pullthread")); + return (NULL); +} + +static void +upassthru_calculate_xfer_ptr(struct usb_data_xfer *xfer, int *head, int *tail, + int *size) +{ + int cur, i; + int first = -1, length = 0; + struct usb_data_xfer_block *blk; + + for (i = 0, cur = xfer->head; i < xfer->ndata; + ++i, cur = (cur + 1) % USB_MAX_XFER_BLOCKS) { + blk = &xfer->data[cur]; + if (blk->processed) + continue; + switch (blk->status) { + case USB_NEXT_DATA: + case USB_LAST_DATA: + first = first == -1 ? cur : first; + length += blk->blen; + break; + case USB_NO_DATA: + blk->processed = 1; + continue; + } + + if (blk->status == USB_LAST_DATA) + break; + } + *head = first; + *tail = xfer->tail; + *size = length; +} + +static void +upassthru_data_calculate_num_isos(struct usb_data_xfer *xfer, int *head, + int *nframe, int *len) +{ + int i, cur, first = -1, nf = 0, length = 0; + + for (i = 0, cur = xfer->head; i < xfer->ndata; + i = (i + 1) % USB_MAX_XFER_BLOCKS) { + if (xfer->data[i].processed) + continue; + switch (xfer->data[i].status) { + case USB_LAST_DATA: + ++nf; + if (first == -1) + first = i; + /* Fallthrough */ + case USB_NEXT_DATA: + length += xfer->data[i].blen; + break; + default: + break; + } + } + + *nframe = nf; + *head = first; + *len = length; +} + +static struct upassthru_libusb_xfer * +upassthru_xfer_alloc(struct upassthru_softc *sc, int in, + struct usb_data_xfer *usb_xfer, int size, int ep, int iso) +{ + struct upassthru_libusb_xfer *xfer = calloc(1, + sizeof(struct upassthru_libusb_xfer)); + xfer->lusb_xfer = libusb_alloc_transfer(iso); + xfer->usb_xfer = usb_xfer; + xfer->buffer = calloc(1, size); + xfer->size = size; + xfer->in = in; + xfer->sc = sc; + xfer->epid = ep; + return (xfer); +} + +static void +upassthru_xfer_free(struct upassthru_libusb_xfer *xfer) +{ + free(xfer); +} + +static int +upassthru_parse_ep_types(struct upassthru_softc *sc) +{ + struct libusb_config_descriptor *desc; + struct libusb_device *dev; + struct libusb_device_handle *handle; + struct libusb_interface_descriptor *inf_desc; + struct libusb_endpoint_descriptor *ep_desc; + int res, intf, ep, addr, dir; + + handle = sc->handle; + dev = libusb_get_device(handle); + res = libusb_get_active_config_descriptor(dev, &desc); + if (res != LIBUSB_SUCCESS) + return (libusb_error_to_usb_error(res)); + assert(desc->bNumInterfaces == sc->num_interfaces); + + free(sc->endpoint_types[0]); + free(sc->endpoint_types[1]); + sc->ep_cnts[0] = sc->ep_cnts[1] = 0; + for (intf = 0; intf < desc->bNumInterfaces; ++intf) { + inf_desc = &desc->interface[intf] + .altsetting[sc->active_altsettings[intf]]; + for (ep = 0; ep < inf_desc->bNumEndpoints; ++ep) { + addr = inf_desc->endpoint[ep].bEndpointAddress; + dir = !!(addr & LIBUSB_ENDPOINT_DIR_MASK); + addr = addr & LIBUSB_ENDPOINT_ADDRESS_MASK; + sc->ep_cnts[dir] = (addr > sc->ep_cnts[dir] ? + addr : + sc->ep_cnts[dir]); + } + } + + sc->endpoint_types[OUT] = calloc(sc->ep_cnts[OUT] + 1, sizeof(uint8_t)); + sc->endpoint_types[IN] = calloc(sc->ep_cnts[IN] + 1, sizeof(uint8_t)); + for (intf = 0; intf < desc->bNumInterfaces; ++intf) { + inf_desc = &desc->interface[intf] + .altsetting[sc->active_altsettings[intf]]; + for (ep = 0; ep < inf_desc->bNumEndpoints; ++ep) { + ep_desc = &inf_desc->endpoint[ep]; + addr = ep_desc->bEndpointAddress; + sc->endpoint_types[!!(addr & LIBUSB_ENDPOINT_DIR_MASK)] + [addr & + LIBUSB_ENDPOINT_ADDRESS_MASK] = + ep_desc->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK; + } + } + + libusb_free_config_descriptor(desc); + return (0); +} + +static int +upassthru_guest_attach_device(struct upassthru_softc *sc) +{ + struct libusb_config_descriptor *desc; + struct libusb_device *dev; + struct libusb_device_handle *handle; + int intf, res; + + handle = sc->handle; + dev = libusb_get_device(handle); + res = libusb_get_active_config_descriptor(dev, &desc); + if (res != LIBUSB_SUCCESS) + return (libusb_error_to_usb_error(res)); + + free(sc->active_altsettings); + sc->num_interfaces = desc->bNumInterfaces; + sc->active_altsettings = calloc(sc->num_interfaces, sizeof(int)); + + for (intf = 0; intf < desc->bNumInterfaces; ++intf) { + res = libusb_detach_kernel_driver(handle, intf); + if (res != LIBUSB_SUCCESS) + break; + res = libusb_claim_interface(handle, intf); + if (res != LIBUSB_SUCCESS) + break; + } + + libusb_free_config_descriptor(desc); + upassthru_parse_ep_types(sc); + return (res); +} + +static int +upassthru_guest_detach_device_on_host(struct upassthru_softc *sc) +{ + struct libusb_config_descriptor *desc; + struct libusb_device *dev; + struct libusb_device_handle *handle; + int intf, res; + + handle = sc->handle; + if (handle == NULL) + return (libusb_error_to_usb_error(LIBUSB_SUCCESS)); + dev = libusb_get_device(handle); + + res = libusb_get_active_config_descriptor(dev, &desc); + if (res != LIBUSB_SUCCESS) + return (libusb_error_to_usb_error(res)); + + for (intf = 0; intf < desc->bNumInterfaces; ++intf) { + res = libusb_release_interface(handle, intf); + if (res != LIBUSB_SUCCESS) + break; + res = libusb_attach_kernel_driver(handle, intf); + if (res != LIBUSB_SUCCESS) + break; + } + + libusb_free_config_descriptor(desc); + free(sc->active_altsettings); + free(sc->endpoint_types[0]); + free(sc->endpoint_types[1]); + sc->endpoint_types[0] = NULL; + sc->endpoint_types[1] = NULL; + sc->active_altsettings = NULL; + + return (libusb_error_to_usb_error(res)); +} + +static int +upassthru_guest_detach_device(struct upassthru_softc *sc __unused) +{ + return (0); +} + +static int +upassthru_hotplug_callback(struct libusb_context *ctx __unused, + struct libusb_device *dev, libusb_hotplug_event event, void *user_data) +{ + struct upassthru_softc *sc = user_data; + int err = 0; + + DPRINTF(("%s: enter hotplug handler event: %d", __func__, event)); + + pthread_mutex_lock(&sc->mtx); + + if (event == LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT) { + assert(sc->hci->hci_event(sc->hci, USBDEV_REMOVE, sc) == 0); + libusb_close(sc->handle); + sc->handle = NULL; + } else if (event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED) { + if (sc->handle != NULL) + goto done; + err = libusb_open(dev, &sc->handle); + if (err) + goto done; + upassthru_guest_attach_device(sc); + assert(sc->hci->hci_event(sc->hci, USBDEV_ATTACH, sc) == 0); + } + +done: + pthread_mutex_unlock(&sc->mtx); + + return (err); +} + +static void +upassthru_data_callback(struct libusb_transfer *lusb_xfer) +{ + struct upassthru_libusb_xfer *up_xfer = lusb_xfer->user_data; + struct usb_data_xfer *uxfer = up_xfer->usb_xfer; + struct upassthru_softc *sc = up_xfer->sc; + struct usb_hci *hci = sc->hci; + int act_len = lusb_xfer->actual_length; + int head, tail, size, cur_len, cur, offset, nframe; + char *buffer; + int cur_iso = 0, i; + + USB_DATA_XFER_LOCK(uxfer); + if (lusb_xfer->type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) + upassthru_data_calculate_num_isos(uxfer, &head, &nframe, &size); + else + upassthru_calculate_xfer_ptr(uxfer, &head, &tail, &size); + + if (lusb_xfer->status == LIBUSB_TRANSFER_STALL || + lusb_xfer->status == LIBUSB_TRANSFER_NO_DEVICE || + lusb_xfer->status == LIBUSB_TRANSFER_CANCELLED) { + USB_DATA_SET_ERRCODE(&uxfer->data[head], USB_STALL); + goto done; + } + + DPRINTF(("%s: act_len: %d blen:%d in:%d epid:%d status: %d", __func__, + act_len, size, up_xfer->in, up_xfer->epid, lusb_xfer->status)); + + pthread_mutex_lock(&up_xfer->sc->mtx); + + if (up_xfer->in) { + if (lusb_xfer->type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) + act_len = lusb_xfer->iso_packet_desc[0].actual_length; + + if (act_len > size) + act_len = size; + + for (cur = head, offset = 0, i = 0; i < uxfer->ndata; + cur = (cur + 1) % USB_MAX_XFER_BLOCKS, ++i) { + cur_len = uxfer->data[cur].blen; + if (cur_len > act_len) { + cur_len = act_len; + USB_DATA_SET_ERRCODE(&uxfer->data[cur], + USB_SHORT); + } + if (lusb_xfer->type != LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) + buffer = &lusb_xfer->buffer[offset]; + else + buffer = libusb_get_iso_packet_buffer_simple( + lusb_xfer, cur_iso) + + offset; + if (cur_len) + memcpy(uxfer->data[cur].buf, buffer, cur_len); + act_len -= cur_len; + offset += cur_len; + if (act_len <= 0 && + lusb_xfer->type == + LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) { + ++cur_iso; + act_len = lusb_xfer->iso_packet_desc[cur_iso] + .actual_length; + offset = 0; + } + uxfer->data[cur].blen -= cur_len; + uxfer->data[cur].bdone = cur_len; + uxfer->data[cur].processed = 1; + } + } else { + for (cur = head; uxfer->data[cur].buf; + cur = (cur + 1) % USB_MAX_XFER_BLOCKS) + uxfer->data[cur].processed = 1; + + USB_DATA_SET_ERRCODE(&uxfer->data[head], + lusb_xfer->status == LIBUSB_TRANSFER_COMPLETED ? + USB_ERR_NORMAL_COMPLETION : + USB_ERR_IOERROR); + } + pthread_mutex_unlock(&sc->mtx); + +done: + uxfer->tr_softc = NULL; + USB_DATA_XFER_UNLOCK(up_xfer->usb_xfer); + hci->hci_intr(hci, up_xfer->epid); + upassthru_xfer_free(up_xfer); +} + +static void +upassthru_exit(void) +{ + struct upassthru_softc *sc, *tmp; + + LIST_FOREACH_SAFE(sc, &devices, next, tmp) { + LIST_REMOVE(sc, next); + upassthru_remove(sc); + } + + event_thread_exit = true; +} + +static void * +upassthru_probe(struct usb_hci *hci, const nvlist_t *nvl) +{ + struct upassthru_softc *sc; + struct libusb_device_descriptor dev_desc; + struct libusb_device *dev; + struct libusb_init_option opts[] = { + { .option = LIBUSB_OPTION_CAPSICUMIZE } + }; + const char *param; + int error; + + if (!libusb_is_init) { + libusb_is_init = true; + error = libusb_init_context(NULL, opts, + sizeof(opts) / sizeof(struct libusb_init_option)); + if (error) { + EPRINTLN("failed to capsicumize libusb"); + return (NULL); + } + LIST_INIT(&devices); + } + sc = calloc(1, sizeof(struct upassthru_softc)); + + param = get_config_value_node(nvl, "param1"); + if (param == NULL) { + free(sc); + return (NULL); + } + sc->vid = strtoul(param, NULL, 16); + param = get_config_value_node(nvl, "param2"); + if (param == NULL) { + free(sc); + return (NULL); + } + sc->pid = strtoul(param, NULL, 16); + + sc->hci = hci; + sc->handle = libusb_open_device_with_vid_pid(NULL, sc->vid, sc->pid); + if (sc->handle == NULL) { + EPRINTLN("failed to open usb device with %0lx %0lx", sc->vid, + sc->pid); + free(sc); + return (NULL); + } + dev = libusb_get_device(sc->handle); + if (libusb_get_device_descriptor(dev, &dev_desc) != LIBUSB_SUCCESS) { + EPRINTLN("failed to get usb device descriptor with %0lx %0lx", + sc->vid, sc->pid); + free(sc); + return (NULL); + } + if (dev_desc.bcdUSB >= 0x0300) { + hci->hci_usbver = 3; + } else { + hci->hci_usbver = 2; + } + LIST_INSERT_HEAD(&devices, sc, next); + + return (sc); +} + +static int +upassthru_init(void *scarg) +{ + struct upassthru_softc *sc = (struct upassthru_softc *)scarg; + struct usb_hci *hci; + enum libusb_speed speed; + int error; + + hci = sc->hci; + pthread_mutex_init(&sc->mtx, NULL); + + speed = libusb_get_device_speed(libusb_get_device(sc->handle)); + switch (speed) { + case LIBUSB_SPEED_LOW: + hci->hci_speed = 2; + break; + case LIBUSB_SPEED_FULL: + hci->hci_speed = 1; + break; + case LIBUSB_SPEED_HIGH: + hci->hci_speed = 3; + break; + default: + break; + } + + if ((error = upassthru_guest_attach_device(sc)) != LIBUSB_SUCCESS) { + EPRINTLN("failed to attach device to guest %s", + libusb_error_name(error)); + goto failed; + } + + error = libusb_hotplug_register_callback(NULL, + LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | + LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, + 0, sc->vid, sc->pid, LIBUSB_HOTPLUG_MATCH_ANY, + upassthru_hotplug_callback, (void *)sc, &sc->cb); + if (error != LIBUSB_SUCCESS) { + EPRINTLN("failed to create callback for usb device %s", + libusb_error_name(error)); + goto failed; + } + + if (libusb_thread == NULL) { + error = pthread_create(&libusb_thread, NULL, libusb_pull_thread, + NULL); + if (error) + goto failed; + } + + atexit(upassthru_exit); + return (error); + +failed: + pthread_mutex_destroy(&sc->mtx); + free(sc); + return (1); +} + +#define UREQ(x,y) ((x) | ((y) << 8)) + +static int +upassthru_request(void *scarg, struct usb_data_xfer *xfer) +{ + struct upassthru_softc *sc = scarg; + struct usb_data_xfer_block *data; + int i, cur, err, value, index, len; + + data = NULL; + cur = xfer->head; + err = USB_ERR_NORMAL_COMPLETION; + + for (i = 0; i < xfer->ndata; i++) { + xfer->data[cur].bdone = 0; + if (data == NULL && USB_DATA_OK(xfer, i)) { + data = &xfer->data[cur]; + } + xfer->data[cur].processed = 1; + cur = (cur + 1) % USB_MAX_XFER_BLOCKS; + } + + if (data == NULL) + goto done; + + if (!xfer->ureq) { + DPRINTF(("upassthru_request not found: port %d", + sc->hci->hci_port)); + goto done; + } + + value = UGETW(xfer->ureq->wValue); + index = UGETW(xfer->ureq->wIndex); + len = UGETW(xfer->ureq->wLength); + DPRINTF(( + "upassthru_request: bRequest: %x bmRequestType: %x wValue: %x wIndex: %d wLength: %x", + xfer->ureq->bRequest, xfer->ureq->bmRequestType, value, index, + len)); + + pthread_mutex_lock(&sc->mtx); + if (sc->handle == NULL) { + err = USB_ERR_STALLED; + goto done; + } + + switch (UREQ(xfer->ureq->bRequest, xfer->ureq->bmRequestType)) { + case UREQ(UR_SET_ADDRESS, UT_WRITE_DEVICE): + err = 0; + goto done; + case UREQ(UR_SET_CONFIG, UT_WRITE_DEVICE): + err = upassthru_guest_detach_device_on_host(sc); + if (err) + goto done; + err = libusb_set_configuration(sc->handle, value & 0xff); + if (err) + goto done; + err = upassthru_guest_attach_device(sc); + if (err) + goto done; + goto done; + case UREQ(UR_SET_INTERFACE, UT_WRITE_INTERFACE): + if (sc->num_interfaces <= index) { + err = -1; + goto done; + } + err = libusb_set_interface_alt_setting(sc->handle, index, + value); + if (err == LIBUSB_SUCCESS) + sc->active_altsettings[index] = value; + upassthru_parse_ep_types(sc); + goto done; + case UREQ(UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT): + err = libusb_clear_halt(sc->handle, index); + goto done; + } + + data->blen = len; + data->bdone = 0; + + err = libusb_control_transfer(sc->handle, xfer->ureq->bmRequestType, + xfer->ureq->bRequest, UGETW(xfer->ureq->wValue), + UGETW(xfer->ureq->wIndex), data->buf, data->blen, 1000); + if (err < 0) + goto done; + data->blen -= err; + data->bdone = err; + err = 0; +done: + pthread_mutex_unlock(&sc->mtx); + if (xfer->ureq && (xfer->ureq->bmRequestType & UT_WRITE) && + (err == USB_ERR_NORMAL_COMPLETION) && (data != NULL)) + data->blen = 0; + + if (data) + DPRINTF( + ("upassthru request error code %d (0=ok), blen %u txlen %u", + err, (data ? data->blen : 0), + (data ? data->bdone : 0))); + + return (libusb_error_to_usb_error(err)); +} + +static void +upassthru_data_fill_xfer_iso(struct usb_data_xfer *xfer, + struct libusb_transfer *lusb_xfer) +{ + int i, cur, idx; + + for (i = 0, cur = xfer->head, idx = 0; i < xfer->ndata; + i = (i + 1) % USB_MAX_XFER_BLOCKS) { + if (xfer->data[cur].status == USB_LAST_DATA) { + lusb_xfer->iso_packet_desc[idx].length += + xfer->data[cur].blen; + } else if (xfer->data[cur].status == USB_NEXT_DATA) { + lusb_xfer->iso_packet_desc[idx].length += + xfer->data[cur].blen; + continue; + } else { + continue; + } + ++idx; + } +} + +static int +upassthru_data_handler(void *scarg, struct usb_data_xfer *xfer, int dir, + int epctx) +{ + struct upassthru_softc *sc = scarg; + struct upassthru_libusb_xfer *up_xfer; + int err, cur, len, ep, head, tail, offset, ep_type, nframe; + + assert(sc->ep_cnts[dir] >= epctx); + + ep_type = sc->endpoint_types[dir][epctx]; + + err = USB_ERR_NORMAL_COMPLETION; + ep = (dir ? LIBUSB_ENDPOINT_IN : 0) | epctx; + + if (ep_type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) + upassthru_data_calculate_num_isos(xfer, &head, &nframe, &len); + else + upassthru_calculate_xfer_ptr(xfer, &head, &tail, &len); + + if (len == 0) + goto done; + + DPRINTF(( + "upassthru handle data - DIR=%s|EP=%d, blen %d, transfer_type: %d, ndata: %d", + dir ? "IN" : "OUT", epctx, len, sc->endpoint_types[dir][epctx], + xfer->ndata)); + + pthread_mutex_lock(&sc->mtx); + if (sc->handle == NULL) + goto done; + + up_xfer = upassthru_xfer_alloc(sc, dir, xfer, len, ep, + ep_type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS ? nframe : 0); + + if (ep_type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) + upassthru_data_fill_xfer_iso(xfer, up_xfer->lusb_xfer); + + if (!dir) { + for (cur = head, offset = 0; offset < len; + cur = (cur + 1) % USB_MAX_XFER_BLOCKS) { + memcpy(&up_xfer->buffer[offset], xfer->data[cur].buf, + xfer->data[cur].blen); + offset += xfer->data[cur].blen; + xfer->data[cur].bdone = xfer->data[cur].blen = 0; + xfer->data[cur].blen = 0; + } + } + + switch (ep_type) { + case LIBUSB_TRANSFER_TYPE_BULK: + libusb_fill_bulk_transfer(up_xfer->lusb_xfer, sc->handle, ep, + up_xfer->buffer, len, upassthru_data_callback, up_xfer, 0); + break; + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + libusb_fill_interrupt_transfer(up_xfer->lusb_xfer, sc->handle, + ep, up_xfer->buffer, len, upassthru_data_callback, up_xfer, + 0); + break; + case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: + libusb_fill_iso_transfer(up_xfer->lusb_xfer, sc->handle, ep, + up_xfer->buffer, len, nframe, upassthru_data_callback, + up_xfer, 0); + break; + } + up_xfer->lusb_xfer->flags = LIBUSB_TRANSFER_FREE_TRANSFER | + LIBUSB_TRANSFER_FREE_BUFFER; + err = libusb_submit_transfer(up_xfer->lusb_xfer); + if (err) + goto done; + xfer->tr_softc = up_xfer; +done: + pthread_mutex_unlock(&sc->mtx); + return (libusb_error_to_usb_error(err)); +} + +static int +upassthru_reset(void *scarg __unused) +{ + struct upassthru_softc *sc = scarg; + int err = 0; + + DPRINTF(("%s", __FUNCTION__)); + + if (sc->handle) { + err = libusb_reset_device(sc->handle); + if (err != LIBUSB_SUCCESS) + goto done; + err = upassthru_guest_attach_device(sc); + } +done: + return (libusb_error_to_usb_error(err)); +} + +static int +upassthru_remove(void *scarg) +{ + struct upassthru_softc *sc = scarg; + int err; + + pthread_mutex_lock(&sc->mtx); + err = upassthru_guest_detach_device(sc); + if (err) + return (err); + libusb_hotplug_deregister_callback(NULL, sc->cb); + if ((err = upassthru_guest_detach_device_on_host(sc)) != + LIBUSB_SUCCESS) { + return (err); + } + libusb_close(sc->handle); + pthread_mutex_unlock(&sc->mtx); + + return (0); +} + +static int +upassthru_stop(void *scarg __unused) +{ + return (0); +} + +static void +upassthru_cancel(struct usb_data_xfer *xfer) +{ + struct upassthru_libusb_xfer *up_xfer = xfer->tr_softc; + struct upassthru_softc *sc; + + if (up_xfer == NULL) + return; + + sc = up_xfer->sc; + DPRINTF(("%s", __FUNCTION__)); + pthread_mutex_lock(&sc->mtx); + libusb_cancel_transfer(up_xfer->lusb_xfer); + xfer->tr_softc = NULL; + pthread_mutex_unlock(&sc->mtx); +} + +static struct usb_devemu ue_passthru = { + .ue_emu = "passthru", + .ue_usbver = 3, + .ue_usbspeed = USB_SPEED_HIGH, + .ue_probe = upassthru_probe, + .ue_init = upassthru_init, + .ue_request = upassthru_request, + .ue_data = upassthru_data_handler, + .ue_reset = upassthru_reset, + .ue_remove = upassthru_remove, + .ue_stop = upassthru_stop, + .ue_cancel = upassthru_cancel, +#ifdef BHYVE_SNAPSHOT + .ue_snapshot = upassthru_snapshot, +#endif +}; + +USB_EMUL_SET(ue_passthru);