Changeset View
Changeset View
Standalone View
Standalone View
usr.sbin/bhyve/pci_emul.c
Show First 20 Lines • Show All 49 Lines • ▼ Show 20 Lines | |||||
#include <machine/vmm.h> | #include <machine/vmm.h> | ||||
#include <machine/vmm_snapshot.h> | #include <machine/vmm_snapshot.h> | ||||
#include <machine/cpufunc.h> | #include <machine/cpufunc.h> | ||||
#include <machine/specialreg.h> | #include <machine/specialreg.h> | ||||
#include <vmmapi.h> | #include <vmmapi.h> | ||||
#include "acpi.h" | #include "acpi.h" | ||||
#include "bhyverun.h" | #include "bhyverun.h" | ||||
#include "config.h" | |||||
#include "debug.h" | #include "debug.h" | ||||
#include "inout.h" | #include "inout.h" | ||||
#include "ioapic.h" | #include "ioapic.h" | ||||
#include "mem.h" | #include "mem.h" | ||||
#include "pci_emul.h" | #include "pci_emul.h" | ||||
#include "pci_irq.h" | #include "pci_irq.h" | ||||
#include "pci_lpc.h" | #include "pci_lpc.h" | ||||
#define CONF1_ADDR_PORT 0x0cf8 | #define CONF1_ADDR_PORT 0x0cf8 | ||||
#define CONF1_DATA_PORT 0x0cfc | #define CONF1_DATA_PORT 0x0cfc | ||||
#define CONF1_ENABLE 0x80000000ul | #define CONF1_ENABLE 0x80000000ul | ||||
#define MAXBUSES (PCI_BUSMAX + 1) | #define MAXBUSES (PCI_BUSMAX + 1) | ||||
#define MAXSLOTS (PCI_SLOTMAX + 1) | #define MAXSLOTS (PCI_SLOTMAX + 1) | ||||
#define MAXFUNCS (PCI_FUNCMAX + 1) | #define MAXFUNCS (PCI_FUNCMAX + 1) | ||||
struct funcinfo { | struct funcinfo { | ||||
char *fi_name; | nvlist_t *fi_config; | ||||
char *fi_param; | struct pci_devemu *fi_pde; | ||||
struct pci_devinst *fi_devi; | struct pci_devinst *fi_devi; | ||||
}; | }; | ||||
struct intxinfo { | struct intxinfo { | ||||
int ii_count; | int ii_count; | ||||
int ii_pirq_pin; | int ii_pirq_pin; | ||||
int ii_ioapic_irq; | int ii_ioapic_irq; | ||||
}; | }; | ||||
Show All 23 Lines | |||||
#define PCI_EMUL_IOLIMIT 0x10000 | #define PCI_EMUL_IOLIMIT 0x10000 | ||||
#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); | ||||
#define PCI_EMUL_MEMLIMIT32 PCI_EMUL_ECFG_BASE | #define PCI_EMUL_MEMLIMIT32 PCI_EMUL_ECFG_BASE | ||||
static struct pci_devemu *pci_emul_finddev(char *name); | static struct pci_devemu *pci_emul_finddev(const char *name); | ||||
static void pci_lintr_route(struct pci_devinst *pi); | static void pci_lintr_route(struct pci_devinst *pi); | ||||
static void pci_lintr_update(struct pci_devinst *pi); | static void pci_lintr_update(struct pci_devinst *pi); | ||||
static void pci_cfgrw(struct vmctx *ctx, int vcpu, int in, int bus, int slot, | static void pci_cfgrw(struct vmctx *ctx, int vcpu, int in, int bus, int slot, | ||||
int func, int coff, int bytes, uint32_t *val); | int func, int coff, int bytes, uint32_t *val); | ||||
static __inline void | static __inline void | ||||
CFGWRITE(struct pci_devinst *pi, int coff, uint32_t val, int bytes) | CFGWRITE(struct pci_devinst *pi, int coff, uint32_t val, int bytes) | ||||
{ | { | ||||
Show All 39 Lines | |||||
*/ | */ | ||||
static void | static void | ||||
pci_parse_slot_usage(char *aopt) | pci_parse_slot_usage(char *aopt) | ||||
{ | { | ||||
EPRINTLN("Invalid PCI slot info field \"%s\"", aopt); | EPRINTLN("Invalid PCI slot info field \"%s\"", aopt); | ||||
} | } | ||||
/* | |||||
* Helper function to parse a list of comma-separated options where | |||||
* each option is formatted as "name[=value]". If no value is | |||||
* provided, the option is treated as a boolean and is given a value | |||||
* of true. | |||||
*/ | |||||
int | int | ||||
pci_parse_legacy_config(nvlist_t *nvl, const char *opt) | |||||
{ | |||||
char *config, *name, *tofree, *value; | |||||
if (opt == NULL) | |||||
return (0); | |||||
config = tofree = strdup(opt); | |||||
while ((name = strsep(&config, ",")) != NULL) { | |||||
value = strchr(name, '='); | |||||
if (value != NULL) { | |||||
*value = '\0'; | |||||
value++; | |||||
set_config_value_node(nvl, name, value); | |||||
} else | |||||
set_config_bool_node(nvl, name, true); | |||||
} | |||||
free(tofree); | |||||
return (0); | |||||
} | |||||
/* | |||||
* PCI device configuration is stored in MIBs that encode the device's | |||||
* location: | |||||
* | |||||
* pci.<bus>.<slot>.<func> | |||||
* | |||||
* Where "bus", "slot", and "func" are all decimal values without | |||||
* leading zeroes. Each valid device must have a "device" node which | |||||
* identifies the driver model of the device. | |||||
* | |||||
* Device backends can provide a parser for the "config" string. If | |||||
* a custom parser is not provided, pci_parse_legacy_config() is used | |||||
* to parse the string. | |||||
*/ | |||||
int | |||||
pci_parse_slot(char *opt) | pci_parse_slot(char *opt) | ||||
{ | { | ||||
struct businfo *bi; | char node_name[sizeof("pci.XXX.XX.X")]; | ||||
struct slotinfo *si; | struct pci_devemu *pde; | ||||
char *emul, *config, *str, *cp; | char *emul, *config, *str, *cp; | ||||
int error, bnum, snum, fnum; | int error, bnum, snum, fnum; | ||||
nvlist_t *nvl; | |||||
error = -1; | error = -1; | ||||
str = strdup(opt); | str = strdup(opt); | ||||
emul = config = NULL; | emul = config = NULL; | ||||
if ((cp = strchr(str, ',')) != NULL) { | if ((cp = strchr(str, ',')) != NULL) { | ||||
*cp = '\0'; | *cp = '\0'; | ||||
emul = cp + 1; | emul = cp + 1; | ||||
Show All 20 Lines | pci_parse_slot(char *opt) | ||||
} | } | ||||
if (bnum < 0 || bnum >= MAXBUSES || snum < 0 || snum >= MAXSLOTS || | if (bnum < 0 || bnum >= MAXBUSES || snum < 0 || snum >= MAXSLOTS || | ||||
fnum < 0 || fnum >= MAXFUNCS) { | fnum < 0 || fnum >= MAXFUNCS) { | ||||
pci_parse_slot_usage(opt); | pci_parse_slot_usage(opt); | ||||
goto done; | goto done; | ||||
} | } | ||||
if (pci_businfo[bnum] == NULL) | pde = pci_emul_finddev(emul); | ||||
pci_businfo[bnum] = calloc(1, sizeof(struct businfo)); | if (pde == NULL) { | ||||
EPRINTLN("pci slot %d:%d:%d: unknown device \"%s\"", bnum, snum, | |||||
bi = pci_businfo[bnum]; | fnum, emul); | ||||
si = &bi->slotinfo[snum]; | |||||
if (si->si_funcs[fnum].fi_name != NULL) { | |||||
EPRINTLN("pci slot %d:%d already occupied!", | |||||
snum, fnum); | |||||
goto done; | goto done; | ||||
} | } | ||||
if (pci_emul_finddev(emul) == NULL) { | snprintf(node_name, sizeof(node_name), "pci.%d.%d.%d", bnum, snum, | ||||
EPRINTLN("pci slot %d:%d: unknown device \"%s\"", | fnum); | ||||
snum, fnum, emul); | nvl = find_config_node(node_name); | ||||
if (nvl != NULL) { | |||||
EPRINTLN("pci slot %d:%d:%d already occupied!", bnum, snum, | |||||
fnum); | |||||
goto done; | goto done; | ||||
} | } | ||||
nvl = create_config_node(node_name); | |||||
if (pde->pe_alias != NULL) | |||||
set_config_value_node(nvl, "device", pde->pe_alias); | |||||
else | |||||
set_config_value_node(nvl, "device", pde->pe_emu); | |||||
error = 0; | if (pde->pe_legacy_config != NULL) | ||||
si->si_funcs[fnum].fi_name = emul; | error = pde->pe_legacy_config(nvl, config); | ||||
si->si_funcs[fnum].fi_param = config; | else | ||||
error = pci_parse_legacy_config(nvl, config); | |||||
done: | done: | ||||
if (error) | |||||
free(str); | free(str); | ||||
return (error); | return (error); | ||||
} | } | ||||
void | void | ||||
pci_print_supported_devices() | pci_print_supported_devices() | ||||
{ | { | ||||
struct pci_devemu **pdpp, *pdp; | struct pci_devemu **pdpp, *pdp; | ||||
▲ Show 20 Lines • Show All 459 Lines • ▼ Show 20 Lines | pci_emul_add_capability(struct pci_devinst *pi, u_char *capdata, int caplen) | ||||
pci_set_cfgdata8(pi, capoff + 1, 0); | pci_set_cfgdata8(pi, capoff + 1, 0); | ||||
pi->pi_prevcap = capoff; | pi->pi_prevcap = capoff; | ||||
pi->pi_capend = capoff + reallen - 1; | pi->pi_capend = capoff + reallen - 1; | ||||
return (0); | return (0); | ||||
} | } | ||||
static struct pci_devemu * | static struct pci_devemu * | ||||
pci_emul_finddev(char *name) | pci_emul_finddev(const char *name) | ||||
{ | { | ||||
struct pci_devemu **pdpp, *pdp; | struct pci_devemu **pdpp, *pdp; | ||||
SET_FOREACH(pdpp, pci_devemu_set) { | SET_FOREACH(pdpp, pci_devemu_set) { | ||||
pdp = *pdpp; | pdp = *pdpp; | ||||
if (!strcmp(pdp->pe_emu, name)) { | if (!strcmp(pdp->pe_emu, name)) { | ||||
return (pdp); | return (pdp); | ||||
} | } | ||||
Show All 24 Lines | pci_emul_init(struct vmctx *ctx, struct pci_devemu *pde, int bus, int slot, | ||||
snprintf(pdi->pi_name, PI_NAMESZ, "%s-pci-%d", pde->pe_emu, slot); | snprintf(pdi->pi_name, PI_NAMESZ, "%s-pci-%d", pde->pe_emu, slot); | ||||
/* Disable legacy interrupts */ | /* Disable legacy interrupts */ | ||||
pci_set_cfgdata8(pdi, PCIR_INTLINE, 255); | pci_set_cfgdata8(pdi, PCIR_INTLINE, 255); | ||||
pci_set_cfgdata8(pdi, PCIR_INTPIN, 0); | pci_set_cfgdata8(pdi, PCIR_INTPIN, 0); | ||||
pci_set_cfgdata8(pdi, PCIR_COMMAND, PCIM_CMD_BUSMASTEREN); | pci_set_cfgdata8(pdi, PCIR_COMMAND, PCIM_CMD_BUSMASTEREN); | ||||
err = (*pde->pe_init)(ctx, pdi, fi->fi_param); | err = (*pde->pe_init)(ctx, pdi, fi->fi_config); | ||||
if (err == 0) | if (err == 0) | ||||
fi->fi_devi = pdi; | fi->fi_devi = pdi; | ||||
else | else | ||||
free(pdi); | free(pdi); | ||||
return (err); | return (err); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 312 Lines • ▼ Show 20 Lines | |||||
} | } | ||||
#define BUSIO_ROUNDUP 32 | #define BUSIO_ROUNDUP 32 | ||||
#define BUSMEM_ROUNDUP (1024 * 1024) | #define BUSMEM_ROUNDUP (1024 * 1024) | ||||
int | int | ||||
init_pci(struct vmctx *ctx) | init_pci(struct vmctx *ctx) | ||||
{ | { | ||||
char node_name[sizeof("pci.XXX.XX.X")]; | |||||
struct mem_range mr; | struct mem_range mr; | ||||
struct pci_devemu *pde; | struct pci_devemu *pde; | ||||
struct businfo *bi; | struct businfo *bi; | ||||
struct slotinfo *si; | struct slotinfo *si; | ||||
struct funcinfo *fi; | struct funcinfo *fi; | ||||
nvlist_t *nvl; | |||||
const char *emul; | |||||
size_t lowmem; | size_t lowmem; | ||||
uint64_t cpu_maxphysaddr, pci_emul_memresv64; | uint64_t cpu_maxphysaddr, pci_emul_memresv64; | ||||
u_int regs[4]; | u_int regs[4]; | ||||
int bus, slot, func, error; | int bus, slot, func, error; | ||||
pci_emul_iobase = PCI_EMUL_IOBASE; | pci_emul_iobase = PCI_EMUL_IOBASE; | ||||
pci_emul_membase32 = vm_get_lowmem_limit(ctx); | pci_emul_membase32 = vm_get_lowmem_limit(ctx); | ||||
do_cpuid(0x80000008, regs); | do_cpuid(0x80000008, regs); | ||||
cpu_maxphysaddr = 1ULL << (regs[0] & 0xff); | cpu_maxphysaddr = 1ULL << (regs[0] & 0xff); | ||||
if (cpu_maxphysaddr > VM_MAXUSER_ADDRESS_LA48) | if (cpu_maxphysaddr > VM_MAXUSER_ADDRESS_LA48) | ||||
cpu_maxphysaddr = VM_MAXUSER_ADDRESS_LA48; | cpu_maxphysaddr = VM_MAXUSER_ADDRESS_LA48; | ||||
pci_emul_memresv64 = cpu_maxphysaddr / 4; | pci_emul_memresv64 = cpu_maxphysaddr / 4; | ||||
/* | /* | ||||
* Max power of 2 that is less then | * Max power of 2 that is less then | ||||
* cpu_maxphysaddr - pci_emul_memresv64. | * cpu_maxphysaddr - pci_emul_memresv64. | ||||
*/ | */ | ||||
pci_emul_membase64 = 1ULL << (flsl(cpu_maxphysaddr - | pci_emul_membase64 = 1ULL << (flsl(cpu_maxphysaddr - | ||||
pci_emul_memresv64) - 1); | pci_emul_memresv64) - 1); | ||||
pci_emul_memlim64 = cpu_maxphysaddr; | pci_emul_memlim64 = cpu_maxphysaddr; | ||||
for (bus = 0; bus < MAXBUSES; bus++) { | for (bus = 0; bus < MAXBUSES; bus++) { | ||||
if ((bi = pci_businfo[bus]) == NULL) | snprintf(node_name, sizeof(node_name), "pci.%d", bus); | ||||
nvl = find_config_node(node_name); | |||||
if (nvl == NULL) | |||||
continue; | continue; | ||||
pci_businfo[bus] = calloc(1, sizeof(struct businfo)); | |||||
bi = pci_businfo[bus]; | |||||
/* | /* | ||||
* Keep track of the i/o and memory resources allocated to | * Keep track of the i/o and memory resources allocated to | ||||
* this bus. | * this bus. | ||||
*/ | */ | ||||
bi->iobase = pci_emul_iobase; | bi->iobase = pci_emul_iobase; | ||||
bi->membase32 = pci_emul_membase32; | bi->membase32 = pci_emul_membase32; | ||||
bi->membase64 = pci_emul_membase64; | bi->membase64 = pci_emul_membase64; | ||||
for (slot = 0; slot < MAXSLOTS; slot++) { | for (slot = 0; slot < MAXSLOTS; slot++) { | ||||
si = &bi->slotinfo[slot]; | si = &bi->slotinfo[slot]; | ||||
for (func = 0; func < MAXFUNCS; func++) { | for (func = 0; func < MAXFUNCS; func++) { | ||||
fi = &si->si_funcs[func]; | fi = &si->si_funcs[func]; | ||||
if (fi->fi_name == NULL) | snprintf(node_name, sizeof(node_name), | ||||
"pci.%d.%d.%d", bus, slot, func); | |||||
nvl = find_config_node(node_name); | |||||
if (nvl == NULL) | |||||
continue; | continue; | ||||
pde = pci_emul_finddev(fi->fi_name); | |||||
assert(pde != NULL); | fi->fi_config = nvl; | ||||
emul = get_config_value_node(nvl, "device"); | |||||
if (emul == NULL) { | |||||
EPRINTLN("pci slot %d:%d:%d: missing " | |||||
"\"device\" value", bus, slot, func); | |||||
return (EINVAL); | |||||
} | |||||
pde = pci_emul_finddev(emul); | |||||
if (pde == NULL) { | |||||
EPRINTLN("pci slot %d:%d:%d: unknown " | |||||
"device \"%s\"", bus, slot, func, | |||||
emul); | |||||
return (EINVAL); | |||||
} | |||||
if (pde->pe_alias != NULL) { | |||||
EPRINTLN("pci slot %d:%d:%d: legacy " | |||||
"device \"%s\", use \"%s\" instead", | |||||
bus, slot, func, emul, | |||||
pde->pe_alias); | |||||
return (EINVAL); | |||||
} | |||||
fi->fi_pde = pde; | |||||
error = pci_emul_init(ctx, pde, bus, slot, | error = pci_emul_init(ctx, pde, bus, slot, | ||||
func, fi); | func, fi); | ||||
if (error) | if (error) | ||||
return (error); | return (error); | ||||
} | } | ||||
} | } | ||||
/* | /* | ||||
▲ Show 20 Lines • Show All 895 Lines • ▼ Show 20 Lines | pci_find_slotted_dev(const char *dev_name, struct pci_devemu **pde, | ||||
for (bus = 0; bus < MAXBUSES; bus++) { | for (bus = 0; bus < MAXBUSES; bus++) { | ||||
if ((bi = pci_businfo[bus]) == NULL) | if ((bi = pci_businfo[bus]) == NULL) | ||||
continue; | continue; | ||||
for (slot = 0; slot < MAXSLOTS; slot++) { | for (slot = 0; slot < MAXSLOTS; slot++) { | ||||
si = &bi->slotinfo[slot]; | si = &bi->slotinfo[slot]; | ||||
for (func = 0; func < MAXFUNCS; func++) { | for (func = 0; func < MAXFUNCS; func++) { | ||||
fi = &si->si_funcs[func]; | fi = &si->si_funcs[func]; | ||||
if (fi->fi_name == NULL) | if (fi->fi_pde == NULL) | ||||
continue; | continue; | ||||
if (strcmp(dev_name, fi->fi_name)) | if (strcmp(dev_name, fi->fi_pde->pe_emu) != 0) | ||||
continue; | continue; | ||||
*pde = pci_emul_finddev(fi->fi_name); | *pde = fi->fi_pde; | ||||
assert(*pde != NULL); | |||||
*pdi = fi->fi_devi; | *pdi = fi->fi_devi; | ||||
return (0); | return (0); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
return (EINVAL); | return (EINVAL); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 105 Lines • ▼ Show 20 Lines | struct pci_emul_dsoftc { | ||||
uint8_t ioregs[DIOSZ]; | uint8_t ioregs[DIOSZ]; | ||||
uint8_t memregs[2][DMEMSZ]; | uint8_t memregs[2][DMEMSZ]; | ||||
}; | }; | ||||
#define PCI_EMUL_MSI_MSGS 4 | #define PCI_EMUL_MSI_MSGS 4 | ||||
#define PCI_EMUL_MSIX_MSGS 16 | #define PCI_EMUL_MSIX_MSGS 16 | ||||
static int | static int | ||||
pci_emul_dinit(struct vmctx *ctx, struct pci_devinst *pi, char *opts) | pci_emul_dinit(struct vmctx *ctx, struct pci_devinst *pi, nvlist_t *nvl) | ||||
{ | { | ||||
int error; | int error; | ||||
struct pci_emul_dsoftc *sc; | struct pci_emul_dsoftc *sc; | ||||
sc = calloc(1, sizeof(struct pci_emul_dsoftc)); | sc = calloc(1, sizeof(struct pci_emul_dsoftc)); | ||||
pi->pi_arg = sc; | pi->pi_arg = sc; | ||||
▲ Show 20 Lines • Show All 165 Lines • Show Last 20 Lines |