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 @@ -61,8 +61,7 @@ #endif #include "usb_emul.h" - -static int xhci_debug = 0; +static int xhci_debug = 1; #define DPRINTF(params) if (xhci_debug) PRINTLN params #define WPRINTF(params) PRINTLN params @@ -406,7 +405,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 +624,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; @@ -661,6 +661,8 @@ devep = &dev->eps[epid]; pstreams = XHCI_EPCTX_0_MAXP_STREAMS_GET(ep_ctx->dwEpCtx0); if (pstreams > 0) { + assert(pstreams <= 15); + pstreams = 1 << (pstreams + 1); DPRINTF(("init_ep %d with pstreams %u", epid, pstreams)); assert(devep->ep_sctx_trbs == NULL); @@ -689,6 +691,9 @@ devep->ep_xfer = malloc(sizeof(struct usb_data_xfer)); USB_DATA_XFER_INIT(devep->ep_xfer); } + if (dev->dev_ue->ue_configure_ep) + (void)dev->dev_ue->ue_configure_ep(dev->dev_sc, epid, ep_ctx, + 1); } static void @@ -726,6 +731,8 @@ free(devep->ep_xfer); devep->ep_xfer = NULL; } + if (dev->dev_ue->ue_configure_ep) + dev->dev_ue->ue_configure_ep(dev->dev_sc, epid, ep_ctx, 0); memset(devep, 0, sizeof(struct pci_xhci_dev_ep)); } @@ -1179,8 +1186,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) @@ -1189,8 +1197,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 */ @@ -1203,8 +1209,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); @@ -1229,6 +1233,9 @@ USB_DATA_XFER_UNLOCK(devep->ep_xfer); } + if (devep->ep_xfer != NULL) + USB_DATA_XFER_RESET(devep->ep_xfer); + done: return (cmderr); } @@ -1546,8 +1553,8 @@ evtrb.qwTrb0 = crcr; evtrb.dwTrb2 |= XHCI_TRB_2_ERROR_SET(cmderr); evtrb.dwTrb3 |= XHCI_TRB_3_SLOT_SET(slot); - DPRINTF(("pci_xhci: command 0x%x result: 0x%x", - type, cmderr)); + DPRINTF(("pci_xhci: command 0x%x result: 0x%x", type, + cmderr)); pci_xhci_insert_event(sc, &evtrb, 1); } @@ -1601,25 +1608,19 @@ pci_xhci_xfer_complete(struct pci_xhci_softc *sc, struct usb_data_xfer *xfer, uint32_t slot, uint32_t epid, int *do_intr) { - struct pci_xhci_dev_emu *dev; - struct pci_xhci_dev_ep *devep; - struct xhci_dev_ctx *dev_ctx; - struct xhci_endp_ctx *ep_ctx; + struct xhci_dev_ctx *dev_ctx; struct xhci_trb *trb; struct xhci_trb evtrb; uint32_t trbflags; uint32_t edtla; + uint32_t remain = 0; int i, err; - dev = XHCI_SLOTDEV_PTR(sc, slot); - devep = &dev->eps[epid]; dev_ctx = pci_xhci_get_dev_ctx(sc, slot); if (dev_ctx == NULL) { return XHCI_TRB_ERROR_PARAMETER; } - ep_ctx = &dev_ctx->ctx_ep[epid]; - err = XHCI_TRB_ERROR_SUCCESS; *do_intr = 0; edtla = 0; @@ -1639,17 +1640,24 @@ 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); - pci_xhci_update_ep_ring(sc, dev, devep, ep_ctx, - xfer->data[i].streamid, xfer->data[i].trbnext, - xfer->data[i].ccs); + remain += xfer->data[i].blen; + + if (USB_DATA_GET_ERRCODE(&xfer->data[i])) + err = USB_TO_XHCI_ERR( + USB_DATA_GET_ERRCODE(&xfer->data[i])); + + if (xfer->data[i].blen > 0) + err = XHCI_TRB_ERROR_SHORT_PKT; /* Only interrupt if IOC or short packet */ if (!(trb->dwTrb3 & XHCI_TRB_3_IOC_BIT) && @@ -1661,7 +1669,7 @@ } evtrb.dwTrb2 = XHCI_TRB_2_ERROR_SET(err) | - XHCI_TRB_2_REM_SET(xfer->data[i].blen); + XHCI_TRB_2_REM_SET(remain); evtrb.dwTrb3 = XHCI_TRB_3_TYPE_SET(XHCI_TRB_EVENT_TRANSFER) | XHCI_TRB_3_SLOT_SET(slot) | XHCI_TRB_3_EP_SET(epid); @@ -1681,6 +1689,7 @@ if (err != XHCI_TRB_ERROR_SUCCESS) { break; } + err = XHCI_TRB_ERROR_SUCCESS; i = (i + 1) % USB_MAX_XFER_BLOCKS; } @@ -1746,7 +1755,6 @@ do_intr = 0; xfer = devep->ep_xfer; - USB_DATA_XFER_LOCK(xfer); /* outstanding requests queued up */ if (dev->dev_ue->ue_data != NULL) { @@ -1759,19 +1767,14 @@ } 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); } } - USB_DATA_XFER_UNLOCK(xfer); - - return (err); } @@ -1785,6 +1788,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; @@ -1808,10 +1812,11 @@ pci_xhci_dump_trb(trb); trbflags = trb->dwTrb3; - if (XHCI_TRB_3_TYPE_GET(trbflags) != XHCI_TRB_TYPE_LINK && + XHCI_TRB_3_TYPE_GET(trbflags) != + XHCI_TRB_TYPE_STATUS_STAGE && (trbflags & XHCI_TRB_3_CYCLE_BIT) != - (ccs & XHCI_TRB_3_CYCLE_BIT)) { + (ccs & XHCI_TRB_3_CYCLE_BIT)) { DPRINTF(("Cycle-bit changed trbflags %x, ccs %x", trbflags & XHCI_TRB_3_CYCLE_BIT, ccs)); break; @@ -1823,6 +1828,7 @@ case XHCI_TRB_TYPE_LINK: if (trb->dwTrb3 & XHCI_TRB_3_TC_BIT) ccs ^= 0x1; + trb->dwTrb3 &= ~XHCI_TRB_3_IOC_BIT; xfer_block = usb_data_xfer_append(xfer, NULL, 0, (void *)addr, ccs); @@ -1839,14 +1845,20 @@ setup_trb = trb; val = trb->qwTrb0; - if (!xfer->ureq) + if (xfer->ureq == NULL) xfer->ureq = malloc( sizeof(struct usb_device_request)); + if (xfer->ureq == NULL) { + err = XHCI_TRB_ERROR_STALL; + goto errout; + } + memcpy(xfer->ureq, &val, sizeof(struct usb_device_request)); xfer_block = usb_data_xfer_append(xfer, NULL, 0, - (void *)addr, ccs); + (void *)addr, ccs); + xfer_block->status = USB_NO_DATA; xfer_block->processed = 1; break; @@ -1865,24 +1877,42 @@ (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: xfer_block = usb_data_xfer_append(xfer, NULL, 0, (void *)addr, ccs); + setup_trb = NULL; break; case XHCI_TRB_TYPE_NOOP: - xfer_block = usb_data_xfer_append(xfer, NULL, 0, - (void *)addr, ccs); + if (!xfer_block) { + err = XHCI_TRB_ERROR_STALL; + goto errout; + } xfer_block->processed = 1; break; case XHCI_TRB_TYPE_EVENT_DATA: xfer_block = usb_data_xfer_append(xfer, NULL, 0, (void *)addr, ccs); - if ((epid > 1) && (trbflags & XHCI_TRB_3_IOC_BIT)) { - xfer_block->processed = 1; + if (!xfer_block) { + err = XHCI_TRB_ERROR_TRB; + goto errout; + } + 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; @@ -1901,23 +1931,21 @@ if (xfer_block) { xfer_block->trbnext = addr; xfer_block->streamid = streamid; + pci_xhci_update_ep_ring(sc, dev, devep, ep_ctx, + streamid, addr, ccs); } + 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; } - - /* 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; - break; - } } - DPRINTF(("pci_xhci[%d]: xfer->ndata %u", __LINE__, xfer->ndata)); + DPRINTF( + ("pci_xhci[%d]: xfer->ndata %u %d", __LINE__, xfer->ndata, epid)); if (xfer->ndata <= 0) goto errout; @@ -1952,13 +1980,16 @@ if (!do_retry) USB_DATA_XFER_UNLOCK(xfer); - if (do_intr) + if (do_intr) { pci_xhci_assert_interrupt(sc); + do_intr = 0; + } if (do_retry) { USB_DATA_XFER_RESET(xfer); DPRINTF(("pci_xhci[%d]: retry:continuing with next TRBs", __LINE__)); + do_retry = 0; goto retry; } @@ -2013,8 +2044,10 @@ return; /* handle pending transfers */ - if (devep->ep_xfer->ndata > 0) { + if (dev->dev_ue->ue_static && devep->ep_xfer->ndata > 0) { + USB_DATA_XFER_LOCK(devep->ep_xfer); pci_xhci_try_usb_xfer(sc, dev, devep, ep_ctx, slot, epid); + USB_DATA_XFER_UNLOCK(devep->ep_xfer); return; } @@ -2615,6 +2648,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); @@ -2649,7 +2683,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 { @@ -2665,6 +2699,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) { @@ -2674,8 +2736,10 @@ struct pci_xhci_softc *sc; struct pci_xhci_portregs *p; struct xhci_endp_ctx *ep_ctx; + struct usb_data_xfer *xfer; int error = 0; int dir_in; + int do_intr; int epid; dir_in = epctx & 0x80; @@ -2723,17 +2787,67 @@ DPRINTF(("xhci device interrupt on endpoint %d", epid)); - pci_xhci_device_doorbell(sc, hci->hci_port, epid, 0); + /* + * When the device is dynamic, we need to complete the previous TD then + * assert all interrupt from previous TD + */ + if (!dev->dev_ue->ue_static) { + xfer = dev->eps[epid].ep_xfer; + error = pci_xhci_xfer_complete(sc, xfer, hci->hci_slot, epid, + &do_intr); + if (error == XHCI_TRB_ERROR_SUCCESS) { + if (do_intr) + pci_xhci_assert_interrupt(sc); + USB_DATA_XFER_RESET(xfer); + } + } + pci_xhci_device_doorbell(sc, hci->hci_slot, epid, 0); done: return (error); } static int -pci_xhci_dev_event(struct usb_hci *hci, enum hci_usbev evid __unused, +pci_xhci_dev_event(struct usb_hci *hci, enum hci_usbev evid, 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; + int err; + + 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); + if ((err = pci_xhci_insert_event(xsc, &evtrb, 1)) != + XHCI_TRB_ERROR_SUCCESS) + return (err); + pci_xhci_assert_interrupt(xsc); + xsc->opregs.usbsts |= XHCI_STS_PCD; + return (0); + 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); + if ((err = pci_xhci_insert_event(xsc, &evtrb, 1)) != + XHCI_TRB_ERROR_SUCCESS) + return (err); + xsc->opregs.usbsts |= XHCI_STS_PCD; + pci_xhci_assert_interrupt(xsc); + return (0); + break; + default: + break; + } return (0); } @@ -2755,7 +2869,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) @@ -2775,7 +2889,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 @@ -2803,8 +2926,10 @@ usb3_port = sc->usb3_port_start; usb2_port = sc->usb2_port_start; - sc->devices = calloc(XHCI_MAX_DEVS, sizeof(struct pci_xhci_dev_emu *)); - sc->slots = calloc(XHCI_MAX_SLOTS, sizeof(struct pci_xhci_dev_emu *)); + sc->devices = calloc(XHCI_MAX_DEVS + 1, + sizeof(struct pci_xhci_dev_emu *)); + sc->slots = calloc(XHCI_MAX_SLOTS + 1, + sizeof(struct pci_xhci_dev_emu *)); ndevices = 0; @@ -2863,9 +2988,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; @@ -2875,7 +3001,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 " @@ -2913,7 +3039,8 @@ } portsfinal: - sc->portregs = calloc(XHCI_MAX_DEVS, sizeof(struct pci_xhci_portregs)); + sc->portregs = calloc(XHCI_MAX_DEVS + 1, + sizeof(struct pci_xhci_portregs)); if (ndevices > 0) { for (i = 1; i <= XHCI_MAX_DEVS; i++) { @@ -2926,8 +3053,11 @@ bad: for (i = 1; i <= XHCI_MAX_DEVS; i++) { - if (XHCI_DEVINST_PTR(sc, i) != NULL) + if (XHCI_DEVINST_PTR(sc, i) != NULL) { + XHCI_DEVINST_PTR(sc, i)->dev_ue->ue_remove( + XHCI_DEVINST_PTR(sc, i)->dev_sc); free(XHCI_DEVINST_PTR(sc, i)->dev_sc); + } free(XHCI_DEVINST_PTR(sc, i)); } @@ -2975,8 +3105,7 @@ XHCI_SET_HCCP1_NSS(1) | /* no 2nd-streams */ XHCI_SET_HCCP1_SPC(1) | /* short packet */ XHCI_SET_HCCP1_MAXPSA(XHCI_STREAMS_MAX); - sc->hccparams2 = XHCI_SET_HCCP2_LEC(1) | - XHCI_SET_HCCP2_U3C(1); + sc->hccparams2 = XHCI_SET_HCCP2_LEC(1) | XHCI_SET_HCCP2_U3C(1); sc->dboff = XHCI_SET_DOORBELL(XHCI_CAPLEN + XHCI_PORTREGS_START + XHCI_MAX_DEVS * sizeof(struct pci_xhci_portregs)); @@ -3275,6 +3404,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 @@ -34,7 +34,7 @@ #include #include -#define USB_MAX_XFER_BLOCKS 8 +#define USB_MAX_XFER_BLOCKS 256 #define USB_XFER_OUT 0 #define USB_XFER_IN 1 @@ -44,15 +44,17 @@ struct usb_device_request; struct usb_data_xfer; struct vm_snapshot_meta; +struct xhci_endp_ctx; /* Device emulation handlers */ struct usb_devemu { const char *ue_emu; /* name of device emulation */ int ue_usbver; /* usb version: 2 or 3 */ int ue_usbspeed; /* usb device speed */ + bool ue_static; /* for static emulated device like mouse */ /* 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 */ @@ -64,6 +66,8 @@ int (*ue_stop)(void *sc); int (*ue_snapshot)(void *scarg, struct vm_snapshot_meta *meta); int (*ue_cancel)(struct usb_data_xfer *xfer); + int (*ue_configure_ep)(void *sc, int epid, struct xhci_endp_ctx *epctx, + int configure); }; #define USB_EMUL_SET(x) DATA_SET(usb_emu_set, x) @@ -77,6 +81,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 +101,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 +120,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 +129,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; @@ -816,18 +816,19 @@ #endif static struct usb_devemu ue_mouse = { - .ue_emu = "tablet", - .ue_usbver = 3, - .ue_usbspeed = USB_SPEED_HIGH, - .ue_probe = umouse_probe, - .ue_init = umouse_init, - .ue_request = umouse_request, - .ue_data = umouse_data_handler, - .ue_reset = umouse_reset, - .ue_remove = umouse_remove, - .ue_stop = umouse_stop, + .ue_emu = "tablet", + .ue_static = 1, + .ue_usbver = 3, + .ue_usbspeed = USB_SPEED_HIGH, + .ue_probe = umouse_probe, + .ue_init = umouse_init, + .ue_request = umouse_request, + .ue_data = umouse_data_handler, + .ue_reset = umouse_reset, + .ue_remove = umouse_remove, + .ue_stop = umouse_stop, #ifdef BHYVE_SNAPSHOT - .ue_snapshot = umouse_snapshot, + .ue_snapshot = umouse_snapshot, #endif }; USB_EMUL_SET(ue_mouse); 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,871 @@ +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "debug.h" +#include "pci_xhci.h" +#include "usb_emul.h" + +static int usb_passthru_debug = 1; +static bool libusb_is_init = false; +#define DPRINTF(params) if (usb_passthru_debug) PRINTLN params +#define WPRINTF(params) PRINTLN params + +#define OUT 0 +#define IN 1 + +struct usb_passthru_libusb_xfer; + +struct usb_passthru_ep_types { + bool inout; + int type; +}; + +struct usb_passthru_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 */ + struct usb_passthru_ep_types endpoint_types[32]; + LIST_ENTRY(usb_passthru_softc) next; +}; + +struct usb_passthru_libusb_xfer { + struct libusb_transfer *lusb_xfer; + struct usb_data_xfer *usb_xfer; + struct usb_passthru_softc *sc; + uint8_t *buffer; + bool in; + int epid; + int size; +}; + +static LIST_HEAD(, usb_passthru_softc) devices = LIST_HEAD_INITIALIZER( + &devices); + +static int usb_passthru_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_STALLED); + 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 +usb_passthru_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]; + DPRINTF(("%s processed: %d len: %d, status: %d", __func__, + blk->processed, blk->blen, blk->status)); + 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 = i; + *size = length; +} + +static void +usb_passthru_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 usb_passthru_libusb_xfer * +usb_passthru_xfer_alloc(struct usb_passthru_softc *sc, int in, + struct usb_data_xfer *usb_xfer, int size, int ep, int iso) +{ + struct usb_passthru_libusb_xfer *xfer = calloc(1, + sizeof(struct usb_passthru_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 +usb_passthru_xfer_free(struct usb_passthru_libusb_xfer *xfer) +{ + free(xfer); +} + +static int +usb_passthru_guest_attach_device(struct usb_passthru_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) + goto done; + + 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; + } + +done: + libusb_free_config_descriptor(desc); + return (res); +} + +static int +usb_passthru_guest_detach_device_on_host(struct usb_passthru_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); + + return (libusb_error_to_usb_error(res)); +} + +static int +usb_passthru_guest_detach_device(struct usb_passthru_softc *sc __unused) +{ + return (USB_ERR_NORMAL_COMPLETION); +} + +static int +usb_passthru_hotplug_callback(struct libusb_context *ctx __unused, + struct libusb_device *dev, libusb_hotplug_event event, void *user_data) +{ + struct usb_passthru_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; + if ((err = usb_passthru_guest_attach_device(sc)) != + LIBUSB_SUCCESS) + goto done; + assert(sc->hci->hci_event(sc->hci, USBDEV_ATTACH, sc) == 0); + } + +done: + pthread_mutex_unlock(&sc->mtx); + + return (err); +} + +static void +usb_passthru_data_callback(struct libusb_transfer *lusb_xfer) +{ + struct usb_passthru_libusb_xfer *up_xfer = lusb_xfer->user_data; + struct usb_data_xfer *uxfer = up_xfer->usb_xfer; + struct usb_passthru_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; + + /* + * uxfer == NULL means the guest is cancelled by guest + * like machine shutdown. + */ + if (lusb_xfer->status == LIBUSB_TRANSFER_CANCELLED && uxfer == NULL) { + usb_passthru_xfer_free(up_xfer); + return; + } + + USB_DATA_XFER_LOCK(uxfer); + + if (lusb_xfer->type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) + usb_passthru_data_calculate_num_isos(uxfer, &head, &nframe, + &size); + else + usb_passthru_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)); + + 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 && act_len; + cur = (cur + 1) % USB_MAX_XFER_BLOCKS) { + uxfer->data[cur].processed = 1; + cur_len = act_len; + if (cur_len > uxfer->data[cur].blen) + cur_len = uxfer->data[cur].blen; + act_len -= cur_len; + uxfer->data[cur].blen -= cur_len; + uxfer->data[cur].bdone += cur_len; + } + + USB_DATA_SET_ERRCODE(&uxfer->data[head], + lusb_xfer->status == LIBUSB_TRANSFER_COMPLETED ? + USB_ERR_NORMAL_COMPLETION : + USB_ERR_IOERROR); + } + +done: + uxfer->tr_softc = NULL; + USB_DATA_XFER_UNLOCK(up_xfer->usb_xfer); + hci->hci_intr(hci, up_xfer->epid); + usb_passthru_xfer_free(up_xfer); +} + +static void +usb_passthru_exit(void) +{ + struct usb_passthru_softc *sc, *tmp; + + LIST_FOREACH_SAFE(sc, &devices, next, tmp) { + usb_passthru_remove(sc); + free(sc); + } + + event_thread_exit = true; +} + +static void * +usb_passthru_probe(struct usb_hci *hci, const nvlist_t *nvl) +{ + struct usb_passthru_softc *sc; + struct libusb_device_descriptor dev_desc; + struct libusb_device *dev; + struct libusb_init_option opts[] = { + { .option = LIBUSB_OPTION_CAPSICUMIZE } + }; + const char *param; + char *endp; + 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 init libusb: %s", + libusb_error_name(error)); + return (NULL); + } + } + sc = calloc(1, sizeof(struct usb_passthru_softc)); + + param = get_config_value_node(nvl, "param1"); + if (param == NULL) { + free(sc); + return (NULL); + } + sc->vid = strtoul(param, &endp, 16); + if (*endp != '\0') { + free(sc); + return (NULL); + } + param = get_config_value_node(nvl, "param2"); + if (param == NULL) { + free(sc); + return (NULL); + } + sc->pid = strtoul(param, &endp, 16); + if (*endp != '\0') { + free(sc); + return (NULL); + } + + 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 vid: %0lx pid: %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; + } + memset(sc->endpoint_types, -1, sizeof(sc->endpoint_types)); + + return (sc); +} + +static int +usb_passthru_init(void *scarg) +{ + struct usb_passthru_softc *sc = (struct usb_passthru_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 = usb_passthru_guest_attach_device(sc)) != LIBUSB_SUCCESS) + 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, + usb_passthru_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; + } + + LIST_INSERT_HEAD(&devices, sc, next); + atexit(usb_passthru_exit); + + return (error); + +failed: + usb_passthru_remove(&sc); + pthread_mutex_destroy(&sc->mtx); + free(sc); + return (1); +} + +#define UREQ(x,y) ((x) | ((y) << 8)) + +static int +usb_passthru_request(void *scarg, struct usb_data_xfer *xfer) +{ + struct usb_passthru_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 (!xfer->ureq) { + DPRINTF(("usb_passthru_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); + if (data == NULL && len != 0) { + err = LIBUSB_ERROR_NO_DEVICE; + goto done; + } + + pthread_mutex_lock(&sc->mtx); + + if (sc->handle == NULL) { + err = LIBUSB_ERROR_INVALID_PARAM; + goto done_locked; + } + + switch (UREQ(xfer->ureq->bRequest, xfer->ureq->bmRequestType)) { + case UREQ(UR_SET_ADDRESS, UT_WRITE_DEVICE): + err = 0; + goto done_locked; + case UREQ(UR_SET_CONFIG, UT_WRITE_DEVICE): + err = usb_passthru_guest_detach_device_on_host(sc); + if (err) + goto done_locked; + err = libusb_set_configuration(sc->handle, value & 0xff); + if (err) + goto done_locked; + err = usb_passthru_guest_attach_device(sc); + if (err) + goto done_locked; + goto done_locked; + case UREQ(UR_SET_INTERFACE, UT_WRITE_INTERFACE): + err = libusb_set_interface_alt_setting(sc->handle, index, + value); + goto done_locked; + case UREQ(UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT): + err = libusb_clear_halt(sc->handle, index); + goto done_locked; + } + + if (data) { + data->blen = len; + data->bdone = 0; + } + DPRINTF(( + "usb_passthru_request: bRequest: %x bmRequestType: %x wValue: %x wIndex: %d wLength: %x", + xfer->ureq->bRequest, xfer->ureq->bmRequestType, value, index, + len)); + + err = libusb_control_transfer(sc->handle, xfer->ureq->bmRequestType, + xfer->ureq->bRequest, UGETW(xfer->ureq->wValue), + UGETW(xfer->ureq->wIndex), data ? data->buf : NULL, len, 1000); + + if (err < 0) { + if (err == LIBUSB_ERROR_INTERRUPTED || + err == LIBUSB_ERROR_PIPE) { + if (data) + USB_DATA_SET_ERRCODE(data, USB_STALL); + err = LIBUSB_ERROR_NOT_FOUND; + } else { + if (data) + USB_DATA_SET_ERRCODE(data, USB_ERR); + err = LIBUSB_ERROR_NOT_FOUND; + } + goto done_locked; + } + if (data) { + data->blen -= err; + data->bdone = err; + } + err = 0; + +done_locked: + pthread_mutex_unlock(&sc->mtx); +done: + err = libusb_error_to_usb_error(err); + + if (xfer->ureq && (xfer->ureq->bmRequestType & UT_WRITE) && + (err == USB_ERR_NORMAL_COMPLETION) && (data != NULL)) + data->blen = 0; + + DPRINTF(("usb_passthru request error code %d (0=ok), blen %u txlen %u", + err, (data ? data->blen : 0), (data ? data->bdone : 0))); + + return (err); +} + +static void +usb_passthru_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 +usb_passthru_data_handler(void *scarg, struct usb_data_xfer *xfer, int dir, + int epctx) +{ + struct usb_passthru_softc *sc = scarg; + struct usb_passthru_libusb_xfer *up_xfer; + int err, cur, len, ep, head, tail, offset, ep_type, nframe; + + ep_type = sc->endpoint_types[epctx << 1 | dir].type; + dir = sc->endpoint_types[epctx << 1 | dir].inout; + assert(ep_type != -1); + + err = USB_ERR_NORMAL_COMPLETION; + ep = (dir ? LIBUSB_ENDPOINT_IN : 0) | epctx; + + if (ep_type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) + usb_passthru_data_calculate_num_isos(xfer, &head, &nframe, + &len); + else + usb_passthru_calculate_xfer_ptr(xfer, &head, &tail, &len); + + if (len == 0) + goto done; + + DPRINTF(( + "usb_passthru handle data - DIR=%s|EP=%d, blen %d, transfer_type: %d, ndata: %d", + dir ? "IN" : "OUT", epctx, len, + sc->endpoint_types[epctx << 1 | dir].type, xfer->ndata)); + + pthread_mutex_lock(&sc->mtx); + if (sc->handle == NULL || xfer->tr_softc) + goto done_locked; + pthread_mutex_unlock(&sc->mtx); + + up_xfer = usb_passthru_xfer_alloc(sc, dir, xfer, len, ep, + ep_type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS ? nframe : 0); + + if (ep_type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) + usb_passthru_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; + } + } + + pthread_mutex_lock(&sc->mtx); + switch (ep_type) { + case LIBUSB_TRANSFER_TYPE_BULK: + libusb_fill_bulk_transfer(up_xfer->lusb_xfer, sc->handle, ep, + up_xfer->buffer, len, usb_passthru_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, usb_passthru_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, usb_passthru_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_locked; + xfer->tr_softc = up_xfer; +done_locked: + pthread_mutex_unlock(&sc->mtx); +done: + return (libusb_error_to_usb_error(err)); +} + +static int +usb_passthru_reset(void *scarg __unused) +{ + struct usb_passthru_softc *sc = scarg; + int err = 0; + + DPRINTF(("%s", __FUNCTION__)); + + pthread_mutex_lock(&sc->mtx); + + if (sc->handle) { + err = libusb_reset_device(sc->handle); + if (err != LIBUSB_SUCCESS) + goto done; + err = usb_passthru_guest_attach_device(sc); + } +done: + pthread_mutex_unlock(&sc->mtx); + return (libusb_error_to_usb_error(err)); +} + +static int +usb_passthru_remove(void *scarg) +{ + struct usb_passthru_softc *sc = scarg; + int err; + if (sc == NULL) + return (USB_ERR_NORMAL_COMPLETION); + + LIST_REMOVE(sc, next); + pthread_mutex_lock(&sc->mtx); + err = usb_passthru_guest_detach_device(sc); + if (err) + goto done; + libusb_hotplug_deregister_callback(NULL, sc->cb); + if ((err = usb_passthru_guest_detach_device_on_host(sc)) != + LIBUSB_SUCCESS) { + goto done; + } + libusb_close(sc->handle); + +done: + pthread_mutex_unlock(&sc->mtx); + + return (err); +} + +static int +usb_passthru_stop(void *scarg __unused) +{ + return (0); +} + +static int +usb_passthru_configure_ep(void *scarg, int epid, struct xhci_endp_ctx *ctx, + int configure) +{ + struct usb_passthru_softc *sc = scarg; + int type; + + if (configure) { + type = XHCI_EPCTX_1_EPTYPE_GET(ctx->dwEpCtx1); + sc->endpoint_types[epid].inout = type >= 5; + type &= 3; + sc->endpoint_types[epid].type = type; + } else { + sc->endpoint_types[epid].inout = sc->endpoint_types[epid].type = + -1; + } + + return (0); +} + +static int +usb_passthru_cancel(struct usb_data_xfer *xfer) +{ + struct usb_passthru_libusb_xfer *up_xfer; + int err; + + up_xfer = xfer->tr_softc; + if (up_xfer == NULL) { + return (0); + } + up_xfer->usb_xfer = NULL; + + DPRINTF(("%s", __FUNCTION__)); + + err = libusb_cancel_transfer(up_xfer->lusb_xfer); + xfer->tr_softc = NULL; + + return (libusb_error_to_usb_error(err)); +} + +static struct usb_devemu ue_passthru = { + .ue_emu = "passthru", + .ue_static = 0, + .ue_usbver = 3, + .ue_usbspeed = USB_SPEED_HIGH, + .ue_probe = usb_passthru_probe, + .ue_init = usb_passthru_init, + .ue_request = usb_passthru_request, + .ue_data = usb_passthru_data_handler, + .ue_reset = usb_passthru_reset, + .ue_remove = usb_passthru_remove, + .ue_stop = usb_passthru_stop, + .ue_cancel = usb_passthru_cancel, + .ue_configure_ep = usb_passthru_configure_ep, +#ifdef BHYVE_SNAPSHOT + .ue_snapshot = usb_passthru_snapshot, +#endif +}; + +USB_EMUL_SET(ue_passthru);