Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F138826494
D52166.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
42 KB
Referenced Files
None
Subscribers
None
D52166.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
@@ -67,6 +67,7 @@
uart_emul.c \
usb_emul.c \
usb_mouse.c \
+ usb_passthru.c \
virtio.c \
vmexit.c \
vmgenc.c
@@ -95,7 +96,7 @@
-I${.CURDIR}/../../contrib/lib9p \
-I${SRCTOP}/sys
-LIBADD+= vmmapi md nv pthread z util sbuf cam 9p
+LIBADD+= vmmapi md nv pthread z util sbuf cam 9p usb
.if ${MK_BHYVE_SNAPSHOT} != "no"
LIBADD+= ucl xo
diff --git a/usr.sbin/bhyve/Makefile.depend b/usr.sbin/bhyve/Makefile.depend
--- a/usr.sbin/bhyve/Makefile.depend
+++ b/usr.sbin/bhyve/Makefile.depend
@@ -16,6 +16,7 @@
lib/libsbuf \
lib/libthr \
lib/libutil \
+ lib/libusb \
lib/libvmmapi \
lib/libz \
diff --git a/usr.sbin/bhyve/pci_xhci.c b/usr.sbin/bhyve/pci_xhci.c
--- a/usr.sbin/bhyve/pci_xhci.c
+++ b/usr.sbin/bhyve/pci_xhci.c
@@ -61,8 +61,7 @@
#endif
#include "usb_emul.h"
-
-static int xhci_debug = 0;
+static int xhci_debug = 1;
#define DPRINTF(params) if (xhci_debug) PRINTLN params
#define WPRINTF(params) PRINTLN params
@@ -85,7 +84,7 @@
#define XHCI_PORTREGS_START 0x400
#define XHCI_DOORBELL_MAX 256
-#define XHCI_STREAMS_MAX 1 /* 4-15 in XHCI spec */
+#define XHCI_STREAMS_MAX 65536 /* 4-15 in XHCI spec */
/* caplength and hci-version registers */
#define XHCI_SET_CAPLEN(x) ((x) & 0xFF)
@@ -406,7 +405,7 @@
* XHCI 4.19.3 USB2 RxDetect->Polling,
* USB3 Polling->U0
*/
- if (dev->hci.hci_usbver == 2)
+ if (dev->hci.hci_usbver <= 2)
port->portsc |=
XHCI_PS_PLS_SET(UPS_PORT_LS_POLL);
else
@@ -625,6 +624,7 @@
static void
pci_xhci_assert_interrupt(struct pci_xhci_softc *sc)
{
+ DPRINTF(("%s", __func__));
sc->rtsregs.intrreg.erdp |= XHCI_ERDP_LO_BUSY;
sc->rtsregs.intrreg.iman |= XHCI_IMAN_INTR_PEND;
@@ -661,6 +661,8 @@
devep = &dev->eps[epid];
pstreams = XHCI_EPCTX_0_MAXP_STREAMS_GET(ep_ctx->dwEpCtx0);
if (pstreams > 0) {
+ assert(pstreams <= 15);
+ pstreams = 1 << (pstreams + 1);
DPRINTF(("init_ep %d with pstreams %u", epid, pstreams));
assert(devep->ep_sctx_trbs == NULL);
@@ -689,6 +691,9 @@
devep->ep_xfer = malloc(sizeof(struct usb_data_xfer));
USB_DATA_XFER_INIT(devep->ep_xfer);
}
+ if (dev->dev_ue->ue_configure_ep)
+ (void)dev->dev_ue->ue_configure_ep(dev->dev_sc, epid, ep_ctx,
+ 1);
}
static void
@@ -726,6 +731,8 @@
free(devep->ep_xfer);
devep->ep_xfer = NULL;
}
+ if (dev->dev_ue->ue_configure_ep)
+ dev->dev_ue->ue_configure_ep(dev->dev_sc, epid, ep_ctx, 0);
memset(devep, 0, sizeof(struct pci_xhci_dev_ep));
}
@@ -1179,8 +1186,9 @@
uint32_t type;
epid = XHCI_TRB_3_EP_GET(trb->dwTrb3);
+ type = XHCI_TRB_3_TYPE_GET(trb->dwTrb3);
- DPRINTF(("pci_xhci: reset ep %u: slot %u", epid, slot));
+ DPRINTF(("pci_xhci: reset ep %u: slot %u: type %u", epid, slot, type));
cmderr = pci_xhci_validate_slot(slot);
if (cmderr != XHCI_TRB_ERROR_SUCCESS)
@@ -1189,8 +1197,6 @@
dev = XHCI_SLOTDEV_PTR(sc, slot);
assert(dev != NULL);
- type = XHCI_TRB_3_TYPE_GET(trb->dwTrb3);
-
if (type == XHCI_TRB_TYPE_STOP_EP &&
(trb->dwTrb3 & XHCI_TRB_3_SUSP_EP_BIT) != 0) {
/* XXX suspend endpoint for 10ms */
@@ -1203,8 +1209,6 @@
}
devep = &dev->eps[epid];
- if (devep->ep_xfer != NULL)
- USB_DATA_XFER_RESET(devep->ep_xfer);
dev_ctx = dev->dev_ctx;
assert(dev_ctx != NULL);
@@ -1229,6 +1233,9 @@
USB_DATA_XFER_UNLOCK(devep->ep_xfer);
}
+ if (devep->ep_xfer != NULL)
+ USB_DATA_XFER_RESET(devep->ep_xfer);
+
done:
return (cmderr);
}
@@ -1546,8 +1553,8 @@
evtrb.qwTrb0 = crcr;
evtrb.dwTrb2 |= XHCI_TRB_2_ERROR_SET(cmderr);
evtrb.dwTrb3 |= XHCI_TRB_3_SLOT_SET(slot);
- DPRINTF(("pci_xhci: command 0x%x result: 0x%x",
- type, cmderr));
+ DPRINTF(("pci_xhci: command 0x%x result: 0x%x", type,
+ cmderr));
pci_xhci_insert_event(sc, &evtrb, 1);
}
@@ -1601,25 +1608,19 @@
pci_xhci_xfer_complete(struct pci_xhci_softc *sc, struct usb_data_xfer *xfer,
uint32_t slot, uint32_t epid, int *do_intr)
{
- struct pci_xhci_dev_emu *dev;
- struct pci_xhci_dev_ep *devep;
- struct xhci_dev_ctx *dev_ctx;
- struct xhci_endp_ctx *ep_ctx;
+ struct xhci_dev_ctx *dev_ctx;
struct xhci_trb *trb;
struct xhci_trb evtrb;
uint32_t trbflags;
uint32_t edtla;
+ uint32_t remain = 0;
int i, err;
- dev = XHCI_SLOTDEV_PTR(sc, slot);
- devep = &dev->eps[epid];
dev_ctx = pci_xhci_get_dev_ctx(sc, slot);
if (dev_ctx == NULL) {
return XHCI_TRB_ERROR_PARAMETER;
}
- ep_ctx = &dev_ctx->ctx_ep[epid];
-
err = XHCI_TRB_ERROR_SUCCESS;
*do_intr = 0;
edtla = 0;
@@ -1639,17 +1640,24 @@
if (!xfer->data[i].processed) {
xfer->head = i;
+ err = XHCI_TRB_ERROR_INVALID;
break;
}
xfer->ndata--;
+ xfer->head = (xfer->head + 1) % USB_MAX_XFER_BLOCKS;
edtla += xfer->data[i].bdone;
trb->dwTrb3 = (trb->dwTrb3 & ~0x1) | (xfer->data[i].ccs);
- pci_xhci_update_ep_ring(sc, dev, devep, ep_ctx,
- xfer->data[i].streamid, xfer->data[i].trbnext,
- xfer->data[i].ccs);
+ remain += xfer->data[i].blen;
+
+ if (USB_DATA_GET_ERRCODE(&xfer->data[i]))
+ err = USB_TO_XHCI_ERR(
+ USB_DATA_GET_ERRCODE(&xfer->data[i]));
+
+ if (xfer->data[i].blen > 0)
+ err = XHCI_TRB_ERROR_SHORT_PKT;
/* Only interrupt if IOC or short packet */
if (!(trb->dwTrb3 & XHCI_TRB_3_IOC_BIT) &&
@@ -1661,7 +1669,7 @@
}
evtrb.dwTrb2 = XHCI_TRB_2_ERROR_SET(err) |
- XHCI_TRB_2_REM_SET(xfer->data[i].blen);
+ XHCI_TRB_2_REM_SET(remain);
evtrb.dwTrb3 = XHCI_TRB_3_TYPE_SET(XHCI_TRB_EVENT_TRANSFER) |
XHCI_TRB_3_SLOT_SET(slot) | XHCI_TRB_3_EP_SET(epid);
@@ -1681,6 +1689,7 @@
if (err != XHCI_TRB_ERROR_SUCCESS) {
break;
}
+ err = XHCI_TRB_ERROR_SUCCESS;
i = (i + 1) % USB_MAX_XFER_BLOCKS;
}
@@ -1746,7 +1755,6 @@
do_intr = 0;
xfer = devep->ep_xfer;
- USB_DATA_XFER_LOCK(xfer);
/* outstanding requests queued up */
if (dev->dev_ue->ue_data != NULL) {
@@ -1759,19 +1767,14 @@
} else {
err = pci_xhci_xfer_complete(sc, xfer, slot, epid,
&do_intr);
- if (err == XHCI_TRB_ERROR_SUCCESS && do_intr) {
- pci_xhci_assert_interrupt(sc);
+ if (err == XHCI_TRB_ERROR_SUCCESS) {
+ if (do_intr)
+ pci_xhci_assert_interrupt(sc);
+ USB_DATA_XFER_RESET(xfer);
}
-
-
- /* XXX should not do it if error? */
- USB_DATA_XFER_RESET(xfer);
}
}
- USB_DATA_XFER_UNLOCK(xfer);
-
-
return (err);
}
@@ -1785,6 +1788,7 @@
struct xhci_trb *setup_trb;
struct usb_data_xfer *xfer;
struct usb_data_xfer_block *xfer_block;
+ struct usb_data_xfer_block *prev_xfer_block = NULL;
uint64_t val;
uint32_t trbflags;
int do_intr, err;
@@ -1808,10 +1812,11 @@
pci_xhci_dump_trb(trb);
trbflags = trb->dwTrb3;
-
if (XHCI_TRB_3_TYPE_GET(trbflags) != XHCI_TRB_TYPE_LINK &&
+ XHCI_TRB_3_TYPE_GET(trbflags) !=
+ XHCI_TRB_TYPE_STATUS_STAGE &&
(trbflags & XHCI_TRB_3_CYCLE_BIT) !=
- (ccs & XHCI_TRB_3_CYCLE_BIT)) {
+ (ccs & XHCI_TRB_3_CYCLE_BIT)) {
DPRINTF(("Cycle-bit changed trbflags %x, ccs %x",
trbflags & XHCI_TRB_3_CYCLE_BIT, ccs));
break;
@@ -1823,6 +1828,7 @@
case XHCI_TRB_TYPE_LINK:
if (trb->dwTrb3 & XHCI_TRB_3_TC_BIT)
ccs ^= 0x1;
+ trb->dwTrb3 &= ~XHCI_TRB_3_IOC_BIT;
xfer_block = usb_data_xfer_append(xfer, NULL, 0,
(void *)addr, ccs);
@@ -1839,14 +1845,20 @@
setup_trb = trb;
val = trb->qwTrb0;
- if (!xfer->ureq)
+ if (xfer->ureq == NULL)
xfer->ureq = malloc(
sizeof(struct usb_device_request));
+ if (xfer->ureq == NULL) {
+ err = XHCI_TRB_ERROR_STALL;
+ goto errout;
+ }
+
memcpy(xfer->ureq, &val,
sizeof(struct usb_device_request));
xfer_block = usb_data_xfer_append(xfer, NULL, 0,
- (void *)addr, ccs);
+ (void *)addr, ccs);
+ xfer_block->status = USB_NO_DATA;
xfer_block->processed = 1;
break;
@@ -1865,24 +1877,42 @@
(void *)(trbflags & XHCI_TRB_3_IDT_BIT ?
&trb->qwTrb0 : XHCI_GADDR(sc, trb->qwTrb0)),
trb->dwTrb2 & 0x1FFFF, (void *)addr, ccs);
+ if (xfer_block == NULL) {
+ err = USB_ERR_STALLED;
+ break;
+ }
+ xfer_block->status = trbflags & XHCI_TRB_3_CHAIN_BIT ?
+ USB_NEXT_DATA :
+ USB_LAST_DATA;
+ prev_xfer_block = xfer_block;
break;
case XHCI_TRB_TYPE_STATUS_STAGE:
xfer_block = usb_data_xfer_append(xfer, NULL, 0,
(void *)addr, ccs);
+ setup_trb = NULL;
break;
case XHCI_TRB_TYPE_NOOP:
- xfer_block = usb_data_xfer_append(xfer, NULL, 0,
- (void *)addr, ccs);
+ if (!xfer_block) {
+ err = XHCI_TRB_ERROR_STALL;
+ goto errout;
+ }
xfer_block->processed = 1;
break;
case XHCI_TRB_TYPE_EVENT_DATA:
xfer_block = usb_data_xfer_append(xfer, NULL, 0,
(void *)addr, ccs);
- if ((epid > 1) && (trbflags & XHCI_TRB_3_IOC_BIT)) {
- xfer_block->processed = 1;
+ if (!xfer_block) {
+ err = XHCI_TRB_ERROR_TRB;
+ goto errout;
+ }
+ xfer_block->processed = 1;
+ if (prev_xfer_block != NULL &&
+ prev_xfer_block->status == USB_NEXT_DATA) {
+ prev_xfer_block->status = USB_LAST_DATA;
+ prev_xfer_block = NULL;
}
break;
@@ -1901,23 +1931,21 @@
if (xfer_block) {
xfer_block->trbnext = addr;
xfer_block->streamid = streamid;
+ pci_xhci_update_ep_ring(sc, dev, devep, ep_ctx,
+ streamid, addr, ccs);
}
+ if (trbflags & XHCI_TRB_3_BEI_BIT)
+ continue;
+
if (!setup_trb && !(trbflags & XHCI_TRB_3_CHAIN_BIT) &&
XHCI_TRB_3_TYPE_GET(trbflags) != XHCI_TRB_TYPE_LINK) {
break;
}
-
- /* handle current batch that requires interrupt on complete */
- if (trbflags & XHCI_TRB_3_IOC_BIT) {
- DPRINTF(("pci_xhci: trb IOC bit set"));
- if (epid == 1)
- do_retry = 1;
- break;
- }
}
- DPRINTF(("pci_xhci[%d]: xfer->ndata %u", __LINE__, xfer->ndata));
+ DPRINTF(
+ ("pci_xhci[%d]: xfer->ndata %u %d", __LINE__, xfer->ndata, epid));
if (xfer->ndata <= 0)
goto errout;
@@ -1952,13 +1980,16 @@
if (!do_retry)
USB_DATA_XFER_UNLOCK(xfer);
- if (do_intr)
+ if (do_intr) {
pci_xhci_assert_interrupt(sc);
+ do_intr = 0;
+ }
if (do_retry) {
USB_DATA_XFER_RESET(xfer);
DPRINTF(("pci_xhci[%d]: retry:continuing with next TRBs",
__LINE__));
+ do_retry = 0;
goto retry;
}
@@ -2013,8 +2044,10 @@
return;
/* handle pending transfers */
- if (devep->ep_xfer->ndata > 0) {
+ if (dev->dev_ue->ue_static && devep->ep_xfer->ndata > 0) {
+ USB_DATA_XFER_LOCK(devep->ep_xfer);
pci_xhci_try_usb_xfer(sc, dev, devep, ep_ctx, slot, epid);
+ USB_DATA_XFER_UNLOCK(devep->ep_xfer);
return;
}
@@ -2615,6 +2648,7 @@
port = XHCI_PORTREG_PTR(sc, portn);
dev = XHCI_DEVINST_PTR(sc, portn);
if (dev) {
+ dev->dev_ue->ue_reset(dev->dev_sc);
port->portsc &= ~(XHCI_PS_PLS_MASK | XHCI_PS_PR | XHCI_PS_PRC);
port->portsc |= XHCI_PS_PED |
XHCI_PS_SPEED_SET(dev->hci.hci_speed);
@@ -2649,7 +2683,7 @@
port->portsc = XHCI_PS_CCS | /* connected */
XHCI_PS_PP; /* port power */
- if (dev->hci.hci_usbver == 2) {
+ if (dev->hci.hci_usbver <= 2) {
port->portsc |= XHCI_PS_PLS_SET(UPS_PORT_LS_POLL) |
XHCI_PS_SPEED_SET(dev->hci.hci_speed);
} else {
@@ -2665,6 +2699,34 @@
}
}
+static void
+pci_xhci_deinit_port(struct pci_xhci_softc *sc, int portn)
+{
+ struct pci_xhci_portregs *port;
+ struct pci_xhci_dev_emu *dev;
+
+ port = XHCI_PORTREG_PTR(sc, portn);
+ dev = XHCI_DEVINST_PTR(sc, portn);
+ if (dev) {
+ port->portsc &= ~(XHCI_PS_CCS | /* connected */
+ XHCI_PS_PP); /* port power */
+
+ if (dev->hci.hci_usbver <= 2) {
+ port->portsc &= ~(XHCI_PS_PLS_SET(UPS_PORT_LS_POLL) |
+ XHCI_PS_SPEED_SET(dev->hci.hci_speed));
+ } else {
+ port->portsc &= ~(XHCI_PS_PLS_SET(UPS_PORT_LS_U0) |
+ XHCI_PS_PED | /* enabled */
+ XHCI_PS_SPEED_SET(dev->hci.hci_speed));
+ }
+
+ DPRINTF(("Deinit port %d 0x%x", portn, port->portsc));
+ } else {
+ port->portsc = XHCI_PS_PLS_SET(UPS_PORT_LS_RX_DET) | XHCI_PS_PP;
+ DPRINTF(("Deinit empty port %d 0x%x", portn, port->portsc));
+ }
+}
+
static int
pci_xhci_dev_intr(struct usb_hci *hci, int epctx)
{
@@ -2674,8 +2736,10 @@
struct pci_xhci_softc *sc;
struct pci_xhci_portregs *p;
struct xhci_endp_ctx *ep_ctx;
+ struct usb_data_xfer *xfer;
int error = 0;
int dir_in;
+ int do_intr;
int epid;
dir_in = epctx & 0x80;
@@ -2723,17 +2787,67 @@
DPRINTF(("xhci device interrupt on endpoint %d", epid));
- pci_xhci_device_doorbell(sc, hci->hci_port, epid, 0);
+ /*
+ * When the device is dynamic, we need to complete the previous TD then
+ * assert all interrupt from previous TD
+ */
+ if (!dev->dev_ue->ue_static) {
+ xfer = dev->eps[epid].ep_xfer;
+ error = pci_xhci_xfer_complete(sc, xfer, hci->hci_slot, epid,
+ &do_intr);
+ if (error == XHCI_TRB_ERROR_SUCCESS) {
+ if (do_intr)
+ pci_xhci_assert_interrupt(sc);
+ USB_DATA_XFER_RESET(xfer);
+ }
+ }
+ pci_xhci_device_doorbell(sc, hci->hci_slot, epid, 0);
done:
return (error);
}
static int
-pci_xhci_dev_event(struct usb_hci *hci, enum hci_usbev evid __unused,
+pci_xhci_dev_event(struct usb_hci *hci, enum hci_usbev evid,
void *param __unused)
{
+ struct xhci_trb evtrb;
+ struct pci_xhci_dev_emu *dev = hci->hci_sc;
+ struct pci_xhci_softc *xsc = dev->xsc;
+ struct pci_xhci_portregs *port;
+ int err;
+
+ port = XHCI_PORTREG_PTR(xsc, hci->hci_port);
DPRINTF(("xhci device event port %d", hci->hci_port));
+
+ switch (evid) {
+ case USBDEV_ATTACH:
+ pci_xhci_init_port(xsc, hci->hci_port);
+ port->portsc |= XHCI_PS_CSC;
+ pci_xhci_set_evtrb(&evtrb, hci->hci_port,
+ XHCI_TRB_ERROR_SUCCESS, XHCI_TRB_EVENT_PORT_STS_CHANGE);
+ if ((err = pci_xhci_insert_event(xsc, &evtrb, 1)) !=
+ XHCI_TRB_ERROR_SUCCESS)
+ return (err);
+ pci_xhci_assert_interrupt(xsc);
+ xsc->opregs.usbsts |= XHCI_STS_PCD;
+ return (0);
+ break;
+ case USBDEV_REMOVE:
+ pci_xhci_deinit_port(xsc, hci->hci_port);
+ port->portsc |= XHCI_PS_CSC;
+ pci_xhci_set_evtrb(&evtrb, hci->hci_port,
+ XHCI_TRB_ERROR_SUCCESS, XHCI_TRB_EVENT_PORT_STS_CHANGE);
+ if ((err = pci_xhci_insert_event(xsc, &evtrb, 1)) !=
+ XHCI_TRB_ERROR_SUCCESS)
+ return (err);
+ xsc->opregs.usbsts |= XHCI_STS_PCD;
+ pci_xhci_assert_interrupt(xsc);
+ return (0);
+ break;
+ default:
+ break;
+ }
return (0);
}
@@ -2755,7 +2869,7 @@
{
char node_name[16];
nvlist_t *slots_nvl, *slot_nvl;
- char *cp, *opt, *str, *tofree;
+ char *cp, *opt, *str, *tofree, *subopt;
int slot;
if (opts == NULL)
@@ -2775,7 +2889,16 @@
snprintf(node_name, sizeof(node_name), "%d", slot);
slot++;
slot_nvl = create_relative_config_node(slots_nvl, node_name);
- set_config_value_node(slot_nvl, "device", opt);
+ subopt = strsep(&opt, ".");
+ set_config_value_node(slot_nvl, "device", subopt);
+ subopt = strsep(&opt, ".");
+ if (subopt == NULL)
+ continue;
+ set_config_value_node(slot_nvl, "param1", subopt);
+ subopt = strsep(&opt, ".");
+ if (subopt == NULL)
+ continue;
+ set_config_value_node(slot_nvl, "param2", subopt);
/*
* NB: Given that we split on commas above, the legacy
@@ -2803,8 +2926,10 @@
usb3_port = sc->usb3_port_start;
usb2_port = sc->usb2_port_start;
- sc->devices = calloc(XHCI_MAX_DEVS, sizeof(struct pci_xhci_dev_emu *));
- sc->slots = calloc(XHCI_MAX_SLOTS, sizeof(struct pci_xhci_dev_emu *));
+ sc->devices = calloc(XHCI_MAX_DEVS + 1,
+ sizeof(struct pci_xhci_dev_emu *));
+ sc->slots = calloc(XHCI_MAX_SLOTS + 1,
+ sizeof(struct pci_xhci_dev_emu *));
ndevices = 0;
@@ -2863,9 +2988,10 @@
dev->hci.hci_intr = pci_xhci_dev_intr;
dev->hci.hci_event = pci_xhci_dev_event;
dev->hci.hci_speed = USB_SPEED_MAX;
+ dev->hci.hci_slot = slot;
dev->hci.hci_usbver = -1;
- devsc = ue->ue_probe(&dev->hci, nvl);
+ devsc = ue->ue_probe(&dev->hci, slot_nvl);
if (devsc == NULL) {
free(dev);
goto bad;
@@ -2875,7 +3001,7 @@
if (dev->hci.hci_usbver == -1)
dev->hci.hci_usbver = ue->ue_usbver;
- if (dev->hci.hci_usbver == 2) {
+ if (dev->hci.hci_usbver <= 2) {
if (usb2_port == sc->usb2_port_start +
XHCI_MAX_DEVS / 2) {
WPRINTF(("pci_xhci max number of USB 2 devices "
@@ -2913,7 +3039,8 @@
}
portsfinal:
- sc->portregs = calloc(XHCI_MAX_DEVS, sizeof(struct pci_xhci_portregs));
+ sc->portregs = calloc(XHCI_MAX_DEVS + 1,
+ sizeof(struct pci_xhci_portregs));
if (ndevices > 0) {
for (i = 1; i <= XHCI_MAX_DEVS; i++) {
@@ -2926,8 +3053,11 @@
bad:
for (i = 1; i <= XHCI_MAX_DEVS; i++) {
- if (XHCI_DEVINST_PTR(sc, i) != NULL)
+ if (XHCI_DEVINST_PTR(sc, i) != NULL) {
+ XHCI_DEVINST_PTR(sc, i)->dev_ue->ue_remove(
+ XHCI_DEVINST_PTR(sc, i)->dev_sc);
free(XHCI_DEVINST_PTR(sc, i)->dev_sc);
+ }
free(XHCI_DEVINST_PTR(sc, i));
}
@@ -2975,8 +3105,7 @@
XHCI_SET_HCCP1_NSS(1) | /* no 2nd-streams */
XHCI_SET_HCCP1_SPC(1) | /* short packet */
XHCI_SET_HCCP1_MAXPSA(XHCI_STREAMS_MAX);
- sc->hccparams2 = XHCI_SET_HCCP2_LEC(1) |
- XHCI_SET_HCCP2_U3C(1);
+ sc->hccparams2 = XHCI_SET_HCCP2_LEC(1) | XHCI_SET_HCCP2_U3C(1);
sc->dboff = XHCI_SET_DOORBELL(XHCI_CAPLEN + XHCI_PORTREGS_START +
XHCI_MAX_DEVS * sizeof(struct pci_xhci_portregs));
@@ -3275,6 +3404,7 @@
SNAPSHOT_VAR_OR_LEAVE(dev->hci.hci_address, meta, ret, done);
SNAPSHOT_VAR_OR_LEAVE(dev->hci.hci_port, meta, ret, done);
SNAPSHOT_VAR_OR_LEAVE(dev->hci.hci_speed, meta, ret, done);
+ SNAPSHOT_VAR_OR_LEAVE(dev->hci.hci_slot, meta, ret, done);
SNAPSHOT_VAR_OR_LEAVE(dev->hci.hci_usbver, meta, ret, done);
}
diff --git a/usr.sbin/bhyve/usb_emul.h b/usr.sbin/bhyve/usb_emul.h
--- a/usr.sbin/bhyve/usb_emul.h
+++ b/usr.sbin/bhyve/usb_emul.h
@@ -34,7 +34,7 @@
#include <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;
};
diff --git a/usr.sbin/bhyve/usb_emul.c b/usr.sbin/bhyve/usb_emul.c
--- a/usr.sbin/bhyve/usb_emul.c
+++ b/usr.sbin/bhyve/usb_emul.c
@@ -69,6 +69,7 @@
xb->ccs = ccs;
xb->processed = 0;
xb->bdone = 0;
+ xb->status = USB_NO_DATA;
xfer->ndata++;
xfer->tail = (xfer->tail + 1) % USB_MAX_XFER_BLOCKS;
return (xb);
diff --git a/usr.sbin/bhyve/usb_mouse.c b/usr.sbin/bhyve/usb_mouse.c
--- a/usr.sbin/bhyve/usb_mouse.c
+++ b/usr.sbin/bhyve/usb_mouse.c
@@ -295,7 +295,7 @@
}
static void *
-umouse_probe(struct usb_hci *hci, nvlist_t *nvl __unused)
+umouse_probe(struct usb_hci *hci, const nvlist_t *nvl __unused)
{
struct umouse_softc *sc;
@@ -816,18 +816,19 @@
#endif
static struct usb_devemu ue_mouse = {
- .ue_emu = "tablet",
- .ue_usbver = 3,
- .ue_usbspeed = USB_SPEED_HIGH,
- .ue_probe = umouse_probe,
- .ue_init = umouse_init,
- .ue_request = umouse_request,
- .ue_data = umouse_data_handler,
- .ue_reset = umouse_reset,
- .ue_remove = umouse_remove,
- .ue_stop = umouse_stop,
+ .ue_emu = "tablet",
+ .ue_static = 1,
+ .ue_usbver = 3,
+ .ue_usbspeed = USB_SPEED_HIGH,
+ .ue_probe = umouse_probe,
+ .ue_init = umouse_init,
+ .ue_request = umouse_request,
+ .ue_data = umouse_data_handler,
+ .ue_reset = umouse_reset,
+ .ue_remove = umouse_remove,
+ .ue_stop = umouse_stop,
#ifdef BHYVE_SNAPSHOT
- .ue_snapshot = umouse_snapshot,
+ .ue_snapshot = umouse_snapshot,
#endif
};
USB_EMUL_SET(ue_mouse);
diff --git a/usr.sbin/bhyve/usb_passthru.c b/usr.sbin/bhyve/usb_passthru.c
new file mode 100644
--- /dev/null
+++ b/usr.sbin/bhyve/usb_passthru.c
@@ -0,0 +1,871 @@
+#include <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 "config.h"
+#include "debug.h"
+#include "pci_xhci.h"
+#include "usb_emul.h"
+
+static int usb_passthru_debug = 1;
+static bool libusb_is_init = false;
+#define DPRINTF(params) if (usb_passthru_debug) PRINTLN params
+#define WPRINTF(params) PRINTLN params
+
+#define OUT 0
+#define IN 1
+
+struct usb_passthru_libusb_xfer;
+
+struct usb_passthru_ep_types {
+ bool inout;
+ int type;
+};
+
+struct usb_passthru_softc {
+ struct usb_hci *hci; /* hci structure for issue xhci interrupt */
+ pthread_mutex_t mtx; /* mutex for locking */
+ unsigned long vid; /* product vid */
+ unsigned long pid; /* product pid */
+ struct libusb_device_handle *handle; /* libusb handler */
+ libusb_hotplug_callback_handle cb; /* callback handler */
+ struct usb_passthru_ep_types endpoint_types[32];
+ LIST_ENTRY(usb_passthru_softc) next;
+};
+
+struct usb_passthru_libusb_xfer {
+ struct libusb_transfer *lusb_xfer;
+ struct usb_data_xfer *usb_xfer;
+ struct usb_passthru_softc *sc;
+ uint8_t *buffer;
+ bool in;
+ int epid;
+ int size;
+};
+
+static LIST_HEAD(, usb_passthru_softc) devices = LIST_HEAD_INITIALIZER(
+ &devices);
+
+static int usb_passthru_remove(void *scarg);
+static pthread_t libusb_thread = NULL;
+static int event_thread_exit = false;
+
+static int
+libusb_error_to_usb_error(enum libusb_error error)
+{
+ switch (error) {
+ case LIBUSB_SUCCESS:
+ return (USB_ERR_NORMAL_COMPLETION);
+ case LIBUSB_ERROR_IO:
+ return (USB_ERR_IOERROR);
+ case LIBUSB_ERROR_INVALID_PARAM:
+ return (USB_ERR_INVAL);
+ case LIBUSB_ERROR_ACCESS:
+ return (USB_ERR_INVAL);
+ case LIBUSB_ERROR_NO_DEVICE:
+ return (USB_ERR_NOT_CONFIGURED);
+ case LIBUSB_ERROR_NOT_FOUND:
+ return (USB_ERR_STALLED);
+ case LIBUSB_ERROR_BUSY:
+ return (USB_ERR_IN_USE);
+ case LIBUSB_ERROR_TIMEOUT:
+ return (USB_ERR_TIMEOUT);
+ case LIBUSB_ERROR_OVERFLOW:
+ return (USB_ERR_BAD_BUFSIZE);
+ case LIBUSB_ERROR_PIPE:
+ return (USB_ERR_NO_PIPE);
+ case LIBUSB_ERROR_INTERRUPTED:
+ return (USB_ERR_INTERRUPTED);
+ case LIBUSB_ERROR_NO_MEM:
+ return (USB_ERR_NOMEM);
+ case LIBUSB_ERROR_NOT_SUPPORTED:
+ return (USB_ERR_INVAL);
+ case LIBUSB_ERROR_OTHER:
+ return (USB_ERR_INVAL);
+ }
+}
+
+static void *
+libusb_pull_thread(void *arg __unused)
+{
+ struct timeval tv;
+ int err;
+
+ DPRINTF(("start libusb pullthread"));
+ while (!event_thread_exit) {
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+ err = libusb_handle_events_timeout(NULL, &tv);
+ if (err && err != LIBUSB_ERROR_TIMEOUT)
+ break;
+ }
+
+ DPRINTF(("stop libusb pullthread"));
+ return (NULL);
+}
+
+static void
+usb_passthru_calculate_xfer_ptr(struct usb_data_xfer *xfer, int *head,
+ int *tail, int *size)
+{
+ int cur, i;
+ int first = -1, length = 0;
+ struct usb_data_xfer_block *blk;
+
+ for (i = 0, cur = xfer->head; i < xfer->ndata;
+ ++i, cur = (cur + 1) % USB_MAX_XFER_BLOCKS) {
+ blk = &xfer->data[cur];
+ DPRINTF(("%s processed: %d len: %d, status: %d", __func__,
+ blk->processed, blk->blen, blk->status));
+ if (blk->processed)
+ continue;
+ switch (blk->status) {
+ case USB_NEXT_DATA:
+ case USB_LAST_DATA:
+ first = first == -1 ? cur : first;
+ length += blk->blen;
+ break;
+ case USB_NO_DATA:
+ blk->processed = 1;
+ continue;
+ }
+
+ if (blk->status == USB_LAST_DATA)
+ break;
+ }
+ *head = first;
+ *tail = i;
+ *size = length;
+}
+
+static void
+usb_passthru_data_calculate_num_isos(struct usb_data_xfer *xfer, int *head,
+ int *nframe, int *len)
+{
+ int i, cur, first = -1, nf = 0, length = 0;
+
+ for (i = 0, cur = xfer->head; i < xfer->ndata;
+ i = (i + 1) % USB_MAX_XFER_BLOCKS) {
+ if (xfer->data[i].processed)
+ continue;
+ switch (xfer->data[i].status) {
+ case USB_LAST_DATA:
+ ++nf;
+ if (first == -1)
+ first = i;
+ /* Fallthrough */
+ case USB_NEXT_DATA:
+ length += xfer->data[i].blen;
+ break;
+ default:
+ break;
+ }
+ }
+
+ *nframe = nf;
+ *head = first;
+ *len = length;
+}
+
+static struct usb_passthru_libusb_xfer *
+usb_passthru_xfer_alloc(struct usb_passthru_softc *sc, int in,
+ struct usb_data_xfer *usb_xfer, int size, int ep, int iso)
+{
+ struct usb_passthru_libusb_xfer *xfer = calloc(1,
+ sizeof(struct usb_passthru_libusb_xfer));
+ xfer->lusb_xfer = libusb_alloc_transfer(iso);
+ xfer->usb_xfer = usb_xfer;
+ xfer->buffer = calloc(1, size);
+ xfer->size = size;
+ xfer->in = in;
+ xfer->sc = sc;
+ xfer->epid = ep;
+ return (xfer);
+}
+
+static void
+usb_passthru_xfer_free(struct usb_passthru_libusb_xfer *xfer)
+{
+ free(xfer);
+}
+
+static int
+usb_passthru_guest_attach_device(struct usb_passthru_softc *sc)
+{
+ struct libusb_config_descriptor *desc;
+ struct libusb_device *dev;
+ struct libusb_device_handle *handle;
+ int intf, res;
+
+ handle = sc->handle;
+ dev = libusb_get_device(handle);
+ res = libusb_get_active_config_descriptor(dev, &desc);
+ if (res != LIBUSB_SUCCESS)
+ goto done;
+
+ for (intf = 0; intf < desc->bNumInterfaces; ++intf) {
+ res = libusb_detach_kernel_driver(handle, intf);
+ if (res != LIBUSB_SUCCESS)
+ break;
+ res = libusb_claim_interface(handle, intf);
+ if (res != LIBUSB_SUCCESS)
+ break;
+ }
+
+done:
+ libusb_free_config_descriptor(desc);
+ return (res);
+}
+
+static int
+usb_passthru_guest_detach_device_on_host(struct usb_passthru_softc *sc)
+{
+ struct libusb_config_descriptor *desc;
+ struct libusb_device *dev;
+ struct libusb_device_handle *handle;
+ int intf, res;
+
+ handle = sc->handle;
+ if (handle == NULL)
+ return (libusb_error_to_usb_error(LIBUSB_SUCCESS));
+ dev = libusb_get_device(handle);
+
+ res = libusb_get_active_config_descriptor(dev, &desc);
+ if (res != LIBUSB_SUCCESS)
+ return (libusb_error_to_usb_error(res));
+
+ for (intf = 0; intf < desc->bNumInterfaces; ++intf) {
+ res = libusb_release_interface(handle, intf);
+ if (res != LIBUSB_SUCCESS)
+ break;
+ res = libusb_attach_kernel_driver(handle, intf);
+ if (res != LIBUSB_SUCCESS)
+ break;
+ }
+
+ libusb_free_config_descriptor(desc);
+
+ return (libusb_error_to_usb_error(res));
+}
+
+static int
+usb_passthru_guest_detach_device(struct usb_passthru_softc *sc __unused)
+{
+ return (USB_ERR_NORMAL_COMPLETION);
+}
+
+static int
+usb_passthru_hotplug_callback(struct libusb_context *ctx __unused,
+ struct libusb_device *dev, libusb_hotplug_event event, void *user_data)
+{
+ struct usb_passthru_softc *sc = user_data;
+ int err = 0;
+
+ DPRINTF(("%s: enter hotplug handler event: %d", __func__, event));
+
+ pthread_mutex_lock(&sc->mtx);
+
+ if (event == LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT) {
+ assert(sc->hci->hci_event(sc->hci, USBDEV_REMOVE, sc) == 0);
+ libusb_close(sc->handle);
+ sc->handle = NULL;
+ } else if (event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED) {
+ if (sc->handle != NULL)
+ goto done;
+ err = libusb_open(dev, &sc->handle);
+ if (err)
+ goto done;
+ if ((err = usb_passthru_guest_attach_device(sc)) !=
+ LIBUSB_SUCCESS)
+ goto done;
+ assert(sc->hci->hci_event(sc->hci, USBDEV_ATTACH, sc) == 0);
+ }
+
+done:
+ pthread_mutex_unlock(&sc->mtx);
+
+ return (err);
+}
+
+static void
+usb_passthru_data_callback(struct libusb_transfer *lusb_xfer)
+{
+ struct usb_passthru_libusb_xfer *up_xfer = lusb_xfer->user_data;
+ struct usb_data_xfer *uxfer = up_xfer->usb_xfer;
+ struct usb_passthru_softc *sc = up_xfer->sc;
+ struct usb_hci *hci = sc->hci;
+ int act_len = lusb_xfer->actual_length;
+ int head, tail, size, cur_len, cur, offset, nframe;
+ char *buffer;
+ int cur_iso = 0, i;
+
+ /*
+ * uxfer == NULL means the guest is cancelled by guest
+ * like machine shutdown.
+ */
+ if (lusb_xfer->status == LIBUSB_TRANSFER_CANCELLED && uxfer == NULL) {
+ usb_passthru_xfer_free(up_xfer);
+ return;
+ }
+
+ USB_DATA_XFER_LOCK(uxfer);
+
+ if (lusb_xfer->type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS)
+ usb_passthru_data_calculate_num_isos(uxfer, &head, &nframe,
+ &size);
+ else
+ usb_passthru_calculate_xfer_ptr(uxfer, &head, &tail, &size);
+
+ if (lusb_xfer->status == LIBUSB_TRANSFER_STALL ||
+ lusb_xfer->status == LIBUSB_TRANSFER_NO_DEVICE ||
+ lusb_xfer->status == LIBUSB_TRANSFER_CANCELLED) {
+ USB_DATA_SET_ERRCODE(&uxfer->data[head], USB_STALL);
+ goto done;
+ }
+
+ DPRINTF(("%s: act_len: %d blen:%d in:%d epid:%d status: %d", __func__,
+ act_len, size, up_xfer->in, up_xfer->epid, lusb_xfer->status));
+
+ if (up_xfer->in) {
+ if (lusb_xfer->type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS)
+ act_len = lusb_xfer->iso_packet_desc[0].actual_length;
+
+ if (act_len > size)
+ act_len = size;
+
+ for (cur = head, offset = 0, i = 0; i < uxfer->ndata;
+ cur = (cur + 1) % USB_MAX_XFER_BLOCKS, ++i) {
+ cur_len = uxfer->data[cur].blen;
+ if (cur_len > act_len) {
+ cur_len = act_len;
+ USB_DATA_SET_ERRCODE(&uxfer->data[cur],
+ USB_SHORT);
+ }
+ if (lusb_xfer->type != LIBUSB_TRANSFER_TYPE_ISOCHRONOUS)
+ buffer = &lusb_xfer->buffer[offset];
+ else
+ buffer = libusb_get_iso_packet_buffer_simple(
+ lusb_xfer, cur_iso) +
+ offset;
+ if (cur_len)
+ memcpy(uxfer->data[cur].buf, buffer, cur_len);
+ act_len -= cur_len;
+ offset += cur_len;
+ if (act_len <= 0 &&
+ lusb_xfer->type ==
+ LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) {
+ ++cur_iso;
+ act_len = lusb_xfer->iso_packet_desc[cur_iso]
+ .actual_length;
+ offset = 0;
+ }
+ uxfer->data[cur].blen -= cur_len;
+ uxfer->data[cur].bdone = cur_len;
+ uxfer->data[cur].processed = 1;
+ }
+ } else {
+ for (cur = head; uxfer->data[cur].buf && act_len;
+ cur = (cur + 1) % USB_MAX_XFER_BLOCKS) {
+ uxfer->data[cur].processed = 1;
+ cur_len = act_len;
+ if (cur_len > uxfer->data[cur].blen)
+ cur_len = uxfer->data[cur].blen;
+ act_len -= cur_len;
+ uxfer->data[cur].blen -= cur_len;
+ uxfer->data[cur].bdone += cur_len;
+ }
+
+ USB_DATA_SET_ERRCODE(&uxfer->data[head],
+ lusb_xfer->status == LIBUSB_TRANSFER_COMPLETED ?
+ USB_ERR_NORMAL_COMPLETION :
+ USB_ERR_IOERROR);
+ }
+
+done:
+ uxfer->tr_softc = NULL;
+ USB_DATA_XFER_UNLOCK(up_xfer->usb_xfer);
+ hci->hci_intr(hci, up_xfer->epid);
+ usb_passthru_xfer_free(up_xfer);
+}
+
+static void
+usb_passthru_exit(void)
+{
+ struct usb_passthru_softc *sc, *tmp;
+
+ LIST_FOREACH_SAFE(sc, &devices, next, tmp) {
+ usb_passthru_remove(sc);
+ free(sc);
+ }
+
+ event_thread_exit = true;
+}
+
+static void *
+usb_passthru_probe(struct usb_hci *hci, const nvlist_t *nvl)
+{
+ struct usb_passthru_softc *sc;
+ struct libusb_device_descriptor dev_desc;
+ struct libusb_device *dev;
+ struct libusb_init_option opts[] = {
+ { .option = LIBUSB_OPTION_CAPSICUMIZE }
+ };
+ const char *param;
+ char *endp;
+ int error;
+
+ if (!libusb_is_init) {
+ libusb_is_init = true;
+ error = libusb_init_context(NULL, opts,
+ sizeof(opts) / sizeof(struct libusb_init_option));
+ if (error) {
+ EPRINTLN("failed to init libusb: %s",
+ libusb_error_name(error));
+ return (NULL);
+ }
+ }
+ sc = calloc(1, sizeof(struct usb_passthru_softc));
+
+ param = get_config_value_node(nvl, "param1");
+ if (param == NULL) {
+ free(sc);
+ return (NULL);
+ }
+ sc->vid = strtoul(param, &endp, 16);
+ if (*endp != '\0') {
+ free(sc);
+ return (NULL);
+ }
+ param = get_config_value_node(nvl, "param2");
+ if (param == NULL) {
+ free(sc);
+ return (NULL);
+ }
+ sc->pid = strtoul(param, &endp, 16);
+ if (*endp != '\0') {
+ free(sc);
+ return (NULL);
+ }
+
+ sc->hci = hci;
+ sc->handle = libusb_open_device_with_vid_pid(NULL, sc->vid, sc->pid);
+ if (sc->handle == NULL) {
+ EPRINTLN("failed to open usb device with vid: %0lx pid: %0lx",
+ sc->vid, sc->pid);
+ free(sc);
+ return (NULL);
+ }
+ dev = libusb_get_device(sc->handle);
+ if (libusb_get_device_descriptor(dev, &dev_desc) != LIBUSB_SUCCESS) {
+ EPRINTLN("failed to get usb device descriptor with %0lx %0lx",
+ sc->vid, sc->pid);
+ free(sc);
+ return (NULL);
+ }
+ if (dev_desc.bcdUSB >= 0x0300) {
+ hci->hci_usbver = 3;
+ } else {
+ hci->hci_usbver = 2;
+ }
+ memset(sc->endpoint_types, -1, sizeof(sc->endpoint_types));
+
+ return (sc);
+}
+
+static int
+usb_passthru_init(void *scarg)
+{
+ struct usb_passthru_softc *sc = (struct usb_passthru_softc *)scarg;
+ struct usb_hci *hci;
+ enum libusb_speed speed;
+ int error;
+
+ hci = sc->hci;
+ pthread_mutex_init(&sc->mtx, NULL);
+
+ speed = libusb_get_device_speed(libusb_get_device(sc->handle));
+ switch (speed) {
+ case LIBUSB_SPEED_LOW:
+ hci->hci_speed = 2;
+ break;
+ case LIBUSB_SPEED_FULL:
+ hci->hci_speed = 1;
+ break;
+ case LIBUSB_SPEED_HIGH:
+ hci->hci_speed = 3;
+ break;
+ default:
+ break;
+ }
+
+ if ((error = usb_passthru_guest_attach_device(sc)) != LIBUSB_SUCCESS)
+ goto failed;
+
+ error = libusb_hotplug_register_callback(NULL,
+ LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED |
+ LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT,
+ 0, sc->vid, sc->pid, LIBUSB_HOTPLUG_MATCH_ANY,
+ usb_passthru_hotplug_callback, (void *)sc, &sc->cb);
+ if (error != LIBUSB_SUCCESS) {
+ EPRINTLN("failed to create callback for usb device %s",
+ libusb_error_name(error));
+ goto failed;
+ }
+
+ if (libusb_thread == NULL) {
+ error = pthread_create(&libusb_thread, NULL, libusb_pull_thread,
+ NULL);
+ if (error)
+ goto failed;
+ }
+
+ LIST_INSERT_HEAD(&devices, sc, next);
+ atexit(usb_passthru_exit);
+
+ return (error);
+
+failed:
+ usb_passthru_remove(&sc);
+ pthread_mutex_destroy(&sc->mtx);
+ free(sc);
+ return (1);
+}
+
+#define UREQ(x,y) ((x) | ((y) << 8))
+
+static int
+usb_passthru_request(void *scarg, struct usb_data_xfer *xfer)
+{
+ struct usb_passthru_softc *sc = scarg;
+ struct usb_data_xfer_block *data;
+ int i, cur, err, value, index, len;
+
+ data = NULL;
+ cur = xfer->head;
+ err = USB_ERR_NORMAL_COMPLETION;
+
+ for (i = 0; i < xfer->ndata; i++) {
+ xfer->data[cur].bdone = 0;
+ if (data == NULL && USB_DATA_OK(xfer, i)) {
+ data = &xfer->data[cur];
+ }
+ xfer->data[cur].processed = 1;
+ cur = (cur + 1) % USB_MAX_XFER_BLOCKS;
+ }
+
+ if (!xfer->ureq) {
+ DPRINTF(("usb_passthru_request not found: port %d",
+ sc->hci->hci_port));
+ goto done;
+ }
+
+ value = UGETW(xfer->ureq->wValue);
+ index = UGETW(xfer->ureq->wIndex);
+ len = UGETW(xfer->ureq->wLength);
+ if (data == NULL && len != 0) {
+ err = LIBUSB_ERROR_NO_DEVICE;
+ goto done;
+ }
+
+ pthread_mutex_lock(&sc->mtx);
+
+ if (sc->handle == NULL) {
+ err = LIBUSB_ERROR_INVALID_PARAM;
+ goto done_locked;
+ }
+
+ switch (UREQ(xfer->ureq->bRequest, xfer->ureq->bmRequestType)) {
+ case UREQ(UR_SET_ADDRESS, UT_WRITE_DEVICE):
+ err = 0;
+ goto done_locked;
+ case UREQ(UR_SET_CONFIG, UT_WRITE_DEVICE):
+ err = usb_passthru_guest_detach_device_on_host(sc);
+ if (err)
+ goto done_locked;
+ err = libusb_set_configuration(sc->handle, value & 0xff);
+ if (err)
+ goto done_locked;
+ err = usb_passthru_guest_attach_device(sc);
+ if (err)
+ goto done_locked;
+ goto done_locked;
+ case UREQ(UR_SET_INTERFACE, UT_WRITE_INTERFACE):
+ err = libusb_set_interface_alt_setting(sc->handle, index,
+ value);
+ goto done_locked;
+ case UREQ(UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT):
+ err = libusb_clear_halt(sc->handle, index);
+ goto done_locked;
+ }
+
+ if (data) {
+ data->blen = len;
+ data->bdone = 0;
+ }
+ DPRINTF((
+ "usb_passthru_request: bRequest: %x bmRequestType: %x wValue: %x wIndex: %d wLength: %x",
+ xfer->ureq->bRequest, xfer->ureq->bmRequestType, value, index,
+ len));
+
+ err = libusb_control_transfer(sc->handle, xfer->ureq->bmRequestType,
+ xfer->ureq->bRequest, UGETW(xfer->ureq->wValue),
+ UGETW(xfer->ureq->wIndex), data ? data->buf : NULL, len, 1000);
+
+ if (err < 0) {
+ if (err == LIBUSB_ERROR_INTERRUPTED ||
+ err == LIBUSB_ERROR_PIPE) {
+ if (data)
+ USB_DATA_SET_ERRCODE(data, USB_STALL);
+ err = LIBUSB_ERROR_NOT_FOUND;
+ } else {
+ if (data)
+ USB_DATA_SET_ERRCODE(data, USB_ERR);
+ err = LIBUSB_ERROR_NOT_FOUND;
+ }
+ goto done_locked;
+ }
+ if (data) {
+ data->blen -= err;
+ data->bdone = err;
+ }
+ err = 0;
+
+done_locked:
+ pthread_mutex_unlock(&sc->mtx);
+done:
+ err = libusb_error_to_usb_error(err);
+
+ if (xfer->ureq && (xfer->ureq->bmRequestType & UT_WRITE) &&
+ (err == USB_ERR_NORMAL_COMPLETION) && (data != NULL))
+ data->blen = 0;
+
+ DPRINTF(("usb_passthru request error code %d (0=ok), blen %u txlen %u",
+ err, (data ? data->blen : 0), (data ? data->bdone : 0)));
+
+ return (err);
+}
+
+static void
+usb_passthru_data_fill_xfer_iso(struct usb_data_xfer *xfer,
+ struct libusb_transfer *lusb_xfer)
+{
+ int i, cur, idx;
+
+ for (i = 0, cur = xfer->head, idx = 0; i < xfer->ndata;
+ i = (i + 1) % USB_MAX_XFER_BLOCKS) {
+ if (xfer->data[cur].status == USB_LAST_DATA) {
+ lusb_xfer->iso_packet_desc[idx].length +=
+ xfer->data[cur].blen;
+ } else if (xfer->data[cur].status == USB_NEXT_DATA) {
+ lusb_xfer->iso_packet_desc[idx].length +=
+ xfer->data[cur].blen;
+ continue;
+ } else {
+ continue;
+ }
+ ++idx;
+ }
+}
+
+static int
+usb_passthru_data_handler(void *scarg, struct usb_data_xfer *xfer, int dir,
+ int epctx)
+{
+ struct usb_passthru_softc *sc = scarg;
+ struct usb_passthru_libusb_xfer *up_xfer;
+ int err, cur, len, ep, head, tail, offset, ep_type, nframe;
+
+ ep_type = sc->endpoint_types[epctx << 1 | dir].type;
+ dir = sc->endpoint_types[epctx << 1 | dir].inout;
+ assert(ep_type != -1);
+
+ err = USB_ERR_NORMAL_COMPLETION;
+ ep = (dir ? LIBUSB_ENDPOINT_IN : 0) | epctx;
+
+ if (ep_type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS)
+ usb_passthru_data_calculate_num_isos(xfer, &head, &nframe,
+ &len);
+ else
+ usb_passthru_calculate_xfer_ptr(xfer, &head, &tail, &len);
+
+ if (len == 0)
+ goto done;
+
+ DPRINTF((
+ "usb_passthru handle data - DIR=%s|EP=%d, blen %d, transfer_type: %d, ndata: %d",
+ dir ? "IN" : "OUT", epctx, len,
+ sc->endpoint_types[epctx << 1 | dir].type, xfer->ndata));
+
+ pthread_mutex_lock(&sc->mtx);
+ if (sc->handle == NULL || xfer->tr_softc)
+ goto done_locked;
+ pthread_mutex_unlock(&sc->mtx);
+
+ up_xfer = usb_passthru_xfer_alloc(sc, dir, xfer, len, ep,
+ ep_type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS ? nframe : 0);
+
+ if (ep_type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS)
+ usb_passthru_data_fill_xfer_iso(xfer, up_xfer->lusb_xfer);
+
+ if (!dir) {
+ for (cur = head, offset = 0; offset < len;
+ cur = (cur + 1) % USB_MAX_XFER_BLOCKS) {
+ memcpy(&up_xfer->buffer[offset], xfer->data[cur].buf,
+ xfer->data[cur].blen);
+ offset += xfer->data[cur].blen;
+ }
+ }
+
+ pthread_mutex_lock(&sc->mtx);
+ switch (ep_type) {
+ case LIBUSB_TRANSFER_TYPE_BULK:
+ libusb_fill_bulk_transfer(up_xfer->lusb_xfer, sc->handle, ep,
+ up_xfer->buffer, len, usb_passthru_data_callback, up_xfer,
+ 0);
+ break;
+ case LIBUSB_TRANSFER_TYPE_INTERRUPT:
+ libusb_fill_interrupt_transfer(up_xfer->lusb_xfer, sc->handle,
+ ep, up_xfer->buffer, len, usb_passthru_data_callback,
+ up_xfer, 0);
+ break;
+ case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS:
+ libusb_fill_iso_transfer(up_xfer->lusb_xfer, sc->handle, ep,
+ up_xfer->buffer, len, nframe, usb_passthru_data_callback,
+ up_xfer, 0);
+ break;
+ }
+ up_xfer->lusb_xfer->flags = LIBUSB_TRANSFER_FREE_TRANSFER |
+ LIBUSB_TRANSFER_FREE_BUFFER;
+ err = libusb_submit_transfer(up_xfer->lusb_xfer);
+ if (err)
+ goto done_locked;
+ xfer->tr_softc = up_xfer;
+done_locked:
+ pthread_mutex_unlock(&sc->mtx);
+done:
+ return (libusb_error_to_usb_error(err));
+}
+
+static int
+usb_passthru_reset(void *scarg __unused)
+{
+ struct usb_passthru_softc *sc = scarg;
+ int err = 0;
+
+ DPRINTF(("%s", __FUNCTION__));
+
+ pthread_mutex_lock(&sc->mtx);
+
+ if (sc->handle) {
+ err = libusb_reset_device(sc->handle);
+ if (err != LIBUSB_SUCCESS)
+ goto done;
+ err = usb_passthru_guest_attach_device(sc);
+ }
+done:
+ pthread_mutex_unlock(&sc->mtx);
+ return (libusb_error_to_usb_error(err));
+}
+
+static int
+usb_passthru_remove(void *scarg)
+{
+ struct usb_passthru_softc *sc = scarg;
+ int err;
+ if (sc == NULL)
+ return (USB_ERR_NORMAL_COMPLETION);
+
+ LIST_REMOVE(sc, next);
+ pthread_mutex_lock(&sc->mtx);
+ err = usb_passthru_guest_detach_device(sc);
+ if (err)
+ goto done;
+ libusb_hotplug_deregister_callback(NULL, sc->cb);
+ if ((err = usb_passthru_guest_detach_device_on_host(sc)) !=
+ LIBUSB_SUCCESS) {
+ goto done;
+ }
+ libusb_close(sc->handle);
+
+done:
+ pthread_mutex_unlock(&sc->mtx);
+
+ return (err);
+}
+
+static int
+usb_passthru_stop(void *scarg __unused)
+{
+ return (0);
+}
+
+static int
+usb_passthru_configure_ep(void *scarg, int epid, struct xhci_endp_ctx *ctx,
+ int configure)
+{
+ struct usb_passthru_softc *sc = scarg;
+ int type;
+
+ if (configure) {
+ type = XHCI_EPCTX_1_EPTYPE_GET(ctx->dwEpCtx1);
+ sc->endpoint_types[epid].inout = type >= 5;
+ type &= 3;
+ sc->endpoint_types[epid].type = type;
+ } else {
+ sc->endpoint_types[epid].inout = sc->endpoint_types[epid].type =
+ -1;
+ }
+
+ return (0);
+}
+
+static int
+usb_passthru_cancel(struct usb_data_xfer *xfer)
+{
+ struct usb_passthru_libusb_xfer *up_xfer;
+ int err;
+
+ up_xfer = xfer->tr_softc;
+ if (up_xfer == NULL) {
+ return (0);
+ }
+ up_xfer->usb_xfer = NULL;
+
+ DPRINTF(("%s", __FUNCTION__));
+
+ err = libusb_cancel_transfer(up_xfer->lusb_xfer);
+ xfer->tr_softc = NULL;
+
+ return (libusb_error_to_usb_error(err));
+}
+
+static struct usb_devemu ue_passthru = {
+ .ue_emu = "passthru",
+ .ue_static = 0,
+ .ue_usbver = 3,
+ .ue_usbspeed = USB_SPEED_HIGH,
+ .ue_probe = usb_passthru_probe,
+ .ue_init = usb_passthru_init,
+ .ue_request = usb_passthru_request,
+ .ue_data = usb_passthru_data_handler,
+ .ue_reset = usb_passthru_reset,
+ .ue_remove = usb_passthru_remove,
+ .ue_stop = usb_passthru_stop,
+ .ue_cancel = usb_passthru_cancel,
+ .ue_configure_ep = usb_passthru_configure_ep,
+#ifdef BHYVE_SNAPSHOT
+ .ue_snapshot = usb_passthru_snapshot,
+#endif
+};
+
+USB_EMUL_SET(ue_passthru);
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Dec 6, 2:46 AM (15 h, 43 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
26707054
Default Alt Text
D52166.diff (42 KB)
Attached To
Mode
D52166: bhyve: implement single USB device passthrough support
Attached
Detach File
Event Timeline
Log In to Comment