Changeset View
Changeset View
Standalone View
Standalone View
usr.sbin/bhyve/pci_passthru.c
| Show All 31 Lines | |||||
| __FBSDID("$FreeBSD$"); | __FBSDID("$FreeBSD$"); | ||||
| #include <sys/param.h> | #include <sys/param.h> | ||||
| #ifndef WITHOUT_CAPSICUM | #ifndef WITHOUT_CAPSICUM | ||||
| #include <sys/capsicum.h> | #include <sys/capsicum.h> | ||||
| #endif | #endif | ||||
| #include <sys/types.h> | #include <sys/types.h> | ||||
| #include <sys/mman.h> | #include <sys/mman.h> | ||||
| #include <sys/pciio.h> | |||||
| #include <sys/ioctl.h> | #include <sys/ioctl.h> | ||||
| #include <sys/stat.h> | |||||
| #include <dev/io/iodev.h> | #include <dev/io/iodev.h> | ||||
| #include <dev/pci/pcireg.h> | #include <dev/pci/pcireg.h> | ||||
| #include <vm/vm.h> | #include <vm/vm.h> | ||||
| #include <machine/iodev.h> | #include <machine/iodev.h> | ||||
| #include <machine/vm.h> | #include <machine/vm.h> | ||||
| #ifndef WITHOUT_CAPSICUM | #ifndef WITHOUT_CAPSICUM | ||||
| #include <capsicum_helpers.h> | #include <capsicum_helpers.h> | ||||
| #endif | #endif | ||||
| #include <stdio.h> | #include <stdio.h> | ||||
| #include <stdlib.h> | #include <stdlib.h> | ||||
| #include <string.h> | #include <string.h> | ||||
| #include <err.h> | #include <err.h> | ||||
| #include <errno.h> | #include <errno.h> | ||||
| #include <fcntl.h> | #include <fcntl.h> | ||||
| #include <sysexits.h> | #include <sysexits.h> | ||||
| #include <unistd.h> | #include <unistd.h> | ||||
| #include <machine/vmm.h> | #include <machine/vmm.h> | ||||
| #include <vmmapi.h> | |||||
| #include "config.h" | #include "config.h" | ||||
| #include "debug.h" | #include "debug.h" | ||||
| #include "pci_emul.h" | |||||
| #include "mem.h" | #include "mem.h" | ||||
| #include "pci_passthru.h" | |||||
| #ifndef _PATH_DEVPCI | #ifndef _PATH_DEVPCI | ||||
| #define _PATH_DEVPCI "/dev/pci" | #define _PATH_DEVPCI "/dev/pci" | ||||
| #endif | #endif | ||||
| #define LEGACY_SUPPORT 1 | #define LEGACY_SUPPORT 1 | ||||
| #define MSIX_TABLE_COUNT(ctrl) (((ctrl) & PCIM_MSIXCTRL_TABLE_SIZE) + 1) | #define MSIX_TABLE_COUNT(ctrl) (((ctrl) & PCIM_MSIXCTRL_TABLE_SIZE) + 1) | ||||
| #define MSIX_CAPLEN 12 | #define MSIX_CAPLEN 12 | ||||
| #define PCI_CAP_START_OFFSET 0x40 | |||||
| static int pcifd = -1; | static int pcifd = -1; | ||||
| struct passthru_softc { | |||||
| struct pci_devinst *psc_pi; | |||||
| struct pcibar psc_bar[PCI_BARMAX + 1]; | |||||
| struct { | |||||
| int capoff; | |||||
| int msgctrl; | |||||
| int emulated; | |||||
| } psc_msi; | |||||
| struct { | |||||
| int capoff; | |||||
| } psc_msix; | |||||
| struct pcisel psc_sel; | |||||
| }; | |||||
| static int | static int | ||||
| msi_caplen(int msgctrl) | msi_caplen(int msgctrl) | ||||
| { | { | ||||
| int len; | int len; | ||||
| len = 10; /* minimum length of msi capability */ | len = 10; /* minimum length of msi capability */ | ||||
| if (msgctrl & PCIM_MSICTRL_64BIT) | if (msgctrl & PCIM_MSICTRL_64BIT) | ||||
| len += 4; | len += 4; | ||||
| #if 0 | #if 0 | ||||
| /* | /* | ||||
| * Ignore the 'mask' and 'pending' bits in the MSI capability. | * Ignore the 'mask' and 'pending' bits in the MSI capability. | ||||
| * We'll let the guest manipulate them directly. | * We'll let the guest manipulate them directly. | ||||
| */ | */ | ||||
| if (msgctrl & PCIM_MSICTRL_VECTOR) | if (msgctrl & PCIM_MSICTRL_VECTOR) | ||||
| len += 10; | len += 10; | ||||
| #endif | #endif | ||||
| return (len); | return (len); | ||||
| } | } | ||||
| static uint32_t | uint32_t | ||||
| read_config(const struct pcisel *sel, long reg, int width) | read_config(const struct pcisel *sel, long reg, int width) | ||||
| { | { | ||||
| struct pci_io pi; | struct pci_io pi; | ||||
| bzero(&pi, sizeof(pi)); | bzero(&pi, sizeof(pi)); | ||||
| pi.pi_sel = *sel; | pi.pi_sel = *sel; | ||||
| pi.pi_reg = reg; | pi.pi_reg = reg; | ||||
| pi.pi_width = width; | pi.pi_width = width; | ||||
| if (ioctl(pcifd, PCIOCREAD, &pi) < 0) | if (ioctl(pcifd, PCIOCREAD, &pi) < 0) | ||||
| return (0); /* XXX */ | return (0); /* XXX */ | ||||
| else | else | ||||
| return (pi.pi_data); | return (pi.pi_data); | ||||
| } | } | ||||
| static void | void | ||||
| write_config(const struct pcisel *sel, long reg, int width, uint32_t data) | write_config(const struct pcisel *sel, long reg, int width, uint32_t data) | ||||
| { | { | ||||
| struct pci_io pi; | struct pci_io pi; | ||||
| bzero(&pi, sizeof(pi)); | bzero(&pi, sizeof(pi)); | ||||
| pi.pi_sel = *sel; | pi.pi_sel = *sel; | ||||
| pi.pi_reg = reg; | pi.pi_reg = reg; | ||||
| pi.pi_width = width; | pi.pi_width = width; | ||||
| ▲ Show 20 Lines • Show All 296 Lines • ▼ Show 20 Lines | init_msix_table(struct vmctx *ctx, struct passthru_softc *sc) | ||||
| * 1. The PBA may reside in the first or last page containing the MSI-X | * 1. The PBA may reside in the first or last page containing the MSI-X | ||||
| * table. | * table. | ||||
| * 2. While PCI devices are not supposed to use the page(s) containing | * 2. While PCI devices are not supposed to use the page(s) containing | ||||
| * the MSI-X table for other purposes, some do in practice. | * the MSI-X table for other purposes, some do in practice. | ||||
| */ | */ | ||||
| memset(&pbm, 0, sizeof(pbm)); | memset(&pbm, 0, sizeof(pbm)); | ||||
| pbm.pbm_sel = sc->psc_sel; | pbm.pbm_sel = sc->psc_sel; | ||||
| pbm.pbm_flags = PCIIO_BAR_MMAP_RW; | pbm.pbm_flags = PCIIO_BAR_MMAP_RW; | ||||
| pbm.pbm_reg = PCIR_BAR(pi->pi_msix.pba_bar); | pbm.pbm_reg = PCIR_BAR(pi->pi_msix.table_bar); | ||||
| pbm.pbm_memattr = VM_MEMATTR_DEVICE; | pbm.pbm_memattr = VM_MEMATTR_DEVICE; | ||||
| if (ioctl(pcifd, PCIOCBARMMAP, &pbm) != 0) { | if (ioctl(pcifd, PCIOCBARMMAP, &pbm) != 0) { | ||||
| warn("Failed to map MSI-X table BAR on %d/%d/%d", b, s, f); | warn("Failed to map MSI-X table BAR on %d/%d/%d", b, s, f); | ||||
| return (-1); | return (-1); | ||||
| } | } | ||||
| assert(pbm.pbm_bar_off == 0); | assert(pbm.pbm_bar_off == 0); | ||||
| pi->pi_msix.mapped_addr = (uint8_t *)(uintptr_t)pbm.pbm_map_base; | pi->pi_msix.mapped_addr = (uint8_t *)(uintptr_t)pbm.pbm_map_base; | ||||
| pi->pi_msix.mapped_size = pbm.pbm_map_length; | pi->pi_msix.mapped_size = pbm.pbm_map_length; | ||||
| table_offset = rounddown2(pi->pi_msix.table_offset, 4096); | table_offset = rounddown2(pi->pi_msix.table_offset, 4096); | ||||
| table_size = pi->pi_msix.table_offset - table_offset; | table_size = pi->pi_msix.table_offset - table_offset; | ||||
| table_size += pi->pi_msix.table_count * MSIX_TABLE_ENTRY_SIZE; | table_size += pi->pi_msix.table_count * MSIX_TABLE_ENTRY_SIZE; | ||||
| table_size = roundup2(table_size, 4096); | table_size = roundup2(table_size, 4096); | ||||
| /* | /* | ||||
| * Unmap any pages not covered by the table, we do not need to emulate | * Unmap any pages not containing the table, we do not need to emulate | ||||
| * accesses to them. Avoid releasing address space to help ensure that | * accesses to them. Avoid releasing address space to help ensure that | ||||
| * a buggy out-of-bounds access causes a crash. | * a buggy out-of-bounds access causes a crash. | ||||
| */ | */ | ||||
| if (table_offset != 0) | if (table_offset != 0) | ||||
| if (mprotect(pi->pi_msix.mapped_addr, table_offset, | if (mprotect(pi->pi_msix.mapped_addr, table_offset, | ||||
| PROT_NONE) != 0) | PROT_NONE) != 0) | ||||
| warn("Failed to unmap MSI-X table BAR region"); | warn("Failed to unmap MSI-X table BAR region"); | ||||
| if (table_offset + table_size != pi->pi_msix.mapped_size) | if (table_offset + table_size != pi->pi_msix.mapped_size) | ||||
| if (mprotect(pi->pi_msix.mapped_addr, | if (mprotect( | ||||
| pi->pi_msix.mapped_addr + table_offset + table_size, | |||||
| pi->pi_msix.mapped_size - (table_offset + table_size), | pi->pi_msix.mapped_size - (table_offset + table_size), | ||||
| PROT_NONE) != 0) | PROT_NONE) != 0) | ||||
| warn("Failed to unmap MSI-X table BAR region"); | warn("Failed to unmap MSI-X table BAR region"); | ||||
| return (0); | return (0); | ||||
| } | } | ||||
| static int | static int | ||||
| ▲ Show 20 Lines • Show All 86 Lines • ▼ Show 20 Lines | cfginit(struct vmctx *ctx, struct pci_devinst *pi, int bus, int slot, int func) | ||||
| error = 1; | error = 1; | ||||
| sc = pi->pi_arg; | sc = pi->pi_arg; | ||||
| bzero(&sc->psc_sel, sizeof(struct pcisel)); | bzero(&sc->psc_sel, sizeof(struct pcisel)); | ||||
| sc->psc_sel.pc_bus = bus; | sc->psc_sel.pc_bus = bus; | ||||
| sc->psc_sel.pc_dev = slot; | sc->psc_sel.pc_dev = slot; | ||||
| sc->psc_sel.pc_func = func; | sc->psc_sel.pc_func = func; | ||||
| /* copy physical PCI header to virtual cfgspace */ | |||||
| for (uint32_t i = 0; i < PCI_CAP_START_OFFSET; ++i) { | |||||
| /* | |||||
| * INTLINE and INTPIN shouldn't be aligned with it's physical | |||||
| * value. They are already set by pci_emul_init. | |||||
| */ | |||||
| if (i == PCIR_INTLINE || i == PCIR_INTPIN) | |||||
| continue; | |||||
| pci_set_cfgdata8(pi, i, read_config(&sc->psc_sel, i, 1)); | |||||
| } | |||||
| if (cfginitmsi(sc) != 0) { | if (cfginitmsi(sc) != 0) { | ||||
| warnx("failed to initialize MSI for PCI %d/%d/%d", | warnx("failed to initialize MSI for PCI %d/%d/%d", | ||||
| bus, slot, func); | bus, slot, func); | ||||
| goto done; | goto done; | ||||
| } | } | ||||
| if (cfginitbar(ctx, sc) != 0) { | if (cfginitbar(ctx, sc) != 0) { | ||||
| warnx("failed to initialize BARs for PCI %d/%d/%d", | warnx("failed to initialize BARs for PCI %d/%d/%d", | ||||
| Show All 18 Lines | if (pci_msix_table_bar(pi) >= 0) { | ||||
| } | } | ||||
| } | } | ||||
| error = 0; /* success */ | error = 0; /* success */ | ||||
| done: | done: | ||||
| return (error); | return (error); | ||||
| } | } | ||||
| int | |||||
| set_pcir_handler(struct passthru_softc *const sc, const uint32_t reg, | |||||
| const uint32_t len, const cfgread_handler rhandler, | |||||
| const cfgwrite_handler whandler) | |||||
| { | |||||
| if (reg > PCI_REGMAX || reg + len > PCI_REGMAX + 1) | |||||
| return (-1); | |||||
| for (uint32_t i = reg; i < reg + len; ++i) { | |||||
| sc->psc_pcir_rhandler[i] = rhandler; | |||||
| sc->psc_pcir_whandler[i] = whandler; | |||||
| } | |||||
| return 0; | |||||
| } | |||||
| static int | static int | ||||
| passthru_legacy_config(nvlist_t *nvl, const char *opts) | passthru_legacy_config(nvlist_t *nvl, const char *opts) | ||||
| { | { | ||||
| char value[16]; | char value[16]; | ||||
| int bus, slot, func; | int bus, slot, func; | ||||
| if (opts == NULL) | if (opts == NULL) | ||||
| return (0); | return (0); | ||||
| if (sscanf(opts, "%d/%d/%d", &bus, &slot, &func) != 3) { | if (sscanf(opts, "%d/%d/%d", &bus, &slot, &func) != 3) { | ||||
| EPRINTLN("passthru: invalid options \"%s\"", opts); | EPRINTLN("passthru: invalid options \"%s\"", opts); | ||||
| return (-1); | return (-1); | ||||
| } | } | ||||
| snprintf(value, sizeof(value), "%d", bus); | snprintf(value, sizeof(value), "%d", bus); | ||||
| set_config_value_node(nvl, "bus", value); | set_config_value_node(nvl, "bus", value); | ||||
| snprintf(value, sizeof(value), "%d", slot); | snprintf(value, sizeof(value), "%d", slot); | ||||
| set_config_value_node(nvl, "slot", value); | set_config_value_node(nvl, "slot", value); | ||||
| snprintf(value, sizeof(value), "%d", func); | snprintf(value, sizeof(value), "%d", func); | ||||
| set_config_value_node(nvl, "func", value); | set_config_value_node(nvl, "func", value); | ||||
| return (pci_parse_legacy_config(nvl, strchr(opts, ','))); | |||||
| } | |||||
| static int | |||||
| passthru_init_rom(struct vmctx *const ctx, struct passthru_softc *const sc, | |||||
| const char *const romfile) | |||||
| { | |||||
| if (romfile == NULL) { | |||||
| return (0); | return (0); | ||||
| } | } | ||||
| const int fd = open(romfile, O_RDONLY); | |||||
| if (fd < 0) { | |||||
| warnx("%s: can't open romfile \"%s\"", __func__, romfile); | |||||
| return (-1); | |||||
| } | |||||
| struct stat sbuf; | |||||
| if (fstat(fd, &sbuf) < 0) { | |||||
| warnx("%s: can't fstat romfile \"%s\"", __func__, romfile); | |||||
| close(fd); | |||||
| return (-1); | |||||
| } | |||||
| const uint64_t rom_size = sbuf.st_size; | |||||
| void *const rom_data = mmap(NULL, rom_size, PROT_READ, MAP_SHARED, fd, | |||||
| 0); | |||||
| if (rom_data == MAP_FAILED) { | |||||
| warnx("%s: unable to mmap romfile \"%s\" (%d)", __func__, | |||||
| romfile, errno); | |||||
| close(fd); | |||||
| return (-1); | |||||
| } | |||||
| void *rom_addr; | |||||
| int error = pci_emul_alloc_rom(sc->psc_pi, rom_size, &rom_addr); | |||||
| if (error) { | |||||
| warnx("%s: failed to alloc rom segment", __func__); | |||||
| munmap(rom_data, rom_size); | |||||
| close(fd); | |||||
| return (error); | |||||
| } | |||||
| memcpy(rom_addr, rom_data, rom_size); | |||||
| sc->psc_bar[PCI_ROM_IDX].type = PCIBAR_ROM; | |||||
| sc->psc_bar[PCI_ROM_IDX].addr = (uint64_t)rom_addr; | |||||
| sc->psc_bar[PCI_ROM_IDX].size = rom_size; | |||||
| munmap(rom_data, rom_size); | |||||
| close(fd); | |||||
| return (0); | |||||
| } | |||||
| static int | static int | ||||
| passthru_init_quirks(struct vmctx *const ctx, struct pci_devinst *const pi, | |||||
| nvlist_t *const nvl) | |||||
| { | |||||
| struct passthru_softc *const sc = pi->pi_arg; | |||||
| const uint16_t vendor = read_config(&sc->psc_sel, PCIR_VENDOR, 0x02); | |||||
| const uint8_t class = read_config(&sc->psc_sel, PCIR_CLASS, 0x01); | |||||
| /* currently only display devices have quirks */ | |||||
| if (class != PCIC_DISPLAY) | |||||
| return (0); | |||||
| if (vendor == PCI_VENDOR_INTEL) | |||||
| return gvt_d_init(ctx, pi, nvl); | |||||
| return (0); | |||||
| } | |||||
| static void | |||||
| passthru_deinit_quirks(struct vmctx *const ctx, struct pci_devinst *const pi) | |||||
| { | |||||
| struct passthru_softc *const sc = pi->pi_arg; | |||||
| if (sc == NULL) | |||||
| return; | |||||
| const uint16_t vendor = read_config(&sc->psc_sel, PCIR_VENDOR, 0x02); | |||||
| const uint8_t class = read_config(&sc->psc_sel, PCIR_CLASS, 0x01); | |||||
| /* currently only display devices have quirks */ | |||||
| if (class != PCIC_DISPLAY) | |||||
| return; | |||||
| if (vendor == PCI_VENDOR_INTEL) | |||||
| return gvt_d_deinit(ctx, pi); | |||||
| return; | |||||
| } | |||||
| static int | |||||
| passthru_init(struct vmctx *ctx, struct pci_devinst *pi, nvlist_t *nvl) | passthru_init(struct vmctx *ctx, struct pci_devinst *pi, nvlist_t *nvl) | ||||
| { | { | ||||
| int bus, slot, func, error, memflags; | int bus, slot, func, error, memflags; | ||||
| struct passthru_softc *sc; | struct passthru_softc *sc; | ||||
| const char *value; | const char *value; | ||||
| #ifndef WITHOUT_CAPSICUM | #ifndef WITHOUT_CAPSICUM | ||||
| cap_rights_t rights; | cap_rights_t rights; | ||||
| cap_ioctl_t pci_ioctls[] = | cap_ioctl_t pci_ioctls[] = | ||||
| ▲ Show 20 Lines • Show All 48 Lines • ▼ Show 20 Lines | #define GET_INT_CONFIG(var, name) do { \ | ||||
| } | } | ||||
| sc = calloc(1, sizeof(struct passthru_softc)); | sc = calloc(1, sizeof(struct passthru_softc)); | ||||
| pi->pi_arg = sc; | pi->pi_arg = sc; | ||||
| sc->psc_pi = pi; | sc->psc_pi = pi; | ||||
| /* initialize config space */ | /* initialize config space */ | ||||
| error = cfginit(ctx, pi, bus, slot, func); | if ((error = cfginit(ctx, pi, bus, slot, func)) != 0) | ||||
| goto done; | |||||
| /* set default handler for all PCI registers */ | |||||
| if ((error = set_pcir_handler(sc, 0, PCI_REGMAX + 1, | |||||
| passthru_cfgread_default, passthru_cfgwrite_default)) != 0) | |||||
| goto done; | |||||
| /* protect PCI header */ | |||||
| if ((error = set_pcir_handler(sc, 0, PCI_CAP_START_OFFSET, | |||||
| passthru_cfgread_emulate, passthru_cfgwrite_emulate)) != 0) | |||||
| goto done; | |||||
| /* allow access to command and status register */ | |||||
| if ((error = set_pcir_handler(sc, PCIR_COMMAND, 0x04, | |||||
| passthru_cfgread_default, passthru_cfgwrite_default)) != 0) | |||||
| goto done; | |||||
| if ((error = passthru_init_quirks(ctx, pi, nvl)) != 0) | |||||
| goto done; | |||||
| /* initialize ROM */ | |||||
| if ((error = passthru_init_rom(ctx, sc, | |||||
| get_config_value_node(nvl, "rom"))) != 0) | |||||
| goto done; | |||||
| error = 0; /* success */ | |||||
| done: | done: | ||||
| if (error) { | if (error) { | ||||
| passthru_deinit_quirks(ctx, pi); | |||||
| free(sc); | free(sc); | ||||
| vm_unassign_pptdev(ctx, bus, slot, func); | vm_unassign_pptdev(ctx, bus, slot, func); | ||||
| } | } | ||||
| return (error); | return (error); | ||||
| } | } | ||||
| static int | static int | ||||
| bar_access(int coff) | bar_access(int coff) | ||||
| { | { | ||||
| if (coff >= PCIR_BAR(0) && coff < PCIR_BAR(PCI_BARMAX + 1)) | if ((coff >= PCIR_BAR(0) && coff < PCIR_BAR(PCI_BARMAX + 1)) || | ||||
| coff == PCIR_BIOS) | |||||
| return (1); | return (1); | ||||
| else | else | ||||
| return (0); | return (0); | ||||
| } | } | ||||
| static int | static int | ||||
| msicap_access(struct passthru_softc *sc, int coff) | msicap_access(struct passthru_softc *sc, int coff) | ||||
| { | { | ||||
| Show All 19 Lines | msixcap_access(struct passthru_softc *sc, int coff) | ||||
| return (coff >= sc->psc_msix.capoff && | return (coff >= sc->psc_msix.capoff && | ||||
| coff < sc->psc_msix.capoff + MSIX_CAPLEN); | coff < sc->psc_msix.capoff + MSIX_CAPLEN); | ||||
| } | } | ||||
| static int | static int | ||||
| passthru_cfgread(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, | passthru_cfgread(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, | ||||
| int coff, int bytes, uint32_t *rv) | int coff, int bytes, uint32_t *rv) | ||||
| { | { | ||||
| struct passthru_softc *const sc = pi->pi_arg; | |||||
| return sc->psc_pcir_rhandler[coff](ctx, vcpu, pi, coff, bytes, rv); | |||||
| } | |||||
| int | |||||
| passthru_cfgread_default(struct vmctx *const ctx, const int vcpu, | |||||
| struct pci_devinst *const pi, const int coff, const int bytes, | |||||
| uint32_t *const rv) | |||||
| { | |||||
| struct passthru_softc *sc; | struct passthru_softc *sc; | ||||
| sc = pi->pi_arg; | sc = pi->pi_arg; | ||||
| /* | /* | ||||
| * PCI BARs and MSI capability is emulated. | * MSI capability is emulated. | ||||
| */ | */ | ||||
| if (bar_access(coff) || msicap_access(sc, coff) || | if (msicap_access(sc, coff) || msixcap_access(sc, coff)) | ||||
| msixcap_access(sc, coff)) | |||||
| return (-1); | return (-1); | ||||
| #ifdef LEGACY_SUPPORT | |||||
| /* | /* | ||||
| * Emulate PCIR_CAP_PTR if this device does not support MSI capability | |||||
| * natively. | |||||
| */ | |||||
| if (sc->psc_msi.emulated) { | |||||
| if (coff >= PCIR_CAP_PTR && coff < PCIR_CAP_PTR + 4) | |||||
| return (-1); | |||||
| } | |||||
| #endif | |||||
| /* | |||||
| * Emulate the command register. If a single read reads both the | * Emulate the command register. If a single read reads both the | ||||
| * command and status registers, read the status register from the | * command and status registers, read the status register from the | ||||
| * device's config space. | * device's config space. | ||||
| */ | */ | ||||
| if (coff == PCIR_COMMAND) { | if (coff == PCIR_COMMAND) { | ||||
| if (bytes <= 2) | if (bytes <= 2) | ||||
| return (-1); | return (-1); | ||||
| *rv = read_config(&sc->psc_sel, PCIR_STATUS, 2) << 16 | | *rv = read_config(&sc->psc_sel, PCIR_STATUS, 2) << 16 | | ||||
| pci_get_cfgdata16(pi, PCIR_COMMAND); | pci_get_cfgdata16(pi, PCIR_COMMAND); | ||||
| return (0); | return (0); | ||||
| } | } | ||||
| /* Everything else just read from the device's config space */ | /* Everything else just read from the device's config space */ | ||||
| *rv = read_config(&sc->psc_sel, coff, bytes); | *rv = read_config(&sc->psc_sel, coff, bytes); | ||||
| return (0); | return (0); | ||||
| } | } | ||||
| int | |||||
| passthru_cfgread_emulate(struct vmctx *const ctx, const int vcpu, | |||||
| struct pci_devinst *const pi, const int coff, const int bytes, | |||||
| uint32_t *const rv) | |||||
| { | |||||
| return (-1); | |||||
| } | |||||
| static int | static int | ||||
| passthru_cfgwrite(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, | passthru_cfgwrite(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, | ||||
| int coff, int bytes, uint32_t val) | int coff, int bytes, uint32_t val) | ||||
| { | { | ||||
| struct passthru_softc *const sc = pi->pi_arg; | |||||
| return sc->psc_pcir_whandler[coff](ctx, vcpu, pi, coff, bytes, val); | |||||
| } | |||||
| int | |||||
| passthru_cfgwrite_default(struct vmctx *const ctx, const int vcpu, | |||||
| struct pci_devinst *const pi, const int coff, const int bytes, | |||||
| const uint32_t val) | |||||
| { | |||||
| int error, msix_table_entries, i; | int error, msix_table_entries, i; | ||||
| struct passthru_softc *sc; | struct passthru_softc *sc; | ||||
| uint16_t cmd_old; | uint16_t cmd_old; | ||||
| sc = pi->pi_arg; | sc = pi->pi_arg; | ||||
| /* | /* | ||||
| * PCI BARs are emulated | |||||
| */ | |||||
| if (bar_access(coff)) | |||||
| return (-1); | |||||
| /* | |||||
| * MSI capability is emulated | * MSI capability is emulated | ||||
| */ | */ | ||||
| if (msicap_access(sc, coff)) { | if (msicap_access(sc, coff)) { | ||||
| pci_emul_capwrite(pi, coff, bytes, val, sc->psc_msi.capoff, | pci_emul_capwrite(pi, coff, bytes, val, sc->psc_msi.capoff, | ||||
| PCIY_MSI); | PCIY_MSI); | ||||
| error = vm_setup_pptdev_msi(ctx, vcpu, sc->psc_sel.pc_bus, | error = vm_setup_pptdev_msi(ctx, vcpu, sc->psc_sel.pc_bus, | ||||
| sc->psc_sel.pc_dev, sc->psc_sel.pc_func, | sc->psc_sel.pc_dev, sc->psc_sel.pc_func, | ||||
| pi->pi_msi.addr, pi->pi_msi.msg_data, | pi->pi_msi.addr, pi->pi_msi.msg_data, | ||||
| Show All 23 Lines | if (pi->pi_msix.enabled) { | ||||
| error = vm_disable_pptdev_msix(ctx, sc->psc_sel.pc_bus, | error = vm_disable_pptdev_msix(ctx, sc->psc_sel.pc_bus, | ||||
| sc->psc_sel.pc_dev, sc->psc_sel.pc_func); | sc->psc_sel.pc_dev, sc->psc_sel.pc_func); | ||||
| if (error) | if (error) | ||||
| err(1, "vm_disable_pptdev_msix"); | err(1, "vm_disable_pptdev_msix"); | ||||
| } | } | ||||
| return (0); | return (0); | ||||
| } | } | ||||
| uint32_t write_val = val; | |||||
| #ifdef LEGACY_SUPPORT | #ifdef LEGACY_SUPPORT | ||||
| /* | /* | ||||
| * If this device does not support MSI natively then we cannot let | * If this device does not support MSI natively then we cannot let | ||||
| * the guest disable legacy interrupts from the device. It is the | * the guest disable legacy interrupts from the device. It is the | ||||
| * legacy interrupt that is triggering the virtual MSI to the guest. | * legacy interrupt that is triggering the virtual MSI to the guest. | ||||
| */ | */ | ||||
| if (sc->psc_msi.emulated && pci_msi_enabled(pi)) { | if (sc->psc_msi.emulated && pci_msi_enabled(pi)) { | ||||
| if (coff == PCIR_COMMAND && bytes == 2) | if (coff == PCIR_COMMAND && bytes == 2) | ||||
| val &= ~PCIM_CMD_INTxDIS; | write_val &= ~PCIM_CMD_INTxDIS; | ||||
| } | } | ||||
| #endif | #endif | ||||
| write_config(&sc->psc_sel, coff, bytes, val); | write_config(&sc->psc_sel, coff, bytes, write_val); | ||||
| if (coff == PCIR_COMMAND) { | if (coff == PCIR_COMMAND) { | ||||
| cmd_old = pci_get_cfgdata16(pi, PCIR_COMMAND); | cmd_old = pci_get_cfgdata16(pi, PCIR_COMMAND); | ||||
| if (bytes == 1) | if (bytes == 1) | ||||
| pci_set_cfgdata8(pi, PCIR_COMMAND, val); | pci_set_cfgdata8(pi, PCIR_COMMAND, write_val); | ||||
| else if (bytes == 2) | else if (bytes == 2) | ||||
| pci_set_cfgdata16(pi, PCIR_COMMAND, val); | pci_set_cfgdata16(pi, PCIR_COMMAND, write_val); | ||||
| pci_emul_cmd_changed(pi, cmd_old); | pci_emul_cmd_changed(pi, cmd_old); | ||||
| } | } | ||||
| return (0); | return (0); | ||||
| } | } | ||||
| int | |||||
| passthru_cfgwrite_emulate(struct vmctx *const ctx, const int vcpu, | |||||
| struct pci_devinst *const pi, const int coff, const int bytes, | |||||
| const uint32_t val) | |||||
| { | |||||
| return (-1); | |||||
| } | |||||
| static void | static void | ||||
| passthru_write(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, int baridx, | passthru_write(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, int baridx, | ||||
| uint64_t offset, int size, uint64_t value) | uint64_t offset, int size, uint64_t value) | ||||
| { | { | ||||
| struct passthru_softc *sc; | struct passthru_softc *sc; | ||||
| struct pci_bar_ioreq pio; | struct pci_bar_ioreq pio; | ||||
| sc = pi->pi_arg; | sc = pi->pi_arg; | ||||
| ▲ Show 20 Lines • Show All 118 Lines • ▼ Show 20 Lines | if (vm_map_pptdev_mmio(ctx, sc->psc_sel.pc_bus, | ||||
| sc->psc_sel.pc_func, address, | sc->psc_sel.pc_func, address, | ||||
| sc->psc_bar[baridx].size, | sc->psc_bar[baridx].size, | ||||
| sc->psc_bar[baridx].addr) != 0) | sc->psc_bar[baridx].addr) != 0) | ||||
| warnx("pci_passthru: map_pptdev_mmio failed"); | warnx("pci_passthru: map_pptdev_mmio failed"); | ||||
| } | } | ||||
| } | } | ||||
| static void | static void | ||||
| passthru_addr_rom(struct pci_devinst *const pi, const int idx, | |||||
| const int enabled) | |||||
| { | |||||
| const uint64_t addr = pi->pi_bar[idx].addr; | |||||
| const uint64_t size = pi->pi_bar[idx].size; | |||||
| if (!enabled) { | |||||
| if (vm_munmap_memseg(pi->pi_vmctx, addr, size) != 0) { | |||||
| warnx("%s: munmap_memseg @ [%016lx - %016lx] failed", | |||||
| __func__, addr, addr + size); | |||||
| } | |||||
| } else { | |||||
| if (vm_mmap_memseg(pi->pi_vmctx, addr, VM_PCIROM, | |||||
| pi->pi_romoffset, size, PROT_READ | PROT_EXEC) != 0) { | |||||
| warnx("%s: mnmap_memseg @ [%016lx - %016lx] failed", | |||||
| __func__, addr, addr + size); | |||||
| } | |||||
| } | |||||
| } | |||||
| static void | |||||
| passthru_addr(struct vmctx *ctx, struct pci_devinst *pi, int baridx, | passthru_addr(struct vmctx *ctx, struct pci_devinst *pi, int baridx, | ||||
| int enabled, uint64_t address) | int enabled, uint64_t address) | ||||
| { | { | ||||
| switch (pi->pi_bar[baridx].type) { | |||||
| if (pi->pi_bar[baridx].type == PCIBAR_IO) | case PCIBAR_IO: | ||||
| return; | /* IO BARs are emulated */ | ||||
| break; | |||||
| case PCIBAR_ROM: | |||||
| passthru_addr_rom(pi, baridx, enabled); | |||||
| break; | |||||
| case PCIBAR_MEM32: | |||||
| case PCIBAR_MEM64: | |||||
| if (baridx == pci_msix_table_bar(pi)) | if (baridx == pci_msix_table_bar(pi)) | ||||
| passthru_msix_addr(ctx, pi, baridx, enabled, address); | passthru_msix_addr(ctx, pi, baridx, enabled, address); | ||||
| else | else | ||||
| passthru_mmio_addr(ctx, pi, baridx, enabled, address); | passthru_mmio_addr(ctx, pi, baridx, enabled, address); | ||||
| break; | |||||
| default: | |||||
| errx(4, "%s: invalid BAR type %d", __func__, | |||||
| pi->pi_bar[baridx].type); | |||||
| } | |||||
| } | } | ||||
| struct pci_devemu passthru = { | struct pci_devemu passthru = { | ||||
| .pe_emu = "passthru", | .pe_emu = "passthru", | ||||
| .pe_init = passthru_init, | .pe_init = passthru_init, | ||||
| .pe_legacy_config = passthru_legacy_config, | .pe_legacy_config = passthru_legacy_config, | ||||
| .pe_cfgwrite = passthru_cfgwrite, | .pe_cfgwrite = passthru_cfgwrite, | ||||
| .pe_cfgread = passthru_cfgread, | .pe_cfgread = passthru_cfgread, | ||||
| .pe_barwrite = passthru_write, | .pe_barwrite = passthru_write, | ||||
| .pe_barread = passthru_read, | .pe_barread = passthru_read, | ||||
| .pe_baraddr = passthru_addr, | .pe_baraddr = passthru_addr, | ||||
| }; | }; | ||||
| PCI_EMUL_SET(passthru); | PCI_EMUL_SET(passthru); | ||||