Page MenuHomeFreeBSD

D52166.id.diff
No OneTemporary

D52166.id.diff

diff --git a/usr.sbin/bhyve/Makefile b/usr.sbin/bhyve/Makefile
--- a/usr.sbin/bhyve/Makefile
+++ b/usr.sbin/bhyve/Makefile
@@ -68,6 +68,7 @@
uart_emul.c \
usb_emul.c \
usb_mouse.c \
+ usb_passthru.c \
virtio.c \
vmexit.c \
vmgenc.c
@@ -98,7 +99,7 @@
-I${.CURDIR}/../../contrib/lib9p \
-I${SRCTOP}/sys
-LIBADD+= vmmapi md nv uvmem pthread z util sbuf cam 9p
+LIBADD+= vmmapi md nv uvmem 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/bhyve.8 b/usr.sbin/bhyve/bhyve.8
--- a/usr.sbin/bhyve/bhyve.8
+++ b/usr.sbin/bhyve/bhyve.8
@@ -26,7 +26,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd May 15, 2026
+.Dd June 11, 2026
.Dt BHYVE 8
.Os
.Sh NAME
@@ -1062,15 +1062,38 @@
the session over an encrypted channel provided by IPsec or SSH.
.El
.Ss xHCI USB device backends
-.Bl -bullet
.Sm off
+.Bl -bullet
.It
.Ar tablet
-.Sm on
+.It
+.Ar passthru Cm \&. Ar vendor_id Cm \&. Ar product_id
.El
+.Sm on
.Pp
+USB device backends are defined as follows:
+.Bl -tag -width 10n
+.It Ar tablet
A USB tablet device that provides precise cursor synchronization
when using VNC.
+.It Ar passthru
+A USB device attached to the host that is passed through to the guest.
+The device to pass through is selected by its
+.Ar vendor_id
+and
+.Ar product_id ,
+both given as hexadecimal values, as reported by
+.Xr usbconfig 8 .
+While the virtual machine is running, the device is claimed from the
+host and is controlled exclusively by the guest.
+Control, interrupt, bulk and isochronous transfers are supported.
+If the device is unplugged while the virtual machine is running, it is
+detached from the guest; a device with the same
+.Ar vendor_id
+and
+.Ar product_id
+is attached to the guest again when it is plugged in.
+.El
.Ss NVMe device backends
.Bl -bullet
.Sm off
@@ -1310,6 +1333,8 @@
Run a UEFI virtual machine with a display resolution of 800 by 600 pixels
that can be accessed via VNC at: 0.0.0.0:5900 or via serial console over
TCP at: 127.0.0.1:1234 (unsafe if you expose serial console without protection).
+The host USB device with vendor ID 0x0a12 and product ID 0x0001 is passed
+through to the guest.
.Bd -literal -offset indent
bhyve -c 2 -m 4G -w -H \\
-s 0,hostbridge \\
@@ -1317,7 +1342,7 @@
-s 4,ahci-hd,disk.img \\
-s 5,virtio-net,tap0 \\
-s 29,fbuf,tcp=0.0.0.0:5900,w=800,h=600,wait \\
- -s 30,xhci,tablet \\
+ -s 30,xhci,tablet,passthru.0a12.0001 \\
-s 31,lpc -l com1,tcp=127.0.0.1:1234 \\
-l bootrom,/usr/local/share/uefi-firmware/BHYVE_UEFI.fd \\
uefivm
@@ -1413,6 +1438,7 @@
.Xr ethers 5 ,
.Xr bhyvectl 8 ,
.Xr bhyveload 8 ,
+.Xr usbconfig 8 ,
.Xr domainset 9
.Pp
.Rs
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
@@ -85,7 +84,13 @@
#define XHCI_PORTREGS_START 0x400
#define XHCI_DOORBELL_MAX 256
-#define XHCI_STREAMS_MAX 1 /* 4-15 in XHCI spec */
+/*
+ * Maximum number of primary stream array entries we support. The HCCPARAMS1
+ * MaxPSASize field is a 4-bit encoding where the supported count is
+ * 2^(MaxPSASize+1); XHCI_MAXPSASIZE is that encoding (15 => 2^16 streams).
+ */
+#define XHCI_STREAMS_MAX 65536
+#define XHCI_MAXPSASIZE 15
/* caplength and hci-version registers */
#define XHCI_SET_CAPLEN(x) ((x) & 0xFF)
@@ -265,6 +270,15 @@
struct pci_devinst *xsc_pi;
pthread_mutex_t mtx;
+ pthread_mutex_t event_mtx; /* serialises er_enq_idx / er_events_cnt
+ * updates in insert_event (libusb thread)
+ * against the recalculation in the ERDP
+ * high-word write handler (MMIO thread).
+ * Must NOT be held across pci_generate_msi
+ * to avoid stalling the vCPU's ERDP write,
+ * which also needs this lock (under sc->mtx).
+ * Lock order: sc->mtx -> [xfer_lock ->]
+ * event_mtx */
uint32_t caplength; /* caplen & hciversion */
uint32_t hcsparams1; /* structural parameters 1 */
@@ -406,7 +420,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 +639,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 +676,16 @@
devep = &dev->eps[epid];
pstreams = XHCI_EPCTX_0_MAXP_STREAMS_GET(ep_ctx->dwEpCtx0);
if (pstreams > 0) {
+ /*
+ * MaxPStreams is guest-controlled; reject out-of-range values
+ * instead of asserting (which would let the guest abort bhyve).
+ */
+ if (pstreams > 15) {
+ WPRINTF(("pci_xhci: invalid MaxPStreams %u, clamping",
+ pstreams));
+ pstreams = 15;
+ }
+ pstreams = 1 << (pstreams + 1);
DPRINTF(("init_ep %d with pstreams %u", epid, pstreams));
assert(devep->ep_sctx_trbs == NULL);
@@ -689,6 +714,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 +754,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));
}
@@ -767,6 +797,8 @@
err = XHCI_TRB_ERROR_SUCCESS;
+ pthread_mutex_lock(&sc->event_mtx);
+
rts = &sc->rtsregs;
erdp = rts->intrreg.erdp & ~0xF;
@@ -789,7 +821,7 @@
DPRINTF(("pci_xhci[%d] cannot insert event; ring full",
__LINE__));
err = XHCI_TRB_ERROR_EV_RING_FULL;
- goto done;
+ goto done_locked;
}
if (rts->er_events_cnt == rts->erstba_p->dwEvrsTableSize - 1) {
@@ -814,7 +846,7 @@
err = XHCI_TRB_ERROR_EV_RING_FULL;
do_intr = 1;
- goto done;
+ goto done_locked;
}
} else {
rts->er_events_cnt++;
@@ -830,7 +862,9 @@
if (rts->er_enq_idx == 0)
rts->event_pcs ^= 1;
-done:
+done_locked:
+ pthread_mutex_unlock(&sc->event_mtx);
+
if (do_intr)
pci_xhci_assert_interrupt(sc);
@@ -1173,8 +1207,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)
@@ -1183,8 +1218,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 */
@@ -1197,8 +1230,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);
@@ -1215,14 +1246,40 @@
ep_ctx->dwEpCtx4));
if (type == XHCI_TRB_TYPE_STOP_EP && devep->ep_xfer != NULL) {
+ int stopped;
+
USB_DATA_XFER_LOCK(devep->ep_xfer);
+ stopped = devep->ep_xfer->tr_softc != NULL;
if (dev->dev_ue->ue_cancel == NULL ||
dev->dev_ue->ue_cancel(devep->ep_xfer) !=
USB_ERR_NORMAL_COMPLETION)
cmderr = XHCI_TRB_ERROR_ENDP_NOT_ON;
USB_DATA_XFER_UNLOCK(devep->ep_xfer);
+
+ /*
+ * xHCI 4.6.9: if a TD was in progress when the endpoint was
+ * stopped, post a Transfer Event with completion code Stopped
+ * (referencing the current dequeue pointer) ahead of the
+ * command completion event so the guest can resync its
+ * transfer ring dequeue pointer.
+ */
+ if (stopped && cmderr == XHCI_TRB_ERROR_SUCCESS) {
+ struct xhci_trb evtrb;
+
+ evtrb.qwTrb0 = devep->ep_ringaddr;
+ evtrb.dwTrb2 = XHCI_TRB_2_ERROR_SET(
+ XHCI_TRB_ERROR_STOPPED) |
+ XHCI_TRB_2_REM_SET(0);
+ evtrb.dwTrb3 = XHCI_TRB_3_TYPE_SET(
+ XHCI_TRB_EVENT_TRANSFER) |
+ XHCI_TRB_3_SLOT_SET(slot) | XHCI_TRB_3_EP_SET(epid);
+ pci_xhci_insert_event(sc, &evtrb, 0);
+ }
}
+ if (devep->ep_xfer != NULL)
+ USB_DATA_XFER_RESET(devep->ep_xfer);
+
done:
return (cmderr);
}
@@ -1540,8 +1597,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);
}
@@ -1595,25 +1652,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;
@@ -1633,17 +1684,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) &&
@@ -1655,7 +1713,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);
@@ -1669,12 +1727,19 @@
edtla = 0;
}
- *do_intr = 1;
+ /*
+ * xHCI 6.4.1.1: the event is still placed on the ring, but a
+ * Block Event Interrupt TRB must not assert an interrupt.
+ */
+ if (!(trbflags & XHCI_TRB_3_BEI_BIT))
+ *do_intr = 1;
err = pci_xhci_insert_event(sc, &evtrb, 0);
if (err != XHCI_TRB_ERROR_SUCCESS) {
break;
}
+ err = XHCI_TRB_ERROR_SUCCESS;
+ remain = 0;
i = (i + 1) % USB_MAX_XFER_BLOCKS;
}
@@ -1740,7 +1805,6 @@
do_intr = 0;
xfer = devep->ep_xfer;
- USB_DATA_XFER_LOCK(xfer);
/* outstanding requests queued up */
if (dev->dev_ue->ue_data != NULL) {
@@ -1753,19 +1817,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);
}
@@ -1779,10 +1838,10 @@
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;
- int do_retry;
+ int do_intr, err;
ep_ctx->dwEpCtx0 = FIELD_REPLACE(ep_ctx->dwEpCtx0,
XHCI_ST_EPCTX_RUNNING, 0x7, 0);
@@ -1792,9 +1851,7 @@
DPRINTF(("pci_xhci handle_transfer slot %u", slot));
-retry:
err = XHCI_TRB_ERROR_INVALID;
- do_retry = 0;
do_intr = 0;
setup_trb = NULL;
@@ -1802,10 +1859,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;
@@ -1817,6 +1875,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);
@@ -1833,14 +1892,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;
@@ -1859,24 +1924,44 @@
(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);
+ (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;
@@ -1895,23 +1980,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;
@@ -1929,8 +2012,6 @@
err == XHCI_TRB_ERROR_SHORT_PKT) {
err = pci_xhci_xfer_complete(sc, xfer, slot, epid,
&do_intr);
- if (err != XHCI_TRB_ERROR_SUCCESS)
- do_retry = 0;
}
} else {
@@ -1943,17 +2024,11 @@
if (err == XHCI_TRB_ERROR_EV_RING_FULL)
DPRINTF(("pci_xhci[%d]: event ring full", __LINE__));
- if (!do_retry)
- USB_DATA_XFER_UNLOCK(xfer);
+ USB_DATA_XFER_UNLOCK(xfer);
- if (do_intr)
+ if (do_intr) {
pci_xhci_assert_interrupt(sc);
-
- if (do_retry) {
- USB_DATA_XFER_RESET(xfer);
- DPRINTF(("pci_xhci[%d]: retry:continuing with next TRBs",
- __LINE__));
- goto retry;
+ do_intr = 0;
}
if (epid == 1)
@@ -2007,8 +2082,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;
}
@@ -2148,7 +2225,7 @@
break;
case 0x18:
- /* ERDP low bits */
+ /* ERDP low bits — does not touch er_events_cnt/er_enq_idx */
rts->intrreg.erdp =
MASK_64_HI(sc->rtsregs.intrreg.erdp) |
(rts->intrreg.erdp & XHCI_ERDP_LO_BUSY) |
@@ -2164,6 +2241,7 @@
case 0x1C:
/* ERDP high bits */
+ pthread_mutex_lock(&sc->event_mtx);
rts->intrreg.erdp = (value << 32) |
MASK_64_LO(sc->rtsregs.intrreg.erdp);
@@ -2185,6 +2263,7 @@
DPRINTF(("pci_xhci: erdp 0x%lx, events cnt %u",
erdp, rts->er_events_cnt));
}
+ pthread_mutex_unlock(&sc->event_mtx);
break;
@@ -2609,6 +2688,8 @@
port = XHCI_PORTREG_PTR(sc, portn);
dev = XHCI_DEVINST_PTR(sc, portn);
if (dev) {
+ if (dev->dev_ue->ue_reset != NULL)
+ 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);
@@ -2643,7 +2724,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 {
@@ -2659,6 +2740,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)
{
@@ -2668,8 +2777,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;
@@ -2691,18 +2802,35 @@
p = XHCI_PORTREG_PTR(sc, hci->hci_port);
- /* raise event if link U3 (suspended) state */
+ /*
+ * Device activity while the port is suspended (U3) is a remote
+ * wakeup. Transition the link to Resume and notify the guest with a
+ * Port Status Change Event.
+ *
+ * This MUST assert an interrupt (do_intr = 1): the guest has put the
+ * device into selective suspend and is no longer polling, so an event
+ * queued without an interrupt is never observed and the device stays
+ * silent until something else pokes the controller (e.g. opening the
+ * Bluetooth manager). The previous code inserted the event with
+ * do_intr = 0 and also bailed out early when PLC was already set (which
+ * it is right after a guest-commanded U3 transition), dropping the
+ * wakeup entirely and hanging the device after a Bluetooth profile
+ * switch.
+ *
+ * The libusb event thread is single-threaded, so PLS == U3 is observed
+ * exactly once per resume (the line below immediately moves it to
+ * Resume); no duplicate-event guard is needed. The Port Status Change
+ * Event is enqueued here before the transfer completion event produced
+ * by the fall-through below, so the guest resumes the port (writes U0)
+ * before it processes the pending data -- the correct ordering.
+ */
if (XHCI_PS_PLS_GET(p->portsc) == 3) {
p->portsc &= ~XHCI_PS_PLS_MASK;
- p->portsc |= XHCI_PS_PLS_SET(UPS_PORT_LS_RESUME);
- if ((p->portsc & XHCI_PS_PLC) != 0)
- return (0);
-
- p->portsc |= XHCI_PS_PLC;
+ p->portsc |= XHCI_PS_PLS_SET(UPS_PORT_LS_RESUME) | XHCI_PS_PLC;
pci_xhci_set_evtrb(&evtrb, hci->hci_port,
XHCI_TRB_ERROR_SUCCESS, XHCI_TRB_EVENT_PORT_STS_CHANGE);
- error = pci_xhci_insert_event(sc, &evtrb, 0);
+ error = pci_xhci_insert_event(sc, &evtrb, 1);
if (error != XHCI_TRB_ERROR_SUCCESS)
goto done;
}
@@ -2717,17 +2845,94 @@
DPRINTF(("xhci device interrupt on endpoint %d", epid));
- pci_xhci_device_doorbell(sc, hci->hci_port, epid, 0);
+ /*
+ * Dynamic backends (e.g. usb_passthru) invoke hci_intr() with the
+ * endpoint's xfer lock held, so complete the finished TD(s) here under
+ * that lock. Assert the interrupt whenever events were produced; only
+ * reset the ring once it is fully drained (SUCCESS), otherwise the
+ * still-queued buffers behind the completed TD would be discarded.
+ *
+ * Do not re-drive via device_doorbell() here: that reads new TRBs from
+ * the guest TR and would race with handle_transfer() on the same ring.
+ * Instead use try_usb_xfer() directly, which only resubmits already-
+ * appended blocks, and is safe to call while holding the xfer lock.
+ *
+ * This resubmission handles the case where the guest queued TRBs for
+ * the next TD while the previous one was still in-flight: data_handler
+ * skipped the submit (tr_softc was set), so those blocks are still
+ * pending after xfer_complete() returns INVALID. Without the retry
+ * the blocks would sit idle until the guest's next doorbell, which
+ * may never arrive if the guest is waiting for the matching Transfer
+ * Completion Event.
+ */
+ if (!dev->dev_ue->ue_static) {
+ xfer = dev->eps[epid].ep_xfer;
+ if (xfer != NULL) {
+ error = pci_xhci_xfer_complete(sc, xfer, hci->hci_slot,
+ epid, &do_intr);
+ if (do_intr)
+ pci_xhci_assert_interrupt(sc);
+ if (error == XHCI_TRB_ERROR_SUCCESS) {
+ USB_DATA_XFER_RESET(xfer);
+ } else if (error == XHCI_TRB_ERROR_INVALID) {
+ /*
+ * Blocks from a later TD are already in the
+ * ring (guest queued ahead). Submit them now
+ * rather than waiting for another doorbell.
+ */
+ pci_xhci_try_usb_xfer(sc, dev, &dev->eps[epid],
+ ep_ctx, hci->hci_slot, epid);
+ }
+ }
+ } else {
+ 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);
}
@@ -2749,7 +2954,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)
@@ -2769,7 +2974,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
@@ -2797,8 +3011,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;
@@ -2857,9 +3073,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;
@@ -2869,7 +3086,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 "
@@ -2907,7 +3124,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++) {
@@ -2920,8 +3138,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));
}
@@ -2947,6 +3168,9 @@
pi->pi_arg = sc;
sc->xsc_pi = pi;
+ pthread_mutex_init(&sc->mtx, NULL);
+ pthread_mutex_init(&sc->event_mtx, NULL);
+
sc->usb2_port_start = (XHCI_MAX_DEVS/2) + 1;
sc->usb3_port_start = 1;
@@ -2968,9 +3192,8 @@
sc->hccparams1 = XHCI_SET_HCCP1_AC64(1) | /* 64-bit addrs */
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);
+ XHCI_SET_HCCP1_MAXPSA(XHCI_MAXPSASIZE);
+ 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));
@@ -3015,10 +3238,10 @@
pci_lintr_request(pi);
- pthread_mutex_init(&sc->mtx, NULL);
-
done:
if (error) {
+ pthread_mutex_destroy(&sc->event_mtx);
+ pthread_mutex_destroy(&sc->mtx);
free(sc);
}
@@ -3269,6 +3492,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 <sys/linker_set.h>
#include <pthread.h>
-#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;
};
@@ -142,6 +158,7 @@
memset((x)->data, 0, sizeof((x)->data)); \
(x)->ndata = 0; \
(x)->head = (x)->tail = 0; \
+ (x)->tr_softc = NULL; \
} while (0)
#define USB_DATA_XFER_LOCK(x) do { \
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;
@@ -824,18 +824,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,1016 @@
+#include <sys/cdefs.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#include <assert.h>
+#include <libusb.h>
+#include <pthread.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#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;
+ int head; /* ring index of the first block this transfer covers */
+ int ndata; /* number of blocks this transfer covers (at submit) */
+};
+
+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) {
+ /*
+ * Do not exit the loop on a transient error: this
+ * thread is the only one that delivers async transfer
+ * completions, so tearing it down would permanently
+ * stop every interrupt/bulk/isochronous callback. Log
+ * it and keep polling.
+ */
+ WPRINTF(("libusb_handle_events failed: %s",
+ libusb_error_name(err)));
+ usleep(1000);
+ }
+ }
+
+ 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, cur = (cur + 1) % USB_MAX_XFER_BLOCKS) {
+ if (xfer->data[cur].processed)
+ continue;
+ switch (xfer->data[cur].status) {
+ case USB_LAST_DATA:
+ ++nf;
+ if (first == -1)
+ first = cur;
+ /* Fallthrough */
+ case USB_NEXT_DATA:
+ length += xfer->data[cur].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) {
+ if (libusb_kernel_driver_active(handle, intf) == 1) {
+ 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) {
+ /*
+ * Restore every interface best-effort: an interface that was
+ * never claimed (e.g. the sibling interface of a Bluetooth
+ * device) returns LIBUSB_ERROR_NOT_FOUND, and reattaching its
+ * kernel driver is likewise best-effort, so neither must abort
+ * restoring the rest of the device.
+ */
+ res = libusb_release_interface(handle, intf);
+ if (res != LIBUSB_SUCCESS && res != LIBUSB_ERROR_NOT_FOUND)
+ break;
+ (void)libusb_attach_kernel_driver(handle, intf);
+ res = LIBUSB_SUCCESS;
+ }
+
+ 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, ndata;
+ char *buffer;
+ int cur_iso = 0, i;
+
+ /*
+ * uxfer == NULL means the guest is cancelled by guest
+ * like machine shutdown.
+ */
+ if (uxfer == NULL) {
+ usb_passthru_xfer_free(up_xfer);
+ return;
+ }
+
+ USB_DATA_XFER_LOCK(uxfer);
+
+ if (lusb_xfer->type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) {
+ /*
+ * Scatter into exactly the blocks this transfer carried, not
+ * whatever has accumulated in the ring since it was submitted.
+ */
+ head = up_xfer->head;
+ ndata = up_xfer->ndata;
+ size = up_xfer->size;
+ } else {
+ usb_passthru_calculate_xfer_ptr(uxfer, &head, &tail, &size);
+ ndata = uxfer->ndata;
+ }
+
+ if (lusb_xfer->status == LIBUSB_TRANSFER_STALL ||
+ lusb_xfer->status == LIBUSB_TRANSFER_NO_DEVICE ||
+ lusb_xfer->status == LIBUSB_TRANSFER_CANCELLED) {
+ if (head >= 0) {
+ USB_DATA_SET_ERRCODE(&uxfer->data[head], USB_STALL);
+ for (cur = head, i = 0; i < ndata;
+ cur = (cur + 1) % USB_MAX_XFER_BLOCKS, ++i) {
+ uxfer->data[cur].blen = 0;
+ uxfer->data[cur].bdone = 0;
+ uxfer->data[cur].processed = 1;
+ /*
+ * Non-iso: ndata is the live ring count and may
+ * include TDs the guest queued ahead, so stop
+ * at this TD's terminator. Iso: ndata is this
+ * transfer's captured frame count spanning
+ * several USB_LAST_DATA terminators, so mark
+ * all.
+ */
+ if (lusb_xfer->type !=
+ LIBUSB_TRANSFER_TYPE_ISOCHRONOUS &&
+ uxfer->data[cur].status == USB_LAST_DATA)
+ break;
+ }
+ }
+ 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) {
+ int is_iso = (lusb_xfer->type ==
+ LIBUSB_TRANSFER_TYPE_ISOCHRONOUS);
+ int avail;
+
+ if (!is_iso && act_len > size)
+ act_len = size;
+
+ for (cur = head, offset = 0, i = 0; i < ndata;
+ cur = (cur + 1) % USB_MAX_XFER_BLOCKS, ++i) {
+ struct usb_data_xfer_block *blk = &uxfer->data[cur];
+
+ /* non-data block (e.g. event-data marker) */
+ if (blk->status == USB_NO_DATA) {
+ blk->bdone = 0;
+ blk->processed = 1;
+ continue;
+ }
+
+ /*
+ * For isochronous transfers a frame is one or more
+ * blocks terminated by USB_LAST_DATA; the received data
+ * lives in this frame's iso packet buffer, so consume
+ * from there and advance to the next packet only at the
+ * frame boundary (matches usb_passthru_data_fill_xfer_
+ * iso()). For bulk/interrupt the payload is contiguous.
+ */
+ if (is_iso) {
+ avail = lusb_xfer->iso_packet_desc[cur_iso]
+ .actual_length -
+ offset;
+ buffer = libusb_get_iso_packet_buffer_simple(
+ lusb_xfer, cur_iso) +
+ offset;
+ } else {
+ avail = act_len;
+ buffer = &lusb_xfer->buffer[offset];
+ }
+ if (avail < 0)
+ avail = 0;
+
+ cur_len = blk->blen;
+ if (cur_len > avail) {
+ cur_len = avail;
+ USB_DATA_SET_ERRCODE(blk, USB_SHORT);
+ }
+ if (cur_len)
+ memcpy(blk->buf, buffer, cur_len);
+
+ blk->blen -= cur_len;
+ blk->bdone = cur_len;
+ blk->processed = 1;
+
+ offset += cur_len;
+ if (is_iso) {
+ if (blk->status == USB_LAST_DATA) {
+ ++cur_iso;
+ offset = 0;
+ }
+ } else {
+ act_len -= cur_len;
+ }
+ }
+ } 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;
+ hci->hci_intr(hci, up_xfer->epid);
+ USB_DATA_XFER_UNLOCK(up_xfer->usb_xfer);
+ 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;
+ pthread_mutexattr_t attr;
+ enum libusb_speed speed;
+ int error;
+
+ hci = sc->hci;
+ /*
+ * The mutex must be recursive: a synchronous control transfer issued
+ * from usb_passthru_request() holds sc->mtx while
+ * libusb_control_transfer pumps the libusb event loop, which can
+ * complete a pending async transfer on the same thread. That completion
+ * callback re-enters the device via hci_intr() -> doorbell ->
+ * usb_passthru_data_handler(), which needs sc->mtx again. Without
+ * recursion this self-deadlocks.
+ */
+ pthread_mutexattr_init(&attr);
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
+ pthread_mutex_init(&sc->mtx, &attr);
+ pthread_mutexattr_destroy(&attr);
+
+ 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:
+ /* sc has not been inserted into the device list yet, so undo the
+ * host-side setup directly instead of calling usb_passthru_remove(). */
+ libusb_hotplug_deregister_callback(NULL, sc->cb);
+ usb_passthru_guest_detach_device_on_host(sc);
+ libusb_close(sc->handle);
+ 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;
+ case UREQ(UR_SET_FEATURE, UT_WRITE_DEVICE):
+ if (value == UF_DEVICE_REMOTE_WAKEUP) {
+ /*
+ * Deny remote-wakeup arming. We cannot emulate USB
+ * remote wakeup for a passthrough device: while the
+ * guest has the port suspended there is no in-flight
+ * libusb transfer to observe device activity, so the
+ * controller never sees the wakeup and the port stays
+ * in U3 forever. Refusing this request keeps the guest
+ * from selectively suspending a device it would then be
+ * unable to wake (e.g. a Bluetooth dongle going silent
+ * after its paired mouse switches profiles).
+ */
+ if (data)
+ USB_DATA_SET_ERRCODE(data, USB_STALL);
+ err = LIBUSB_ERROR_NOT_FOUND;
+ goto done_locked;
+ }
+ /* forward all other SET_FEATURE(device) requests */
+ break;
+ }
+
+ 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;
+
+ /*
+ * Hide the device's remote-wakeup capability from the guest by
+ * clearing bmAttributes D5 (byte 7 of the configuration
+ * descriptor). This is the clean counterpart to denying
+ * SET_FEATURE(DEVICE_REMOTE_WAKEUP) above: with the capability
+ * absent the guest never attempts to arm remote wakeup or
+ * selectively suspend the device, which we could not wake.
+ */
+ if (xfer->ureq->bRequest == UR_GET_DESCRIPTOR &&
+ (xfer->ureq->bmRequestType & UT_READ) &&
+ (value >> 8) == UDESC_CONFIG && err >= 8 &&
+ data->buf != NULL)
+ ((uint8_t *)data->buf)[7] &= ~UC_REMOTE_WAKEUP;
+ }
+ 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, cur = (cur + 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) {
+ DPRINTF(("usb_passthru data: skip submit ep 0x%x (handle %p "
+ "tr_softc %p)",
+ ep, (void *)sc->handle, 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);
+
+ /*
+ * Remember which blocks this transfer covers. More TRBs can be queued
+ * (growing xfer->ndata) before the completion callback runs, so the
+ * callback must scatter into exactly these blocks rather than the
+ * current xfer->ndata -- otherwise it walks past the frames it actually
+ * carried, reads out-of-bounds iso packet descriptors, and marks
+ * not-yet-transferred blocks done.
+ */
+ up_xfer->head = xfer->head;
+ up_xfer->ndata = xfer->ndata;
+
+ 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);
+ DPRINTF(("usb_passthru data: submit ep 0x%x type %d len %d -> %d (%s)",
+ ep, ep_type, len, err, libusb_error_name(err)));
+ if (err) {
+ libusb_free_transfer(up_xfer->lusb_xfer);
+ usb_passthru_xfer_free(up_xfer);
+ 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);

File Metadata

Mime Type
text/plain
Expires
Sun, Jul 5, 6:50 PM (16 h, 12 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
34720349
Default Alt Text
D52166.id.diff (58 KB)

Event Timeline