Changeset View
Standalone View
usr.sbin/bhyve/pci_emul.c
Show All 27 Lines | |||||
* $FreeBSD$ | * $FreeBSD$ | ||||
*/ | */ | ||||
#include <sys/cdefs.h> | #include <sys/cdefs.h> | ||||
__FBSDID("$FreeBSD$"); | __FBSDID("$FreeBSD$"); | ||||
#include <sys/param.h> | #include <sys/param.h> | ||||
#include <sys/linker_set.h> | #include <sys/linker_set.h> | ||||
#include <sys/mman.h> | |||||
#include <ctype.h> | #include <ctype.h> | ||||
#include <err.h> | #include <err.h> | ||||
#include <errno.h> | #include <errno.h> | ||||
#include <pthread.h> | #include <pthread.h> | ||||
#include <stdio.h> | #include <stdio.h> | ||||
#include <stdlib.h> | #include <stdlib.h> | ||||
#include <string.h> | #include <string.h> | ||||
▲ Show 20 Lines • Show All 52 Lines • ▼ Show 20 Lines | struct businfo { | ||||
struct slotinfo slotinfo[MAXSLOTS]; | struct slotinfo slotinfo[MAXSLOTS]; | ||||
}; | }; | ||||
static struct businfo *pci_businfo[MAXBUSES]; | static struct businfo *pci_businfo[MAXBUSES]; | ||||
SET_DECLARE(pci_devemu_set, struct pci_devemu); | SET_DECLARE(pci_devemu_set, struct pci_devemu); | ||||
static uint64_t pci_emul_iobase; | static uint64_t pci_emul_iobase; | ||||
static uint8_t *pci_emul_rombase; | |||||
static uint64_t pci_emul_romoffset; | |||||
static uint8_t *pci_emul_romlim; | |||||
static uint64_t pci_emul_membase32; | static uint64_t pci_emul_membase32; | ||||
static uint64_t pci_emul_membase64; | static uint64_t pci_emul_membase64; | ||||
static uint64_t pci_emul_memlim64; | static uint64_t pci_emul_memlim64; | ||||
struct pci_bar_allocation { | struct pci_bar_allocation { | ||||
TAILQ_ENTRY(pci_bar_allocation) chain; | TAILQ_ENTRY(pci_bar_allocation) chain; | ||||
struct pci_devinst *pdi; | struct pci_devinst *pdi; | ||||
int idx; | int idx; | ||||
enum pcibar_type type; | enum pcibar_type type; | ||||
uint64_t size; | uint64_t size; | ||||
}; | }; | ||||
TAILQ_HEAD(pci_bar_list, pci_bar_allocation) pci_bars = TAILQ_HEAD_INITIALIZER( | TAILQ_HEAD(pci_bar_list, pci_bar_allocation) pci_bars = TAILQ_HEAD_INITIALIZER( | ||||
pci_bars); | pci_bars); | ||||
#define PCI_EMUL_IOBASE 0x2000 | #define PCI_EMUL_IOBASE 0x2000 | ||||
#define PCI_EMUL_IOLIMIT 0x10000 | #define PCI_EMUL_IOLIMIT 0x10000 | ||||
#define PCI_EMUL_ROMSIZE 0x10000000 | |||||
#define PCI_EMUL_ECFG_BASE 0xE0000000 /* 3.5GB */ | #define PCI_EMUL_ECFG_BASE 0xE0000000 /* 3.5GB */ | ||||
#define PCI_EMUL_ECFG_SIZE (MAXBUSES * 1024 * 1024) /* 1MB per bus */ | #define PCI_EMUL_ECFG_SIZE (MAXBUSES * 1024 * 1024) /* 1MB per bus */ | ||||
SYSRES_MEM(PCI_EMUL_ECFG_BASE, PCI_EMUL_ECFG_SIZE); | SYSRES_MEM(PCI_EMUL_ECFG_BASE, PCI_EMUL_ECFG_SIZE); | ||||
/* | /* | ||||
* OVMF always uses 0xC0000000 as base address for 32 bit PCI MMIO. Don't | * OVMF always uses 0xC0000000 as base address for 32 bit PCI MMIO. Don't | ||||
* change this address without changing it in OVMF. | * change this address without changing it in OVMF. | ||||
*/ | */ | ||||
▲ Show 20 Lines • Show All 428 Lines • ▼ Show 20 Lines | if (registration) { | ||||
mr.arg2 = idx; | mr.arg2 = idx; | ||||
error = register_mem(&mr); | error = register_mem(&mr); | ||||
} else | } else | ||||
error = unregister_mem(&mr); | error = unregister_mem(&mr); | ||||
if (pe->pe_baraddr != NULL) | if (pe->pe_baraddr != NULL) | ||||
(*pe->pe_baraddr)(pi->pi_vmctx, pi, idx, registration, | (*pe->pe_baraddr)(pi->pi_vmctx, pi, idx, registration, | ||||
pi->pi_bar[idx].addr); | pi->pi_bar[idx].addr); | ||||
break; | break; | ||||
case PCIBAR_ROM: | |||||
error = 0; | |||||
if (pe->pe_baraddr != NULL) | |||||
(*pe->pe_baraddr)(pi->pi_vmctx, pi, idx, registration, | |||||
pi->pi_bar[idx].addr); | |||||
break; | |||||
default: | default: | ||||
error = EINVAL; | error = EINVAL; | ||||
break; | break; | ||||
} | } | ||||
assert(error == 0); | assert(error == 0); | ||||
} | } | ||||
static void | static void | ||||
unregister_bar(struct pci_devinst *pi, int idx) | unregister_bar(struct pci_devinst *pi, int idx) | ||||
{ | { | ||||
modify_bar_registration(pi, idx, 0); | modify_bar_registration(pi, idx, 0); | ||||
} | } | ||||
static void | static void | ||||
register_bar(struct pci_devinst *pi, int idx) | register_bar(struct pci_devinst *pi, int idx) | ||||
{ | { | ||||
modify_bar_registration(pi, idx, 1); | modify_bar_registration(pi, idx, 1); | ||||
} | } | ||||
/* Is the ROM enabled for the emulated pci device? */ | |||||
static int | |||||
romen(struct pci_devinst *pi) | |||||
{ | |||||
return (pi->pi_bar[PCI_ROM_IDX].lobits & PCIM_BIOS_ENABLE) == | |||||
PCIM_BIOS_ENABLE; | |||||
} | |||||
/* Are we decoding i/o port accesses for the emulated pci device? */ | /* Are we decoding i/o port accesses for the emulated pci device? */ | ||||
static int | static int | ||||
porten(struct pci_devinst *pi) | porten(struct pci_devinst *pi) | ||||
{ | { | ||||
uint16_t cmd; | uint16_t cmd; | ||||
cmd = pci_get_cfgdata16(pi, PCIR_COMMAND); | cmd = pci_get_cfgdata16(pi, PCIR_COMMAND); | ||||
▲ Show 20 Lines • Show All 50 Lines • ▼ Show 20 Lines | update_bar_address(struct pci_devinst *pi, uint64_t addr, int idx, int type) | ||||
if (decode) | if (decode) | ||||
register_bar(pi, idx); | register_bar(pi, idx); | ||||
} | } | ||||
int | int | ||||
pci_emul_alloc_bar(struct pci_devinst *pdi, int idx, enum pcibar_type type, | pci_emul_alloc_bar(struct pci_devinst *pdi, int idx, enum pcibar_type type, | ||||
uint64_t size) | uint64_t size) | ||||
{ | { | ||||
assert(idx >= 0 && idx <= PCI_BARMAX); | assert((type == PCIBAR_ROM) || (idx >= 0 && idx <= PCI_BARMAX)); | ||||
assert((type != PCIBAR_ROM) || (idx == PCI_ROM_IDX)); | |||||
if ((size & (size - 1)) != 0) | if ((size & (size - 1)) != 0) | ||||
size = 1UL << flsl(size); /* round up to a power of 2 */ | size = 1UL << flsl(size); /* round up to a power of 2 */ | ||||
/* Enforce minimum BAR sizes required by the PCI standard */ | /* Enforce minimum BAR sizes required by the PCI standard */ | ||||
if (type == PCIBAR_IO) { | if (type == PCIBAR_IO) { | ||||
if (size < 4) | if (size < 4) | ||||
size = 4; | size = 4; | ||||
} else if (type == PCIBAR_ROM) { | |||||
if (size < ~PCIM_BIOS_ADDR_MASK + 1) | |||||
size = ~PCIM_BIOS_ADDR_MASK + 1; | |||||
} else { | } else { | ||||
if (size < 16) | if (size < 16) | ||||
size = 16; | size = 16; | ||||
} | } | ||||
/* | /* | ||||
* To reduce fragmentation of the MMIO space, we allocate the BARs by | * To reduce fragmentation of the MMIO space, we allocate the BARs by | ||||
* size. Therefore, don't allocate the BAR yet. We create a list of all | * size. Therefore, don't allocate the BAR yet. We create a list of all | ||||
▲ Show 20 Lines • Show All 99 Lines • ▼ Show 20 Lines | case PCIBAR_MEM64: | ||||
} | } | ||||
break; | break; | ||||
case PCIBAR_MEM32: | case PCIBAR_MEM32: | ||||
baseptr = &pci_emul_membase32; | baseptr = &pci_emul_membase32; | ||||
limit = PCI_EMUL_MEMLIMIT32; | limit = PCI_EMUL_MEMLIMIT32; | ||||
mask = PCIM_BAR_MEM_BASE; | mask = PCIM_BAR_MEM_BASE; | ||||
lobits = PCIM_BAR_MEM_SPACE | PCIM_BAR_MEM_32; | lobits = PCIM_BAR_MEM_SPACE | PCIM_BAR_MEM_32; | ||||
break; | break; | ||||
case PCIBAR_ROM: | |||||
/* do not claim memory for ROM. OVMF will do it for us. */ | |||||
baseptr = NULL; | |||||
limit = 0; | |||||
mask = PCIM_BIOS_ADDR_MASK; | |||||
lobits = 0; | |||||
break; | |||||
andy_omniosce.org: Porting this over to illumos, and the compiler is saying:
```
pci_emul.c:835:24: error: 'addr'… | |||||
Done Inline ActionsYou're right. I think I've missed it because guests usually write to the ROM register before reading it. We should set addr = 0 in this case block. corvink: You're right. I think I've missed it because guests usually write to the ROM register before… | |||||
Done Inline ActionsCoverity also flagged this -- CID 1486777. cem: Coverity also flagged this -- CID 1486777. | |||||
Done Inline Actionscorvink: D34688 | |||||
default: | default: | ||||
printf("pci_emul_alloc_base: invalid bar type %d\n", type); | printf("pci_emul_alloc_base: invalid bar type %d\n", type); | ||||
assert(0); | assert(0); | ||||
} | } | ||||
if (baseptr != NULL) { | if (baseptr != NULL) { | ||||
error = pci_emul_alloc_resource(baseptr, limit, size, &addr); | error = pci_emul_alloc_resource(baseptr, limit, size, &addr); | ||||
if (error != 0) | if (error != 0) | ||||
Show All 18 Lines | pci_emul_assign_bar(struct pci_devinst *const pdi, const int idx, | ||||
pci_set_cfgdata32(pdi, PCIR_BAR(idx), bar); | pci_set_cfgdata32(pdi, PCIR_BAR(idx), bar); | ||||
if (type == PCIBAR_MEM64) { | if (type == PCIBAR_MEM64) { | ||||
assert(idx + 1 <= PCI_BARMAX); | assert(idx + 1 <= PCI_BARMAX); | ||||
pdi->pi_bar[idx + 1].type = PCIBAR_MEMHI64; | pdi->pi_bar[idx + 1].type = PCIBAR_MEMHI64; | ||||
pci_set_cfgdata32(pdi, PCIR_BAR(idx + 1), bar >> 32); | pci_set_cfgdata32(pdi, PCIR_BAR(idx + 1), bar >> 32); | ||||
} | } | ||||
if (type != PCIBAR_ROM) { | |||||
register_bar(pdi, idx); | register_bar(pdi, idx); | ||||
} | |||||
return (0); | return (0); | ||||
} | } | ||||
int | |||||
pci_emul_alloc_rom(struct pci_devinst *const pdi, const uint64_t size, | |||||
void **const addr) | |||||
{ | |||||
/* allocate ROM space once on first call */ | |||||
if (pci_emul_rombase == 0) { | |||||
pci_emul_rombase = vm_create_devmem(pdi->pi_vmctx, VM_PCIROM, | |||||
"pcirom", PCI_EMUL_ROMSIZE); | |||||
Done Inline ActionsLet's avoid conflating uint64_t with pointer types. This will not work on CHERI, where pointers store additional metadata that is lost when downcasting to uint64_t. Use a pointer here and return it to the caller, which can cast when storing the address in psc_bar. markj: Let's avoid conflating uint64_t with pointer types. This will not work on CHERI, where pointers… | |||||
if (pci_emul_rombase == MAP_FAILED) { | |||||
warnx("%s: failed to create rom segment", __func__); | |||||
Done Inline ActionsAgain here, it seems better to print a warning closer to the source of the error (note you can use warnc() to give a better hint) rather than returning a negative errno value that gets converted to -1 anyway. markj: Again here, it seems better to print a warning closer to the source of the error (note you can… | |||||
return (-1); | |||||
} | |||||
pci_emul_romlim = pci_emul_rombase + PCI_EMUL_ROMSIZE; | |||||
pci_emul_romoffset = 0; | |||||
} | |||||
/* ROM size should be a power of 2 and greater than 2 KB */ | |||||
const uint64_t rom_size = MAX(1UL << flsl(size), | |||||
~PCIM_BIOS_ADDR_MASK + 1); | |||||
/* check if ROM fits into ROM space */ | |||||
if (pci_emul_romoffset + rom_size > PCI_EMUL_ROMSIZE) { | |||||
warnx("%s: no space left in rom segment:", __func__); | |||||
warnx("%16lu bytes left", | |||||
PCI_EMUL_ROMSIZE - pci_emul_romoffset); | |||||
warnx("%16lu bytes required by %d/%d/%d", rom_size, pdi->pi_bus, | |||||
pdi->pi_slot, pdi->pi_func); | |||||
return (-1); | |||||
} | |||||
/* allocate ROM BAR */ | |||||
const int error = pci_emul_alloc_bar(pdi, PCI_ROM_IDX, PCIBAR_ROM, | |||||
rom_size); | |||||
if (error) | |||||
return error; | |||||
/* return address */ | |||||
*addr = pci_emul_rombase + pci_emul_romoffset; | |||||
/* save offset into ROM Space */ | |||||
pdi->pi_romoffset = pci_emul_romoffset; | |||||
/* increase offset for next ROM */ | |||||
pci_emul_romoffset += rom_size; | |||||
return (0); | |||||
} | |||||
#define CAP_START_OFFSET 0x40 | #define CAP_START_OFFSET 0x40 | ||||
static int | static int | ||||
pci_emul_add_capability(struct pci_devinst *pi, u_char *capdata, int caplen) | pci_emul_add_capability(struct pci_devinst *pi, u_char *capdata, int caplen) | ||||
{ | { | ||||
int i, capoff, reallen; | int i, capoff, reallen; | ||||
uint16_t sts; | uint16_t sts; | ||||
assert(caplen > 0); | assert(caplen > 0); | ||||
▲ Show 20 Lines • Show All 1,057 Lines • ▼ Show 20 Lines | pci_emul_cmd_changed(struct pci_devinst *pi, uint16_t old) | ||||
new = pci_get_cfgdata16(pi, PCIR_COMMAND); | new = pci_get_cfgdata16(pi, PCIR_COMMAND); | ||||
changed = old ^ new; | changed = old ^ new; | ||||
/* | /* | ||||
* If the MMIO or I/O address space decoding has changed then | * If the MMIO or I/O address space decoding has changed then | ||||
* register/unregister all BARs that decode that address space. | * register/unregister all BARs that decode that address space. | ||||
*/ | */ | ||||
for (i = 0; i <= PCI_BARMAX; i++) { | for (i = 0; i <= PCI_BARMAX_WITH_ROM; i++) { | ||||
switch (pi->pi_bar[i].type) { | switch (pi->pi_bar[i].type) { | ||||
case PCIBAR_NONE: | case PCIBAR_NONE: | ||||
case PCIBAR_MEMHI64: | case PCIBAR_MEMHI64: | ||||
break; | break; | ||||
case PCIBAR_IO: | case PCIBAR_IO: | ||||
/* I/O address space decoding changed? */ | /* I/O address space decoding changed? */ | ||||
if (changed & PCIM_CMD_PORTEN) { | if (changed & PCIM_CMD_PORTEN) { | ||||
if (new & PCIM_CMD_PORTEN) | if (new & PCIM_CMD_PORTEN) | ||||
register_bar(pi, i); | register_bar(pi, i); | ||||
else | else | ||||
unregister_bar(pi, i); | unregister_bar(pi, i); | ||||
} | } | ||||
break; | break; | ||||
case PCIBAR_ROM: | |||||
/* skip (un-)register of ROM if it disabled */ | |||||
if (!romen(pi)) | |||||
break; | |||||
/* fallthrough */ | |||||
case PCIBAR_MEM32: | case PCIBAR_MEM32: | ||||
case PCIBAR_MEM64: | case PCIBAR_MEM64: | ||||
/* MMIO address space decoding changed? */ | /* MMIO address space decoding changed? */ | ||||
if (changed & PCIM_CMD_MEMEN) { | if (changed & PCIM_CMD_MEMEN) { | ||||
if (new & PCIM_CMD_MEMEN) | if (new & PCIM_CMD_MEMEN) | ||||
register_bar(pi, i); | register_bar(pi, i); | ||||
else | else | ||||
unregister_bar(pi, i); | unregister_bar(pi, i); | ||||
▲ Show 20 Lines • Show All 104 Lines • ▼ Show 20 Lines | if (in) { | ||||
pci_emul_hdrtype_fixup(bus, slot, coff, bytes, eax); | pci_emul_hdrtype_fixup(bus, slot, coff, bytes, eax); | ||||
} else { | } else { | ||||
/* Let the device emulation override the default handler */ | /* Let the device emulation override the default handler */ | ||||
if (pe->pe_cfgwrite != NULL && | if (pe->pe_cfgwrite != NULL && | ||||
(*pe->pe_cfgwrite)(ctx, vcpu, pi, coff, bytes, *eax) == 0) | (*pe->pe_cfgwrite)(ctx, vcpu, pi, coff, bytes, *eax) == 0) | ||||
return; | return; | ||||
/* | /* | ||||
* Special handling for write to BAR registers | * Special handling for write to BAR and ROM registers | ||||
*/ | */ | ||||
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 && coff < PCIR_BIOS + 4)) { | |||||
/* | /* | ||||
* Ignore writes to BAR registers that are not | * Ignore writes to BAR registers that are not | ||||
* 4-byte aligned. | * 4-byte aligned. | ||||
*/ | */ | ||||
if (bytes != 4 || (coff & 0x3) != 0) | if (bytes != 4 || (coff & 0x3) != 0) | ||||
return; | return; | ||||
if (coff != PCIR_BIOS) { | |||||
idx = (coff - PCIR_BAR(0)) / 4; | idx = (coff - PCIR_BAR(0)) / 4; | ||||
} else { | |||||
idx = PCI_ROM_IDX; | |||||
} | |||||
mask = ~(pi->pi_bar[idx].size - 1); | mask = ~(pi->pi_bar[idx].size - 1); | ||||
switch (pi->pi_bar[idx].type) { | switch (pi->pi_bar[idx].type) { | ||||
case PCIBAR_NONE: | case PCIBAR_NONE: | ||||
pi->pi_bar[idx].addr = bar = 0; | pi->pi_bar[idx].addr = bar = 0; | ||||
break; | break; | ||||
Done Inline ActionsPorting to illumos, I'm getting: pci_emul.c:2123 pci_cfgrw() error: buffer overflow 'pi->pi_bar' 7 <= 8 and several more. I think line 2118 should be: if (coff < PCIR_BIOS) andy_omniosce.org: Porting to illumos, I'm getting:
```
pci_emul.c:2123 pci_cfgrw() error: buffer overflow 'pi… | |||||
Done Inline ActionsWrites that aren't 4 byte aligned, are ignored. For that reason, if (coff != PCIR_BIOS) works as intented. However, making it more clear might be a good idea. corvink: Writes that aren't 4 byte aligned, are ignored. For that reason, `if (coff != PCIR_BIOS)` works… | |||||
Done Inline Actionscorvink: D34689 | |||||
case PCIBAR_IO: | case PCIBAR_IO: | ||||
addr = *eax & mask; | addr = *eax & mask; | ||||
addr &= 0xffff; | addr &= 0xffff; | ||||
bar = addr | pi->pi_bar[idx].lobits; | bar = addr | pi->pi_bar[idx].lobits; | ||||
/* | /* | ||||
* Register the new BAR value for interception | * Register the new BAR value for interception | ||||
*/ | */ | ||||
if (addr != pi->pi_bar[idx].addr) { | if (addr != pi->pi_bar[idx].addr) { | ||||
Show All 20 Lines | if ((coff >= PCIR_BAR(0) && coff < PCIR_BAR(PCI_BARMAX + 1)) || | ||||
case PCIBAR_MEMHI64: | case PCIBAR_MEMHI64: | ||||
mask = ~(pi->pi_bar[idx - 1].size - 1); | mask = ~(pi->pi_bar[idx - 1].size - 1); | ||||
addr = ((uint64_t)*eax << 32) & mask; | addr = ((uint64_t)*eax << 32) & mask; | ||||
bar = addr >> 32; | bar = addr >> 32; | ||||
if (bar != pi->pi_bar[idx - 1].addr >> 32) { | if (bar != pi->pi_bar[idx - 1].addr >> 32) { | ||||
update_bar_address(pi, addr, idx - 1, | update_bar_address(pi, addr, idx - 1, | ||||
PCIBAR_MEMHI64); | PCIBAR_MEMHI64); | ||||
} | } | ||||
break; | |||||
case PCIBAR_ROM: | |||||
addr = bar = *eax & mask; | |||||
if (memen(pi) && romen(pi)) { | |||||
unregister_bar(pi, idx); | |||||
} | |||||
pi->pi_bar[idx].addr = addr; | |||||
pi->pi_bar[idx].lobits = *eax & | |||||
PCIM_BIOS_ENABLE; | |||||
/* romen could have changed it value */ | |||||
if (memen(pi) && romen(pi)) { | |||||
register_bar(pi, idx); | |||||
} | |||||
bar |= pi->pi_bar[idx].lobits; | |||||
break; | break; | ||||
default: | default: | ||||
assert(0); | assert(0); | ||||
} | } | ||||
pci_set_cfgdata32(pi, coff, bar); | pci_set_cfgdata32(pi, coff, bar); | ||||
} else if (pci_emul_iscap(pi, coff)) { | } else if (pci_emul_iscap(pi, coff)) { | ||||
pci_emul_capwrite(pi, coff, bytes, *eax, 0, 0); | pci_emul_capwrite(pi, coff, bytes, *eax, 0, 0); | ||||
▲ Show 20 Lines • Show All 437 Lines • Show Last 20 Lines |
Porting this over to illumos, and the compiler is saying:
Seems right - do we need to set addr = 0; in this case block?