Changeset View
Changeset View
Standalone View
Standalone View
usr.sbin/bhyve/pci_xhci.c
Show First 20 Lines • Show All 50 Lines • ▼ Show 20 Lines | |||||
#include <machine/vmm_snapshot.h> | #include <machine/vmm_snapshot.h> | ||||
#include <dev/usb/usbdi.h> | #include <dev/usb/usbdi.h> | ||||
#include <dev/usb/usb.h> | #include <dev/usb/usb.h> | ||||
#include <dev/usb/usb_freebsd.h> | #include <dev/usb/usb_freebsd.h> | ||||
#include <xhcireg.h> | #include <xhcireg.h> | ||||
#include "bhyverun.h" | #include "bhyverun.h" | ||||
#include "config.h" | |||||
#include "debug.h" | #include "debug.h" | ||||
#include "pci_emul.h" | #include "pci_emul.h" | ||||
#include "pci_xhci.h" | #include "pci_xhci.h" | ||||
#include "usb_emul.h" | #include "usb_emul.h" | ||||
static int xhci_debug = 0; | static int xhci_debug = 0; | ||||
#define DPRINTF(params) if (xhci_debug) PRINTLN params | #define DPRINTF(params) if (xhci_debug) PRINTLN params | ||||
▲ Show 20 Lines • Show All 204 Lines • ▼ Show 20 Lines | struct pci_xhci_softc { | ||||
uint32_t regsend; /* end of configuration registers */ | uint32_t regsend; /* end of configuration registers */ | ||||
struct pci_xhci_opregs opregs; | struct pci_xhci_opregs opregs; | ||||
struct pci_xhci_rtsregs rtsregs; | struct pci_xhci_rtsregs rtsregs; | ||||
struct pci_xhci_portregs *portregs; | struct pci_xhci_portregs *portregs; | ||||
struct pci_xhci_dev_emu **devices; /* XHCI[port] = device */ | struct pci_xhci_dev_emu **devices; /* XHCI[port] = device */ | ||||
struct pci_xhci_dev_emu **slots; /* slots assigned from 1 */ | struct pci_xhci_dev_emu **slots; /* slots assigned from 1 */ | ||||
int ndevices; | |||||
int usb2_port_start; | int usb2_port_start; | ||||
int usb3_port_start; | int usb3_port_start; | ||||
}; | }; | ||||
/* portregs and devices arrays are set up to start from idx=1 */ | /* portregs and devices arrays are set up to start from idx=1 */ | ||||
#define XHCI_PORTREG_PTR(x,n) &(x)->portregs[(n)] | #define XHCI_PORTREG_PTR(x,n) &(x)->portregs[(n)] | ||||
▲ Show 20 Lines • Show All 277 Lines • ▼ Show 20 Lines | |||||
} | } | ||||
struct xhci_dev_ctx * | struct xhci_dev_ctx * | ||||
pci_xhci_get_dev_ctx(struct pci_xhci_softc *sc, uint32_t slot) | pci_xhci_get_dev_ctx(struct pci_xhci_softc *sc, uint32_t slot) | ||||
{ | { | ||||
uint64_t devctx_addr; | uint64_t devctx_addr; | ||||
struct xhci_dev_ctx *devctx; | struct xhci_dev_ctx *devctx; | ||||
assert(slot > 0 && slot <= sc->ndevices); | assert(slot > 0 && slot <= XHCI_MAX_DEVS); | ||||
assert(XHCI_SLOTDEV_PTR(sc, slot) != NULL); | |||||
assert(sc->opregs.dcbaa_p != NULL); | assert(sc->opregs.dcbaa_p != NULL); | ||||
devctx_addr = sc->opregs.dcbaa_p->dcba[slot]; | devctx_addr = sc->opregs.dcbaa_p->dcba[slot]; | ||||
if (devctx_addr == 0) { | if (devctx_addr == 0) { | ||||
DPRINTF(("get_dev_ctx devctx_addr == 0")); | DPRINTF(("get_dev_ctx devctx_addr == 0")); | ||||
return (NULL); | return (NULL); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 256 Lines • ▼ Show 20 Lines | pci_xhci_cmd_disable_slot(struct pci_xhci_softc *sc, uint32_t slot) | ||||
uint32_t cmderr; | uint32_t cmderr; | ||||
DPRINTF(("pci_xhci disable slot %u", slot)); | DPRINTF(("pci_xhci disable slot %u", slot)); | ||||
cmderr = XHCI_TRB_ERROR_NO_SLOTS; | cmderr = XHCI_TRB_ERROR_NO_SLOTS; | ||||
if (sc->portregs == NULL) | if (sc->portregs == NULL) | ||||
goto done; | goto done; | ||||
if (slot > sc->ndevices) { | if (slot > XHCI_MAX_SLOTS) { | ||||
cmderr = XHCI_TRB_ERROR_SLOT_NOT_ON; | cmderr = XHCI_TRB_ERROR_SLOT_NOT_ON; | ||||
goto done; | goto done; | ||||
} | } | ||||
dev = XHCI_SLOTDEV_PTR(sc, slot); | dev = XHCI_SLOTDEV_PTR(sc, slot); | ||||
if (dev) { | if (dev) { | ||||
if (dev->dev_slotstate == XHCI_ST_DISABLED) { | if (dev->dev_slotstate == XHCI_ST_DISABLED) { | ||||
cmderr = XHCI_TRB_ERROR_SLOT_NOT_ON; | cmderr = XHCI_TRB_ERROR_SLOT_NOT_ON; | ||||
} else { | } else { | ||||
dev->dev_slotstate = XHCI_ST_DISABLED; | dev->dev_slotstate = XHCI_ST_DISABLED; | ||||
cmderr = XHCI_TRB_ERROR_SUCCESS; | cmderr = XHCI_TRB_ERROR_SUCCESS; | ||||
/* TODO: reset events and endpoints */ | /* TODO: reset events and endpoints */ | ||||
} | } | ||||
} | } else | ||||
cmderr = XHCI_TRB_ERROR_SLOT_NOT_ON; | |||||
done: | done: | ||||
return (cmderr); | return (cmderr); | ||||
} | } | ||||
static uint32_t | static uint32_t | ||||
pci_xhci_cmd_reset_device(struct pci_xhci_softc *sc, uint32_t slot) | pci_xhci_cmd_reset_device(struct pci_xhci_softc *sc, uint32_t slot) | ||||
{ | { | ||||
▲ Show 20 Lines • Show All 1,032 Lines • ▼ Show 20 Lines | pci_xhci_device_doorbell(struct pci_xhci_softc *sc, uint32_t slot, | ||||
struct pci_xhci_trb_ring *sctx_tr; | struct pci_xhci_trb_ring *sctx_tr; | ||||
struct xhci_trb *trb; | struct xhci_trb *trb; | ||||
uint64_t ringaddr; | uint64_t ringaddr; | ||||
uint32_t ccs; | uint32_t ccs; | ||||
DPRINTF(("pci_xhci doorbell slot %u epid %u stream %u", | DPRINTF(("pci_xhci doorbell slot %u epid %u stream %u", | ||||
slot, epid, streamid)); | slot, epid, streamid)); | ||||
if (slot == 0 || slot > sc->ndevices) { | if (slot == 0 || slot > XHCI_MAX_SLOTS) { | ||||
DPRINTF(("pci_xhci: invalid doorbell slot %u", slot)); | DPRINTF(("pci_xhci: invalid doorbell slot %u", slot)); | ||||
return; | return; | ||||
} | } | ||||
if (epid == 0 || epid >= XHCI_MAX_ENDPOINTS) { | if (epid == 0 || epid >= XHCI_MAX_ENDPOINTS) { | ||||
DPRINTF(("pci_xhci: invalid endpoint %u", epid)); | DPRINTF(("pci_xhci: invalid endpoint %u", epid)); | ||||
return; | return; | ||||
} | } | ||||
▲ Show 20 Lines • Show All 720 Lines • ▼ Show 20 Lines | |||||
static int | static int | ||||
pci_xhci_dev_event(struct usb_hci *hci, enum hci_usbev evid, void *param) | pci_xhci_dev_event(struct usb_hci *hci, enum hci_usbev evid, void *param) | ||||
{ | { | ||||
DPRINTF(("xhci device event port %d", hci->hci_port)); | DPRINTF(("xhci device event port %d", hci->hci_port)); | ||||
return (0); | return (0); | ||||
} | } | ||||
/* | |||||
* Each controller contains a "slot" node which contains a list of | |||||
* child nodes each of which is a device. Each slot node's name | |||||
* corresponds to a specific controller slot. These nodes | |||||
* contain a "device" variable identifying the device model of the | |||||
* USB device. For example: | |||||
* | |||||
* pci.0.1.0 | |||||
* .device="xhci" | |||||
* .slot | |||||
* .1 | |||||
* .device="tablet" | |||||
*/ | |||||
static int | |||||
pci_xhci_legacy_config(nvlist_t *nvl, const char *opts) | |||||
{ | |||||
char node_name[16]; | |||||
nvlist_t *slots_nvl, *slot_nvl; | |||||
char *cp, *opt, *str, *tofree; | |||||
int slot; | |||||
if (opts == NULL) | |||||
return (0); | |||||
static void | slots_nvl = create_relative_config_node(nvl, "slot"); | ||||
pci_xhci_device_usage(char *opt) | slot = 1; | ||||
{ | tofree = str = strdup(opts); | ||||
while ((opt = strsep(&str, ",")) != NULL) { | |||||
/* device[=<config>] */ | |||||
cp = strchr(opt, '='); | |||||
if (cp != NULL) { | |||||
*cp = '\0'; | |||||
cp++; | |||||
} | |||||
EPRINTLN("Invalid USB emulation \"%s\"", opt); | 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); | |||||
/* | |||||
* NB: Given that we split on commas above, the legacy | |||||
* format only supports a single option. | |||||
*/ | |||||
if (cp != NULL && *cp != '\0') | |||||
pci_parse_legacy_config(slot_nvl, cp); | |||||
} | } | ||||
free(tofree); | |||||
return (0); | |||||
} | |||||
static int | static int | ||||
pci_xhci_parse_opts(struct pci_xhci_softc *sc, char *opts) | pci_xhci_parse_devices(struct pci_xhci_softc *sc, nvlist_t *nvl) | ||||
{ | { | ||||
struct pci_xhci_dev_emu **devices; | |||||
struct pci_xhci_dev_emu *dev; | struct pci_xhci_dev_emu *dev; | ||||
struct usb_devemu *ue; | struct usb_devemu *ue; | ||||
void *devsc; | const nvlist_t *slots_nvl, *slot_nvl; | ||||
char *uopt, *xopts, *config; | const char *name, *device; | ||||
int usb3_port, usb2_port, i; | char *cp; | ||||
void *devsc, *cookie; | |||||
long slot; | |||||
int type, usb3_port, usb2_port, i, ndevices; | |||||
uopt = NULL; | usb3_port = sc->usb3_port_start; | ||||
usb3_port = sc->usb3_port_start - 1; | usb2_port = sc->usb2_port_start; | ||||
usb2_port = sc->usb2_port_start - 1; | |||||
devices = NULL; | |||||
if (opts == NULL) | sc->devices = calloc(XHCI_MAX_DEVS, sizeof(struct pci_xhci_dev_emu *)); | ||||
goto portsfinal; | sc->slots = calloc(XHCI_MAX_SLOTS, sizeof(struct pci_xhci_dev_emu *)); | ||||
devices = calloc(XHCI_MAX_DEVS, sizeof(struct pci_xhci_dev_emu *)); | /* port and slot numbering start from 1 */ | ||||
sc->devices--; | |||||
sc->slots--; | |||||
sc->slots = calloc(XHCI_MAX_SLOTS, sizeof(struct pci_xhci_dev_emu *)); | ndevices = 0; | ||||
sc->devices = devices; | |||||
sc->ndevices = 0; | |||||
uopt = strdup(opts); | slots_nvl = find_relative_config_node(nvl, "slots"); | ||||
for (xopts = strtok(uopt, ","); | if (slots_nvl == NULL) | ||||
xopts != NULL; | goto portsfinal; | ||||
xopts = strtok(NULL, ",")) { | |||||
if (usb2_port == ((sc->usb2_port_start-1) + XHCI_MAX_DEVS/2) || | cookie = NULL; | ||||
usb3_port == ((sc->usb3_port_start-1) + XHCI_MAX_DEVS/2)) { | while ((name = nvlist_next(slots_nvl, &type, &cookie)) != NULL) { | ||||
if (usb2_port == ((sc->usb2_port_start) + XHCI_MAX_DEVS/2) || | |||||
usb3_port == ((sc->usb3_port_start) + XHCI_MAX_DEVS/2)) { | |||||
WPRINTF(("pci_xhci max number of USB 2 or 3 " | WPRINTF(("pci_xhci max number of USB 2 or 3 " | ||||
"devices reached, max %d", XHCI_MAX_DEVS/2)); | "devices reached, max %d", XHCI_MAX_DEVS/2)); | ||||
usb2_port = usb3_port = -1; | goto bad; | ||||
goto done; | |||||
} | } | ||||
/* device[=<config>] */ | if (type != NV_TYPE_NVLIST) { | ||||
if ((config = strchr(xopts, '=')) == NULL) | EPRINTLN( | ||||
config = ""; /* no config */ | "pci_xhci: config variable '%s' under slot node", | ||||
else | name); | ||||
*config++ = '\0'; | goto bad; | ||||
} | |||||
ue = usb_emu_finddev(xopts); | slot = strtol(name, &cp, 0); | ||||
if (*cp != '\0' || slot <= 0 || slot > XHCI_MAX_SLOTS) { | |||||
EPRINTLN("pci_xhci: invalid slot '%s'", name); | |||||
goto bad; | |||||
} | |||||
if (XHCI_SLOTDEV_PTR(sc, slot) != NULL) { | |||||
EPRINTLN("pci_xhci: duplicate slot '%s'", name); | |||||
goto bad; | |||||
} | |||||
slot_nvl = nvlist_get_nvlist(slots_nvl, name); | |||||
device = get_config_value_node(slot_nvl, "device"); | |||||
if (device == NULL) { | |||||
EPRINTLN( | |||||
"pci_xhci: missing \"device\" value for slot '%s'", | |||||
name); | |||||
goto bad; | |||||
} | |||||
ue = usb_emu_finddev(device); | |||||
if (ue == NULL) { | if (ue == NULL) { | ||||
pci_xhci_device_usage(xopts); | EPRINTLN("pci_xhci: unknown device model \"%s\"", | ||||
DPRINTF(("pci_xhci device not found %s", xopts)); | device); | ||||
usb2_port = usb3_port = -1; | goto bad; | ||||
goto done; | |||||
} | } | ||||
DPRINTF(("pci_xhci adding device %s, opts \"%s\"", | DPRINTF(("pci_xhci adding device %s", device)); | ||||
xopts, config)); | |||||
dev = calloc(1, sizeof(struct pci_xhci_dev_emu)); | dev = calloc(1, sizeof(struct pci_xhci_dev_emu)); | ||||
dev->xsc = sc; | dev->xsc = sc; | ||||
dev->hci.hci_sc = dev; | dev->hci.hci_sc = dev; | ||||
dev->hci.hci_intr = pci_xhci_dev_intr; | dev->hci.hci_intr = pci_xhci_dev_intr; | ||||
dev->hci.hci_event = pci_xhci_dev_event; | dev->hci.hci_event = pci_xhci_dev_event; | ||||
if (ue->ue_usbver == 2) { | if (ue->ue_usbver == 2) { | ||||
dev->hci.hci_port = usb2_port + 1; | if (usb2_port == sc->usb2_port_start + | ||||
devices[usb2_port] = dev; | XHCI_MAX_DEVS / 2) { | ||||
WPRINTF(("pci_xhci max number of USB 2 devices " | |||||
"reached, max %d", XHCI_MAX_DEVS / 2)); | |||||
goto bad; | |||||
} | |||||
dev->hci.hci_port = usb2_port; | |||||
usb2_port++; | usb2_port++; | ||||
} else { | } else { | ||||
dev->hci.hci_port = usb3_port + 1; | if (usb3_port == sc->usb3_port_start + | ||||
devices[usb3_port] = dev; | XHCI_MAX_DEVS / 2) { | ||||
WPRINTF(("pci_xhci max number of USB 3 devices " | |||||
"reached, max %d", XHCI_MAX_DEVS / 2)); | |||||
goto bad; | |||||
} | |||||
dev->hci.hci_port = usb3_port; | |||||
usb3_port++; | usb3_port++; | ||||
} | } | ||||
XHCI_DEVINST_PTR(sc, dev->hci.hci_port) = dev; | |||||
dev->hci.hci_address = 0; | dev->hci.hci_address = 0; | ||||
devsc = ue->ue_init(&dev->hci, config); | devsc = ue->ue_init(&dev->hci, nvl); | ||||
if (devsc == NULL) { | if (devsc == NULL) { | ||||
pci_xhci_device_usage(xopts); | goto bad; | ||||
usb2_port = usb3_port = -1; | |||||
goto done; | |||||
} | } | ||||
dev->dev_ue = ue; | dev->dev_ue = ue; | ||||
dev->dev_sc = devsc; | dev->dev_sc = devsc; | ||||
/* assign slot number to device */ | XHCI_SLOTDEV_PTR(sc, slot) = dev; | ||||
sc->slots[sc->ndevices] = dev; | |||||
sc->ndevices++; | |||||
} | } | ||||
portsfinal: | portsfinal: | ||||
sc->portregs = calloc(XHCI_MAX_DEVS, sizeof(struct pci_xhci_portregs)); | sc->portregs = calloc(XHCI_MAX_DEVS, sizeof(struct pci_xhci_portregs)); | ||||
if (sc->ndevices > 0) { | |||||
/* port and slot numbering start from 1 */ | |||||
sc->devices--; | |||||
sc->portregs--; | sc->portregs--; | ||||
sc->slots--; | |||||
if (ndevices > 0) { | |||||
for (i = 1; i <= XHCI_MAX_DEVS; i++) { | for (i = 1; i <= XHCI_MAX_DEVS; i++) { | ||||
pci_xhci_init_port(sc, i); | pci_xhci_init_port(sc, i); | ||||
} | } | ||||
} else { | } else { | ||||
WPRINTF(("pci_xhci no USB devices configured")); | WPRINTF(("pci_xhci no USB devices configured")); | ||||
sc->ndevices = 1; | |||||
} | } | ||||
return (0); | |||||
done: | bad: | ||||
if (devices != NULL) { | for (i = 1; i <= XHCI_MAX_DEVS; i++) { | ||||
if (usb2_port <= 0 && usb3_port <= 0) { | free(XHCI_DEVINST_PTR(sc, i)); | ||||
sc->devices = NULL; | } | ||||
for (i = 0; devices[i] != NULL; i++) | |||||
free(devices[i]); | |||||
sc->ndevices = -1; | |||||
free(devices); | free(sc->devices + 1); | ||||
free(sc->slots + 1); | |||||
return (-1); | |||||
} | } | ||||
} | |||||
free(uopt); | |||||
return (sc->ndevices); | |||||
} | |||||
static int | static int | ||||
pci_xhci_init(struct vmctx *ctx, struct pci_devinst *pi, char *opts) | pci_xhci_init(struct vmctx *ctx, struct pci_devinst *pi, nvlist_t *nvl) | ||||
{ | { | ||||
struct pci_xhci_softc *sc; | struct pci_xhci_softc *sc; | ||||
int error; | int error; | ||||
if (xhci_in_use) { | if (xhci_in_use) { | ||||
WPRINTF(("pci_xhci controller already defined")); | WPRINTF(("pci_xhci controller already defined")); | ||||
return (-1); | return (-1); | ||||
} | } | ||||
xhci_in_use = 1; | xhci_in_use = 1; | ||||
sc = calloc(1, sizeof(struct pci_xhci_softc)); | sc = calloc(1, sizeof(struct pci_xhci_softc)); | ||||
pi->pi_arg = sc; | pi->pi_arg = sc; | ||||
sc->xsc_pi = pi; | sc->xsc_pi = pi; | ||||
sc->usb2_port_start = (XHCI_MAX_DEVS/2) + 1; | sc->usb2_port_start = (XHCI_MAX_DEVS/2) + 1; | ||||
sc->usb3_port_start = 1; | sc->usb3_port_start = 1; | ||||
/* discover devices */ | /* discover devices */ | ||||
error = pci_xhci_parse_opts(sc, opts); | error = pci_xhci_parse_devices(sc, nvl); | ||||
if (error < 0) | if (error < 0) | ||||
goto done; | goto done; | ||||
else | else | ||||
error = 0; | error = 0; | ||||
sc->caplength = XHCI_SET_CAPLEN(XHCI_CAPLEN) | | sc->caplength = XHCI_SET_CAPLEN(XHCI_CAPLEN) | | ||||
XHCI_SET_HCIVERSION(0x0100); | XHCI_SET_HCIVERSION(0x0100); | ||||
sc->hcsparams1 = XHCI_SET_HCSP1_MAXPORTS(XHCI_MAX_DEVS) | | sc->hcsparams1 = XHCI_SET_HCSP1_MAXPORTS(XHCI_MAX_DEVS) | | ||||
▲ Show 20 Lines • Show All 302 Lines • ▼ Show 20 Lines | for (i = 1; i <= XHCI_MAX_SLOTS; i++) { | ||||
/* devices[i]->dev_sc */ | /* devices[i]->dev_sc */ | ||||
dev->dev_ue->ue_snapshot(dev->dev_sc, meta); | dev->dev_ue->ue_snapshot(dev->dev_sc, meta); | ||||
/* devices[i]->hci */ | /* devices[i]->hci */ | ||||
SNAPSHOT_VAR_OR_LEAVE(dev->hci.hci_address, meta, ret, done); | 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_port, meta, ret, done); | ||||
} | } | ||||
SNAPSHOT_VAR_OR_LEAVE(sc->ndevices, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(sc->usb2_port_start, meta, ret, done); | SNAPSHOT_VAR_OR_LEAVE(sc->usb2_port_start, meta, ret, done); | ||||
SNAPSHOT_VAR_OR_LEAVE(sc->usb3_port_start, meta, ret, done); | SNAPSHOT_VAR_OR_LEAVE(sc->usb3_port_start, meta, ret, done); | ||||
done: | done: | ||||
return (ret); | return (ret); | ||||
} | } | ||||
#endif | #endif | ||||
struct pci_devemu pci_de_xhci = { | struct pci_devemu pci_de_xhci = { | ||||
.pe_emu = "xhci", | .pe_emu = "xhci", | ||||
.pe_init = pci_xhci_init, | .pe_init = pci_xhci_init, | ||||
.pe_legacy_config = pci_xhci_legacy_config, | |||||
.pe_barwrite = pci_xhci_write, | .pe_barwrite = pci_xhci_write, | ||||
.pe_barread = pci_xhci_read, | .pe_barread = pci_xhci_read, | ||||
#ifdef BHYVE_SNAPSHOT | #ifdef BHYVE_SNAPSHOT | ||||
.pe_snapshot = pci_xhci_snapshot, | .pe_snapshot = pci_xhci_snapshot, | ||||
#endif | #endif | ||||
}; | }; | ||||
PCI_EMUL_SET(pci_de_xhci); | PCI_EMUL_SET(pci_de_xhci); |