Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F161549694
D52166.id.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
58 KB
Referenced Files
None
Subscribers
None
D52166.id.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D52166: bhyve: implement single USB device passthrough support
Attached
Detach File
Event Timeline
Log In to Comment