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); | ||||