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