diff options
Diffstat (limited to 'usr.sbin/bhyve/pci_emul.c')
-rw-r--r-- | usr.sbin/bhyve/pci_emul.c | 264 |
1 files changed, 223 insertions, 41 deletions
diff --git a/usr.sbin/bhyve/pci_emul.c b/usr.sbin/bhyve/pci_emul.c index b5e923c..713e197 100644 --- a/usr.sbin/bhyve/pci_emul.c +++ b/usr.sbin/bhyve/pci_emul.c @@ -31,6 +31,7 @@ __FBSDID("$FreeBSD$"); #include <sys/param.h> #include <sys/linker_set.h> +#include <sys/errno.h> #include <ctype.h> #include <stdio.h> @@ -38,6 +39,7 @@ __FBSDID("$FreeBSD$"); #include <string.h> #include <strings.h> #include <assert.h> +#include <stdbool.h> #include <machine/vmm.h> #include <vmmapi.h> @@ -353,20 +355,150 @@ pci_emul_alloc_bar(struct pci_devinst *pdi, int idx, enum pcibar_type type, return (pci_emul_alloc_pbar(pdi, idx, 0, type, size)); } +/* + * Register (or unregister) the MMIO or I/O region associated with the BAR + * register 'idx' of an emulated pci device. + */ +static void +modify_bar_registration(struct pci_devinst *pi, int idx, int registration) +{ + int error; + struct inout_port iop; + struct mem_range mr; + + switch (pi->pi_bar[idx].type) { + case PCIBAR_IO: + bzero(&iop, sizeof(struct inout_port)); + iop.name = pi->pi_name; + iop.port = pi->pi_bar[idx].addr; + iop.size = pi->pi_bar[idx].size; + if (registration) { + iop.flags = IOPORT_F_INOUT; + iop.handler = pci_emul_io_handler; + iop.arg = pi; + error = register_inout(&iop); + } else + error = unregister_inout(&iop); + break; + case PCIBAR_MEM32: + case PCIBAR_MEM64: + bzero(&mr, sizeof(struct mem_range)); + mr.name = pi->pi_name; + mr.base = pi->pi_bar[idx].addr; + mr.size = pi->pi_bar[idx].size; + if (registration) { + mr.flags = MEM_F_RW; + mr.handler = pci_emul_mem_handler; + mr.arg1 = pi; + mr.arg2 = idx; + error = register_mem(&mr); + } else + error = unregister_mem(&mr); + break; + default: + error = EINVAL; + break; + } + assert(error == 0); +} + +static void +unregister_bar(struct pci_devinst *pi, int idx) +{ + + modify_bar_registration(pi, idx, 0); +} + +static void +register_bar(struct pci_devinst *pi, int idx) +{ + + modify_bar_registration(pi, idx, 1); +} + +/* Are we decoding i/o port accesses for the emulated pci device? */ +static int +porten(struct pci_devinst *pi) +{ + uint16_t cmd; + + cmd = pci_get_cfgdata16(pi, PCIR_COMMAND); + + return (cmd & PCIM_CMD_PORTEN); +} + +/* Are we decoding memory accesses for the emulated pci device? */ +static int +memen(struct pci_devinst *pi) +{ + uint16_t cmd; + + cmd = pci_get_cfgdata16(pi, PCIR_COMMAND); + + return (cmd & PCIM_CMD_MEMEN); +} + +/* + * Update the MMIO or I/O address that is decoded by the BAR register. + * + * If the pci device has enabled the address space decoding then intercept + * the address range decoded by the BAR register. + */ +static void +update_bar_address(struct pci_devinst *pi, uint64_t addr, int idx, int type) +{ + int decode; + + if (pi->pi_bar[idx].type == PCIBAR_IO) + decode = porten(pi); + else + decode = memen(pi); + + if (decode) + unregister_bar(pi, idx); + + switch (type) { + case PCIBAR_IO: + case PCIBAR_MEM32: + pi->pi_bar[idx].addr = addr; + break; + case PCIBAR_MEM64: + pi->pi_bar[idx].addr &= ~0xffffffffUL; + pi->pi_bar[idx].addr |= addr; + break; + case PCIBAR_MEMHI64: + pi->pi_bar[idx].addr &= 0xffffffff; + pi->pi_bar[idx].addr |= addr; + break; + default: + assert(0); + } + + if (decode) + register_bar(pi, idx); +} + int pci_emul_alloc_pbar(struct pci_devinst *pdi, int idx, uint64_t hostbase, enum pcibar_type type, uint64_t size) { - int i, error; + int error; uint64_t *baseptr, limit, addr, mask, lobits, bar; - struct inout_port iop; - struct mem_range memp; assert(idx >= 0 && idx <= PCI_BARMAX); if ((size & (size - 1)) != 0) size = 1UL << flsl(size); /* round up to a power of 2 */ + /* Enforce minimum BAR sizes required by the PCI standard */ + if (type == PCIBAR_IO) { + if (size < 4) + size = 4; + } else { + if (size < 16) + size = 16; + } + switch (type) { case PCIBAR_NONE: baseptr = NULL; @@ -443,30 +575,7 @@ pci_emul_alloc_pbar(struct pci_devinst *pdi, int idx, uint64_t hostbase, pci_set_cfgdata32(pdi, PCIR_BAR(idx + 1), bar >> 32); } - /* add a handler to intercept accesses to the I/O bar */ - if (type == PCIBAR_IO) { - iop.name = pdi->pi_name; - iop.flags = IOPORT_F_INOUT; - iop.handler = pci_emul_io_handler; - iop.arg = pdi; - - for (i = 0; i < size; i++) { - iop.port = addr + i; - register_inout(&iop); - } - } else if (type == PCIBAR_MEM32 || type == PCIBAR_MEM64) { - /* add memory bar intercept handler */ - memp.name = pdi->pi_name; - memp.flags = MEM_F_RW; - memp.base = addr; - memp.size = size; - memp.handler = pci_emul_mem_handler; - memp.arg1 = pdi; - memp.arg2 = idx; - - error = register_mem(&memp); - assert(error == 0); - } + register_bar(pdi, idx); return (0); } @@ -1101,6 +1210,62 @@ pci_emul_cfgaddr(struct vmctx *ctx, int vcpu, int in, int port, int bytes, } INOUT_PORT(pci_cfgaddr, CONF1_ADDR_PORT, IOPORT_F_OUT, pci_emul_cfgaddr); +static uint32_t +bits_changed(uint32_t old, uint32_t new, uint32_t mask) +{ + + return ((old ^ new) & mask); +} + +static void +pci_emul_cmdwrite(struct pci_devinst *pi, uint32_t new, int bytes) +{ + int i; + uint16_t old; + + /* + * The command register is at an offset of 4 bytes and thus the + * guest could write 1, 2 or 4 bytes starting at this offset. + */ + + old = pci_get_cfgdata16(pi, PCIR_COMMAND); /* stash old value */ + CFGWRITE(pi, PCIR_COMMAND, new, bytes); /* update config */ + new = pci_get_cfgdata16(pi, PCIR_COMMAND); /* get updated value */ + + /* + * If the MMIO or I/O address space decoding has changed then + * register/unregister all BARs that decode that address space. + */ + for (i = 0; i < PCI_BARMAX; i++) { + switch (pi->pi_bar[i].type) { + case PCIBAR_NONE: + case PCIBAR_MEMHI64: + break; + case PCIBAR_IO: + /* I/O address space decoding changed? */ + if (bits_changed(old, new, PCIM_CMD_PORTEN)) { + if (porten(pi)) + register_bar(pi, i); + else + unregister_bar(pi, i); + } + break; + case PCIBAR_MEM32: + case PCIBAR_MEM64: + /* MMIO address space decoding changed? */ + if (bits_changed(old, new, PCIM_CMD_MEMEN)) { + if (memen(pi)) + register_bar(pi, i); + else + unregister_bar(pi, i); + } + break; + default: + assert(0); + } + } +} + static int pci_emul_cfgdata(struct vmctx *ctx, int vcpu, int in, int port, int bytes, uint32_t *eax, void *arg) @@ -1108,7 +1273,7 @@ pci_emul_cfgdata(struct vmctx *ctx, int vcpu, int in, int port, int bytes, struct pci_devinst *pi; struct pci_devemu *pe; int coff, idx, needcfg; - uint64_t mask, bar; + uint64_t addr, bar, mask; assert(bytes == 1 || bytes == 2 || bytes == 4); @@ -1175,33 +1340,48 @@ pci_emul_cfgdata(struct vmctx *ctx, int vcpu, int in, int port, int bytes, if (bytes != 4 || (coff & 0x3) != 0) return (0); idx = (coff - PCIR_BAR(0)) / 4; + mask = ~(pi->pi_bar[idx].size - 1); switch (pi->pi_bar[idx].type) { case PCIBAR_NONE: - bar = 0; + pi->pi_bar[idx].addr = bar = 0; break; case PCIBAR_IO: - mask = ~(pi->pi_bar[idx].size - 1); - mask &= PCIM_BAR_IO_BASE; - bar = (*eax & mask) | PCIM_BAR_IO_SPACE; + addr = *eax & mask; + addr &= 0xffff; + bar = addr | PCIM_BAR_IO_SPACE; + /* + * Register the new BAR value for interception + */ + if (addr != pi->pi_bar[idx].addr) { + update_bar_address(pi, addr, idx, + PCIBAR_IO); + } break; case PCIBAR_MEM32: - mask = ~(pi->pi_bar[idx].size - 1); - mask &= PCIM_BAR_MEM_BASE; - bar = *eax & mask; + addr = bar = *eax & mask; bar |= PCIM_BAR_MEM_SPACE | PCIM_BAR_MEM_32; + if (addr != pi->pi_bar[idx].addr) { + update_bar_address(pi, addr, idx, + PCIBAR_MEM32); + } break; case PCIBAR_MEM64: - mask = ~(pi->pi_bar[idx].size - 1); - mask &= PCIM_BAR_MEM_BASE; - bar = *eax & mask; + addr = bar = *eax & mask; bar |= PCIM_BAR_MEM_SPACE | PCIM_BAR_MEM_64 | PCIM_BAR_MEM_PREFETCH; + if (addr != (uint32_t)pi->pi_bar[idx].addr) { + update_bar_address(pi, addr, idx, + PCIBAR_MEM64); + } break; case PCIBAR_MEMHI64: mask = ~(pi->pi_bar[idx - 1].size - 1); - mask &= PCIM_BAR_MEM_BASE; - bar = ((uint64_t)*eax << 32) & mask; - bar = bar >> 32; + addr = ((uint64_t)*eax << 32) & mask; + bar = addr >> 32; + if (bar != pi->pi_bar[idx - 1].addr >> 32) { + update_bar_address(pi, addr, idx - 1, + PCIBAR_MEMHI64); + } break; default: assert(0); @@ -1210,6 +1390,8 @@ pci_emul_cfgdata(struct vmctx *ctx, int vcpu, int in, int port, int bytes, } else if (pci_emul_iscap(pi, coff)) { pci_emul_capwrite(pi, coff, bytes, *eax); + } else if (coff == PCIR_COMMAND) { + pci_emul_cmdwrite(pi, *eax, bytes); } else { CFGWRITE(pi, coff, *eax, bytes); } |