diff options
author | jhb <jhb@FreeBSD.org> | 2014-07-22 03:14:37 +0000 |
---|---|---|
committer | jhb <jhb@FreeBSD.org> | 2014-07-22 03:14:37 +0000 |
commit | c1fe945ebd0d209b238eb98b47c40c115576f2e4 (patch) | |
tree | 60ebdda4ab095737aad1e22d1f84da4708e9a68c | |
parent | 87624b14336f152a9daf40db972abf2f70cf951d (diff) | |
download | FreeBSD-src-c1fe945ebd0d209b238eb98b47c40c115576f2e4.zip FreeBSD-src-c1fe945ebd0d209b238eb98b47c40c115576f2e4.tar.gz |
MFC 266125:
Implement a PCI interrupt router to route PCI legacy INTx interrupts to
the legacy 8259A PICs.
-rw-r--r-- | lib/libvmmapi/vmmapi.c | 14 | ||||
-rw-r--r-- | lib/libvmmapi/vmmapi.h | 2 | ||||
-rw-r--r-- | sys/amd64/include/vmm.h | 5 | ||||
-rw-r--r-- | sys/amd64/include/vmm_dev.h | 8 | ||||
-rw-r--r-- | sys/amd64/vmm/io/vatpic.c | 37 | ||||
-rw-r--r-- | sys/amd64/vmm/io/vatpic.h | 1 | ||||
-rw-r--r-- | sys/amd64/vmm/vmm_dev.c | 6 | ||||
-rw-r--r-- | usr.sbin/bhyve/Makefile | 1 | ||||
-rw-r--r-- | usr.sbin/bhyve/acpi.c | 2 | ||||
-rw-r--r-- | usr.sbin/bhyve/acpi.h | 1 | ||||
-rw-r--r-- | usr.sbin/bhyve/bhyverun.c | 3 | ||||
-rw-r--r-- | usr.sbin/bhyve/mptbl.c | 3 | ||||
-rw-r--r-- | usr.sbin/bhyve/pci_emul.c | 177 | ||||
-rw-r--r-- | usr.sbin/bhyve/pci_emul.h | 6 | ||||
-rw-r--r-- | usr.sbin/bhyve/pci_irq.c | 349 | ||||
-rw-r--r-- | usr.sbin/bhyve/pci_irq.h | 45 | ||||
-rw-r--r-- | usr.sbin/bhyve/pci_lpc.c | 68 | ||||
-rw-r--r-- | usr.sbin/bhyve/pci_lpc.h | 2 | ||||
-rw-r--r-- | usr.sbin/bhyve/pm.c | 13 |
19 files changed, 682 insertions, 61 deletions
diff --git a/lib/libvmmapi/vmmapi.c b/lib/libvmmapi/vmmapi.c index b853ae7..5e630f8 100644 --- a/lib/libvmmapi/vmmapi.c +++ b/lib/libvmmapi/vmmapi.c @@ -507,6 +507,7 @@ int vm_isa_pulse_irq(struct vmctx *ctx, int atpic_irq, int ioapic_irq) { struct vm_isa_irq isa_irq; + bzero(&isa_irq, sizeof(struct vm_isa_irq)); isa_irq.atpic_irq = atpic_irq; isa_irq.ioapic_irq = ioapic_irq; @@ -515,6 +516,19 @@ vm_isa_pulse_irq(struct vmctx *ctx, int atpic_irq, int ioapic_irq) } int +vm_isa_set_irq_trigger(struct vmctx *ctx, int atpic_irq, + enum vm_intr_trigger trigger) +{ + struct vm_isa_irq_trigger isa_irq_trigger; + + bzero(&isa_irq_trigger, sizeof(struct vm_isa_irq_trigger)); + isa_irq_trigger.atpic_irq = atpic_irq; + isa_irq_trigger.trigger = trigger; + + return (ioctl(ctx->fd, VM_ISA_SET_IRQ_TRIGGER, &isa_irq_trigger)); +} + +int vm_inject_nmi(struct vmctx *ctx, int vcpu) { struct vm_nmi vmnmi; diff --git a/lib/libvmmapi/vmmapi.h b/lib/libvmmapi/vmmapi.h index c1a4b35..88e9947 100644 --- a/lib/libvmmapi/vmmapi.h +++ b/lib/libvmmapi/vmmapi.h @@ -78,6 +78,8 @@ int vm_ioapic_pincount(struct vmctx *ctx, int *pincount); int vm_isa_assert_irq(struct vmctx *ctx, int atpic_irq, int ioapic_irq); int vm_isa_deassert_irq(struct vmctx *ctx, int atpic_irq, int ioapic_irq); int vm_isa_pulse_irq(struct vmctx *ctx, int atpic_irq, int ioapic_irq); +int vm_isa_set_irq_trigger(struct vmctx *ctx, int atpic_irq, + enum vm_intr_trigger trigger); int vm_inject_nmi(struct vmctx *ctx, int vcpu); int vm_capability_name2type(const char *capname); const char *vm_capability_type2name(int type); diff --git a/sys/amd64/include/vmm.h b/sys/amd64/include/vmm.h index 68240b9..50d879b 100644 --- a/sys/amd64/include/vmm.h +++ b/sys/amd64/include/vmm.h @@ -301,6 +301,11 @@ enum x2apic_state { X2APIC_STATE_LAST }; +enum vm_intr_trigger { + EDGE_TRIGGER, + LEVEL_TRIGGER +}; + /* * The 'access' field has the format specified in Table 21-2 of the Intel * Architecture Manual vol 3b. diff --git a/sys/amd64/include/vmm_dev.h b/sys/amd64/include/vmm_dev.h index fcd437f..ecafa9c 100644 --- a/sys/amd64/include/vmm_dev.h +++ b/sys/amd64/include/vmm_dev.h @@ -84,6 +84,11 @@ struct vm_isa_irq { int ioapic_irq; }; +struct vm_isa_irq_trigger { + int atpic_irq; + enum vm_intr_trigger trigger; +}; + struct vm_capability { int cpuid; enum vm_cap_type captype; @@ -213,6 +218,7 @@ enum { IOCNUM_ISA_ASSERT_IRQ = 80, IOCNUM_ISA_DEASSERT_IRQ = 81, IOCNUM_ISA_PULSE_IRQ = 82, + IOCNUM_ISA_SET_IRQ_TRIGGER = 83, }; #define VM_RUN \ @@ -253,6 +259,8 @@ enum { _IOW('v', IOCNUM_ISA_DEASSERT_IRQ, struct vm_isa_irq) #define VM_ISA_PULSE_IRQ \ _IOW('v', IOCNUM_ISA_PULSE_IRQ, struct vm_isa_irq) +#define VM_ISA_SET_IRQ_TRIGGER \ + _IOW('v', IOCNUM_ISA_SET_IRQ_TRIGGER, struct vm_isa_irq_trigger) #define VM_SET_CAPABILITY \ _IOW('v', IOCNUM_SET_CAPABILITY, struct vm_capability) #define VM_GET_CAPABILITY \ diff --git a/sys/amd64/vmm/io/vatpic.c b/sys/amd64/vmm/io/vatpic.c index 1b9e121..ee6fc84 100644 --- a/sys/amd64/vmm/io/vatpic.c +++ b/sys/amd64/vmm/io/vatpic.c @@ -446,6 +446,43 @@ vatpic_pulse_irq(struct vm *vm, int irq) return (vatpic_set_irqstate(vm, irq, IRQSTATE_PULSE)); } +int +vatpic_set_irq_trigger(struct vm *vm, int irq, enum vm_intr_trigger trigger) +{ + struct vatpic *vatpic; + + if (irq < 0 || irq > 15) + return (EINVAL); + + /* + * See comment in vatpic_elc_handler. These IRQs must be + * edge triggered. + */ + if (trigger == LEVEL_TRIGGER) { + switch (irq) { + case 0: + case 1: + case 2: + case 8: + case 13: + return (EINVAL); + } + } + + vatpic = vm_atpic(vm); + + VATPIC_LOCK(vatpic); + + if (trigger == LEVEL_TRIGGER) + vatpic->elc[irq >> 3] |= 1 << (irq & 0x7); + else + vatpic->elc[irq >> 3] &= ~(1 << (irq & 0x7)); + + VATPIC_UNLOCK(vatpic); + + return (0); +} + void vatpic_pending_intr(struct vm *vm, int *vecptr) { diff --git a/sys/amd64/vmm/io/vatpic.h b/sys/amd64/vmm/io/vatpic.h index d4d6b26c..84d5651 100644 --- a/sys/amd64/vmm/io/vatpic.h +++ b/sys/amd64/vmm/io/vatpic.h @@ -49,6 +49,7 @@ int vatpic_elc_handler(void *vm, int vcpuid, bool in, int port, int bytes, int vatpic_assert_irq(struct vm *vm, int irq); int vatpic_deassert_irq(struct vm *vm, int irq); int vatpic_pulse_irq(struct vm *vm, int irq); +int vatpic_set_irq_trigger(struct vm *vm, int irq, enum vm_intr_trigger trigger); void vatpic_pending_intr(struct vm *vm, int *vecptr); void vatpic_intr_accepted(struct vm *vm, int vector); diff --git a/sys/amd64/vmm/vmm_dev.c b/sys/amd64/vmm/vmm_dev.c index 3112c52..f1d5795 100644 --- a/sys/amd64/vmm/vmm_dev.c +++ b/sys/amd64/vmm/vmm_dev.c @@ -156,6 +156,7 @@ vmmdev_ioctl(struct cdev *cdev, u_long cmd, caddr_t data, int fflag, struct vm_lapic_msi *vmmsi; struct vm_ioapic_irq *ioapic_irq; struct vm_isa_irq *isa_irq; + struct vm_isa_irq_trigger *isa_irq_trigger; struct vm_capability *vmcap; struct vm_pptdev *pptdev; struct vm_pptdev_mmio *pptmmio; @@ -346,6 +347,11 @@ vmmdev_ioctl(struct cdev *cdev, u_long cmd, caddr_t data, int fflag, if (error == 0 && isa_irq->ioapic_irq != -1) error = vioapic_pulse_irq(sc->vm, isa_irq->ioapic_irq); break; + case VM_ISA_SET_IRQ_TRIGGER: + isa_irq_trigger = (struct vm_isa_irq_trigger *)data; + error = vatpic_set_irq_trigger(sc->vm, + isa_irq_trigger->atpic_irq, isa_irq_trigger->trigger); + break; case VM_MAP_MEMORY: seg = (struct vm_memory_segment *)data; error = vm_malloc(sc->vm, seg->gpa, seg->len); diff --git a/usr.sbin/bhyve/Makefile b/usr.sbin/bhyve/Makefile index c73cb3d..23e16cb 100644 --- a/usr.sbin/bhyve/Makefile +++ b/usr.sbin/bhyve/Makefile @@ -23,6 +23,7 @@ SRCS= \ pci_ahci.c \ pci_emul.c \ pci_hostbridge.c \ + pci_irq.c \ pci_lpc.c \ pci_passthru.c \ pci_virtio_block.c \ diff --git a/usr.sbin/bhyve/acpi.c b/usr.sbin/bhyve/acpi.c index db7f0eb..c4ec020 100644 --- a/usr.sbin/bhyve/acpi.c +++ b/usr.sbin/bhyve/acpi.c @@ -704,7 +704,7 @@ basl_fwrite_dsdt(FILE *fp) dsdt_line("DefinitionBlock (\"bhyve_dsdt.aml\", \"DSDT\", 2," "\"BHYVE \", \"BVDSDT \", 0x00000001)"); dsdt_line("{"); - dsdt_line(" Name (_S5, Package (0x02)"); + dsdt_line(" Name (_S5, Package ()"); dsdt_line(" {"); dsdt_line(" 0x05,"); dsdt_line(" Zero,"); diff --git a/usr.sbin/bhyve/acpi.h b/usr.sbin/bhyve/acpi.h index 57edc48..652164a 100644 --- a/usr.sbin/bhyve/acpi.h +++ b/usr.sbin/bhyve/acpi.h @@ -49,5 +49,6 @@ void dsdt_fixed_irq(uint8_t irq); void dsdt_fixed_mem32(uint32_t base, uint32_t length); void dsdt_indent(int levels); void dsdt_unindent(int levels); +void sci_init(struct vmctx *ctx); #endif /* _ACPI_H_ */ diff --git a/usr.sbin/bhyve/bhyverun.c b/usr.sbin/bhyve/bhyverun.c index b2354c9..d9b4418 100644 --- a/usr.sbin/bhyve/bhyverun.c +++ b/usr.sbin/bhyve/bhyverun.c @@ -60,6 +60,7 @@ __FBSDID("$FreeBSD$"); #include "mevent.h" #include "mptbl.h" #include "pci_emul.h" +#include "pci_irq.h" #include "pci_lpc.h" #include "smbiostbl.h" #include "xmsr.h" @@ -770,9 +771,11 @@ main(int argc, char *argv[]) init_mem(); init_inout(); + pci_irq_init(ctx); ioapic_init(ctx); rtc_init(ctx); + sci_init(ctx); /* * Exit if a device emulation finds an error in it's initilization diff --git a/usr.sbin/bhyve/mptbl.c b/usr.sbin/bhyve/mptbl.c index 4c2167e..904d103 100644 --- a/usr.sbin/bhyve/mptbl.c +++ b/usr.sbin/bhyve/mptbl.c @@ -210,7 +210,8 @@ mpt_count_ioint_entries(void) } static void -mpt_generate_pci_int(int bus, int slot, int pin, int ioapic_irq, void *arg) +mpt_generate_pci_int(int bus, int slot, int pin, int pirq_pin, int ioapic_irq, + void *arg) { int_entry_ptr *mpiep, mpie; diff --git a/usr.sbin/bhyve/pci_emul.c b/usr.sbin/bhyve/pci_emul.c index 5455f40..a2c47ec 100644 --- a/usr.sbin/bhyve/pci_emul.c +++ b/usr.sbin/bhyve/pci_emul.c @@ -51,6 +51,7 @@ __FBSDID("$FreeBSD$"); #include "ioapic.h" #include "mem.h" #include "pci_emul.h" +#include "pci_irq.h" #include "pci_lpc.h" #define CONF1_ADDR_PORT 0x0cf8 @@ -81,6 +82,7 @@ struct funcinfo { struct intxinfo { int ii_count; + int ii_pirq_pin; int ii_ioapic_irq; }; @@ -113,6 +115,7 @@ static uint64_t pci_emul_membase64; #define PCI_EMUL_MEMLIMIT64 0xFD00000000UL static struct pci_devemu *pci_emul_finddev(char *name); +static void pci_lintr_route(struct pci_devinst *pi); static void pci_lintr_update(struct pci_devinst *pi); static struct mem_range pci_mem_hole; @@ -714,6 +717,7 @@ pci_emul_init(struct vmctx *ctx, struct pci_devemu *pde, int bus, int slot, pthread_mutex_init(&pdi->pi_lintr.lock, NULL); pdi->pi_lintr.pin = 0; pdi->pi_lintr.state = IDLE; + pdi->pi_lintr.pirq_pin = 0; pdi->pi_lintr.ioapic_irq = 0; pdi->pi_d = pde; snprintf(pdi->pi_name, PI_NAMESZ, "%s-pci-%d", pde->pe_emu, slot); @@ -1084,6 +1088,27 @@ init_pci(struct vmctx *ctx) } /* + * PCI backends are initialized before routing INTx interrupts + * so that LPC devices are able to reserve ISA IRQs before + * routing PIRQ pins. + */ + for (bus = 0; bus < MAXBUSES; bus++) { + if ((bi = pci_businfo[bus]) == NULL) + continue; + + for (slot = 0; slot < MAXSLOTS; slot++) { + si = &bi->slotinfo[slot]; + for (func = 0; func < MAXFUNCS; func++) { + fi = &si->si_funcs[func]; + if (fi->fi_devi == NULL) + continue; + pci_lintr_route(fi->fi_devi); + } + } + } + lpc_pirq_routed(); + + /* * The guest physical memory map looks like the following: * [0, lowmem) guest system memory * [lowmem, lowmem_limit) memory hole (may be absent) @@ -1110,19 +1135,36 @@ init_pci(struct vmctx *ctx) } static void -pci_prt_entry(int bus, int slot, int pin, int ioapic_irq, void *arg) +pci_apic_prt_entry(int bus, int slot, int pin, int pirq_pin, int ioapic_irq, + void *arg) { - int *count; - count = arg; - dsdt_line(" Package (0x04)"); + dsdt_line(" Package ()"); dsdt_line(" {"); dsdt_line(" 0x%X,", slot << 16 | 0xffff); dsdt_line(" 0x%02X,", pin - 1); dsdt_line(" Zero,"); dsdt_line(" 0x%X", ioapic_irq); - dsdt_line(" }%s", *count == 1 ? "" : ","); - (*count)--; + dsdt_line(" },"); +} + +static void +pci_pirq_prt_entry(int bus, int slot, int pin, int pirq_pin, int ioapic_irq, + void *arg) +{ + char *name; + + name = lpc_pirq_name(pirq_pin); + if (name == NULL) + return; + dsdt_line(" Package ()"); + dsdt_line(" {"); + dsdt_line(" 0x%X,", slot << 16 | 0xffff); + dsdt_line(" 0x%02X,", pin - 1); + dsdt_line(" %s,", name); + dsdt_line(" 0x00"); + dsdt_line(" },"); + free(name); } /* @@ -1135,7 +1177,7 @@ pci_bus_write_dsdt(int bus) struct businfo *bi; struct slotinfo *si; struct pci_devinst *pi; - int count, slot, func; + int count, func, slot; /* * If there are no devices on this 'bus' then just return. @@ -1150,9 +1192,6 @@ pci_bus_write_dsdt(int bus) return; } - dsdt_indent(1); - dsdt_line("Scope (_SB)"); - dsdt_line("{"); dsdt_line(" Device (PC%02X)", bus); dsdt_line(" {"); dsdt_line(" Name (_HID, EisaId (\"PNP0A03\"))"); @@ -1245,10 +1284,25 @@ pci_bus_write_dsdt(int bus) count = pci_count_lintr(bus); if (count != 0) { dsdt_indent(2); - dsdt_line("Name (_PRT, Package (0x%02X)", count); + dsdt_line("Name (PPRT, Package ()"); dsdt_line("{"); - pci_walk_lintr(bus, pci_prt_entry, &count); - dsdt_line("})"); + pci_walk_lintr(bus, pci_pirq_prt_entry, NULL); + dsdt_line("})"); + dsdt_line("Name (APRT, Package ()"); + dsdt_line("{"); + pci_walk_lintr(bus, pci_apic_prt_entry, NULL); + dsdt_line("})"); + dsdt_line("Method (_PRT, 0, NotSerialized)"); + dsdt_line("{"); + dsdt_line(" If (PICM)"); + dsdt_line(" {"); + dsdt_line(" Return (APRT)"); + dsdt_line(" }"); + dsdt_line(" Else"); + dsdt_line(" {"); + dsdt_line(" Return (PPRT)"); + dsdt_line(" }"); + dsdt_line("}"); dsdt_unindent(2); } @@ -1264,8 +1318,6 @@ pci_bus_write_dsdt(int bus) dsdt_unindent(2); done: dsdt_line(" }"); - dsdt_line("}"); - dsdt_unindent(1); } void @@ -1273,8 +1325,19 @@ pci_write_dsdt(void) { int bus; + dsdt_indent(1); + dsdt_line("Name (PICM, 0x00)"); + dsdt_line("Method (_PIC, 1, NotSerialized)"); + dsdt_line("{"); + dsdt_line(" Store (Arg0, PICM)"); + dsdt_line("}"); + dsdt_line(""); + dsdt_line("Scope (_SB)"); + dsdt_line("{"); for (bus = 0; bus < MAXBUSES; bus++) pci_bus_write_dsdt(bus); + dsdt_line("}"); + dsdt_unindent(1); } int @@ -1347,18 +1410,19 @@ pci_lintr_permitted(struct pci_devinst *pi) (cmd & PCIM_CMD_INTxDIS))); } -int +void pci_lintr_request(struct pci_devinst *pi) { struct businfo *bi; struct slotinfo *si; - int bestpin, bestcount, irq, pin; + int bestpin, bestcount, pin; bi = pci_businfo[pi->pi_bus]; assert(bi != NULL); /* - * First, allocate a pin from our slot. + * Just allocate a pin from our slot. The pin will be + * assigned IRQs later when interrupts are routed. */ si = &bi->slotinfo[pi->pi_slot]; bestpin = 0; @@ -1370,26 +1434,43 @@ pci_lintr_request(struct pci_devinst *pi) } } - /* - * Attempt to allocate an I/O APIC pin for this intpin. If - * 8259A support is added we will need a separate field to - * assign the intpin to an input pin on the PCI interrupt - * router. - */ - if (si->si_intpins[bestpin].ii_count == 0) { - irq = ioapic_pci_alloc_irq(); - if (irq < 0) - return (-1); - si->si_intpins[bestpin].ii_ioapic_irq = irq; - } else - irq = si->si_intpins[bestpin].ii_ioapic_irq; si->si_intpins[bestpin].ii_count++; - pi->pi_lintr.pin = bestpin + 1; - pi->pi_lintr.ioapic_irq = irq; - pci_set_cfgdata8(pi, PCIR_INTLINE, irq); pci_set_cfgdata8(pi, PCIR_INTPIN, bestpin + 1); - return (0); +} + +static void +pci_lintr_route(struct pci_devinst *pi) +{ + struct businfo *bi; + struct intxinfo *ii; + + if (pi->pi_lintr.pin == 0) + return; + + bi = pci_businfo[pi->pi_bus]; + assert(bi != NULL); + ii = &bi->slotinfo[pi->pi_slot].si_intpins[pi->pi_lintr.pin - 1]; + + /* + * Attempt to allocate an I/O APIC pin for this intpin if one + * is not yet assigned. + */ + if (ii->ii_ioapic_irq == 0) + ii->ii_ioapic_irq = ioapic_pci_alloc_irq(); + assert(ii->ii_ioapic_irq > 0); + + /* + * Attempt to allocate a PIRQ pin for this intpin if one is + * not yet assigned. + */ + if (ii->ii_pirq_pin == 0) + ii->ii_pirq_pin = pirq_alloc_pin(pi->pi_vmctx); + assert(ii->ii_pirq_pin > 0); + + pi->pi_lintr.ioapic_irq = ii->ii_ioapic_irq; + pi->pi_lintr.pirq_pin = ii->ii_pirq_pin; + pci_set_cfgdata8(pi, PCIR_INTLINE, pirq_irq(ii->ii_pirq_pin)); } void @@ -1402,8 +1483,7 @@ pci_lintr_assert(struct pci_devinst *pi) if (pi->pi_lintr.state == IDLE) { if (pci_lintr_permitted(pi)) { pi->pi_lintr.state = ASSERTED; - vm_ioapic_assert_irq(pi->pi_vmctx, - pi->pi_lintr.ioapic_irq); + pci_irq_assert(pi); } else pi->pi_lintr.state = PENDING; } @@ -1419,7 +1499,7 @@ pci_lintr_deassert(struct pci_devinst *pi) pthread_mutex_lock(&pi->pi_lintr.lock); if (pi->pi_lintr.state == ASSERTED) { pi->pi_lintr.state = IDLE; - vm_ioapic_deassert_irq(pi->pi_vmctx, pi->pi_lintr.ioapic_irq); + pci_irq_deassert(pi); } else if (pi->pi_lintr.state == PENDING) pi->pi_lintr.state = IDLE; pthread_mutex_unlock(&pi->pi_lintr.lock); @@ -1431,11 +1511,11 @@ pci_lintr_update(struct pci_devinst *pi) pthread_mutex_lock(&pi->pi_lintr.lock); if (pi->pi_lintr.state == ASSERTED && !pci_lintr_permitted(pi)) { - vm_ioapic_deassert_irq(pi->pi_vmctx, pi->pi_lintr.ioapic_irq); + pci_irq_deassert(pi); pi->pi_lintr.state = PENDING; } else if (pi->pi_lintr.state == PENDING && pci_lintr_permitted(pi)) { pi->pi_lintr.state = ASSERTED; - vm_ioapic_assert_irq(pi->pi_vmctx, pi->pi_lintr.ioapic_irq); + pci_irq_assert(pi); } pthread_mutex_unlock(&pi->pi_lintr.lock); } @@ -1475,7 +1555,8 @@ pci_walk_lintr(int bus, pci_lintr_cb cb, void *arg) for (pin = 0; pin < 4; pin++) { ii = &si->si_intpins[pin]; if (ii->ii_count != 0) - cb(bus, slot, pin + 1, ii->ii_ioapic_irq, arg); + cb(bus, slot, pin + 1, ii->ii_pirq_pin, + ii->ii_ioapic_irq, arg); } } } @@ -1772,20 +1853,6 @@ INOUT_PORT(pci_cfgdata, CONF1_DATA_PORT+1, IOPORT_F_INOUT, pci_emul_cfgdata); INOUT_PORT(pci_cfgdata, CONF1_DATA_PORT+2, IOPORT_F_INOUT, pci_emul_cfgdata); INOUT_PORT(pci_cfgdata, CONF1_DATA_PORT+3, IOPORT_F_INOUT, pci_emul_cfgdata); -/* - * I/O ports to configure PCI IRQ routing. We ignore all writes to it. - */ -static int -pci_irq_port_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes, - uint32_t *eax, void *arg) -{ - assert(in == 0); - return (0); -} -INOUT_PORT(pci_irq, 0xC00, IOPORT_F_OUT, pci_irq_port_handler); -INOUT_PORT(pci_irq, 0xC01, IOPORT_F_OUT, pci_irq_port_handler); -SYSRES_IO(0xC00, 2); - #define PCI_EMUL_TEST #ifdef PCI_EMUL_TEST /* diff --git a/usr.sbin/bhyve/pci_emul.h b/usr.sbin/bhyve/pci_emul.h index e1040a8..866ffc5 100644 --- a/usr.sbin/bhyve/pci_emul.h +++ b/usr.sbin/bhyve/pci_emul.h @@ -120,6 +120,7 @@ struct pci_devinst { struct { int8_t pin; enum lintr_stat state; + int pirq_pin; int ioapic_irq; pthread_mutex_t lock; } pi_lintr; @@ -200,7 +201,8 @@ struct pciecap { uint16_t slot_status2; } __packed; -typedef void (*pci_lintr_cb)(int b, int s, int pin, int ioapic_irq, void *arg); +typedef void (*pci_lintr_cb)(int b, int s, int pin, int pirq_pin, + int ioapic_irq, void *arg); int init_pci(struct vmctx *ctx); void msicap_cfgwrite(struct pci_devinst *pi, int capoff, int offset, @@ -218,7 +220,7 @@ void pci_generate_msi(struct pci_devinst *pi, int msgnum); void pci_generate_msix(struct pci_devinst *pi, int msgnum); void pci_lintr_assert(struct pci_devinst *pi); void pci_lintr_deassert(struct pci_devinst *pi); -int pci_lintr_request(struct pci_devinst *pi); +void pci_lintr_request(struct pci_devinst *pi); int pci_msi_enabled(struct pci_devinst *pi); int pci_msix_enabled(struct pci_devinst *pi); int pci_msix_table_bar(struct pci_devinst *pi); diff --git a/usr.sbin/bhyve/pci_irq.c b/usr.sbin/bhyve/pci_irq.c new file mode 100644 index 0000000..653aeb0 --- /dev/null +++ b/usr.sbin/bhyve/pci_irq.c @@ -0,0 +1,349 @@ +/*- + * Copyright (c) 2014 Advanced Computing Technologies LLC + * Written by: John H. Baldwin <jhb@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <machine/vmm.h> + +#include <assert.h> +#include <pthread.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <vmmapi.h> + +#include "acpi.h" +#include "inout.h" +#include "pci_emul.h" +#include "pci_irq.h" +#include "pci_lpc.h" + +/* + * Implement an 8 pin PCI interrupt router compatible with the router + * present on Intel's ICH10 chip. + */ + +/* Fields in each PIRQ register. */ +#define PIRQ_DIS 0x80 +#define PIRQ_IRQ 0x0f + +/* Only IRQs 3-7, 9-12, and 14-15 are permitted. */ +#define PERMITTED_IRQS 0xdef8 +#define IRQ_PERMITTED(irq) (((1U << (irq)) & PERMITTED_IRQS) != 0) + +/* IRQ count to disable an IRQ. */ +#define IRQ_DISABLED 0xff + +static struct pirq { + uint8_t reg; + int use_count; + int active_count; + pthread_mutex_t lock; +} pirqs[8]; + +static u_char irq_counts[16]; +static int pirq_cold = 1; + +/* + * Returns true if this pin is enabled with a valid IRQ. Setting the + * register to a reserved IRQ causes interrupts to not be asserted as + * if the pin was disabled. + */ +static bool +pirq_valid_irq(int reg) +{ + + if (reg & PIRQ_DIS) + return (false); + return (IRQ_PERMITTED(reg & PIRQ_IRQ)); +} + +uint8_t +pirq_read(int pin) +{ + + assert(pin > 0 && pin <= nitems(pirqs)); + return (pirqs[pin - 1].reg); +} + +void +pirq_write(struct vmctx *ctx, int pin, uint8_t val) +{ + struct pirq *pirq; + + assert(pin > 0 && pin <= nitems(pirqs)); + pirq = &pirqs[pin - 1]; + pthread_mutex_lock(&pirq->lock); + if (pirq->reg != (val & (PIRQ_DIS | PIRQ_IRQ))) { + if (pirq->active_count != 0 && pirq_valid_irq(pirq->reg)) + vm_isa_deassert_irq(ctx, pirq->reg & PIRQ_IRQ, -1); + pirq->reg = val & (PIRQ_DIS | PIRQ_IRQ); + if (pirq->active_count != 0 && pirq_valid_irq(pirq->reg)) + vm_isa_assert_irq(ctx, pirq->reg & PIRQ_IRQ, -1); + } + pthread_mutex_unlock(&pirq->lock); +} + +void +pci_irq_reserve(int irq) +{ + + assert(irq < nitems(irq_counts)); + assert(pirq_cold); + assert(irq_counts[irq] == 0 || irq_counts[irq] == IRQ_DISABLED); + irq_counts[irq] = IRQ_DISABLED; +} + +void +pci_irq_use(int irq) +{ + + assert(irq < nitems(irq_counts)); + assert(pirq_cold); + if (irq_counts[irq] != IRQ_DISABLED) + irq_counts[irq]++; +} + +void +pci_irq_init(struct vmctx *ctx) +{ + int i; + + for (i = 0; i < nitems(pirqs); i++) { + pirqs[i].reg = PIRQ_DIS; + pirqs[i].use_count = 0; + pirqs[i].active_count = 0; + pthread_mutex_init(&pirqs[i].lock, NULL); + } + for (i = 0; i < nitems(irq_counts); i++) { + if (IRQ_PERMITTED(i)) + irq_counts[i] = 0; + else + irq_counts[i] = IRQ_DISABLED; + } +} + +void +pci_irq_assert(struct pci_devinst *pi) +{ + struct pirq *pirq; + + if (pi->pi_lintr.pirq_pin > 0) { + assert(pi->pi_lintr.pirq_pin <= nitems(pirqs)); + pirq = &pirqs[pi->pi_lintr.pirq_pin - 1]; + pthread_mutex_lock(&pirq->lock); + pirq->active_count++; + if (pirq->active_count == 1 && pirq_valid_irq(pirq->reg)) { + vm_isa_assert_irq(pi->pi_vmctx, pirq->reg & PIRQ_IRQ, + pi->pi_lintr.ioapic_irq); + pthread_mutex_unlock(&pirq->lock); + return; + } + pthread_mutex_unlock(&pirq->lock); + } + vm_ioapic_assert_irq(pi->pi_vmctx, pi->pi_lintr.ioapic_irq); +} + +void +pci_irq_deassert(struct pci_devinst *pi) +{ + struct pirq *pirq; + + if (pi->pi_lintr.pirq_pin > 0) { + assert(pi->pi_lintr.pirq_pin <= nitems(pirqs)); + pirq = &pirqs[pi->pi_lintr.pirq_pin - 1]; + pthread_mutex_lock(&pirq->lock); + pirq->active_count--; + if (pirq->active_count == 0 && pirq_valid_irq(pirq->reg)) { + vm_isa_deassert_irq(pi->pi_vmctx, pirq->reg & PIRQ_IRQ, + pi->pi_lintr.ioapic_irq); + pthread_mutex_unlock(&pirq->lock); + return; + } + pthread_mutex_unlock(&pirq->lock); + } + vm_ioapic_deassert_irq(pi->pi_vmctx, pi->pi_lintr.ioapic_irq); +} + +int +pirq_alloc_pin(struct vmctx *ctx) +{ + int best_count, best_irq, best_pin, irq, pin; + + pirq_cold = 1; + + /* First, find the least-used PIRQ pin. */ + best_pin = 0; + best_count = pirqs[0].use_count; + for (pin = 1; pin < nitems(pirqs); pin++) { + if (pirqs[pin].use_count < best_count) { + best_pin = pin; + best_count = pirqs[pin].use_count; + } + } + pirqs[best_pin].use_count++; + + /* Second, route this pin to an IRQ. */ + if (pirqs[best_pin].reg == PIRQ_DIS) { + best_irq = -1; + best_count = 0; + for (irq = 0; irq < nitems(irq_counts); irq++) { + if (irq_counts[irq] == IRQ_DISABLED) + continue; + if (best_irq == -1 || irq_counts[irq] < best_count) { + best_irq = irq; + best_count = irq_counts[irq]; + } + } + assert(best_irq != 0); + irq_counts[best_irq]++; + pirqs[best_pin].reg = best_irq; + vm_isa_set_irq_trigger(ctx, best_irq, LEVEL_TRIGGER); + } + + return (best_pin + 1); +} + +int +pirq_irq(int pin) +{ + + if (pin == -1) + return (255); + assert(pin > 0 && pin <= nitems(pirqs)); + return (pirqs[pin - 1].reg & PIRQ_IRQ); +} + +/* XXX: Generate $PIR table. */ + +static void +pirq_dsdt(void) +{ + char *irq_prs, *old; + int irq, pin; + + irq_prs = NULL; + for (irq = 0; irq < nitems(irq_counts); irq++) { + if (!IRQ_PERMITTED(irq)) + continue; + if (irq_prs == NULL) + asprintf(&irq_prs, "%d", irq); + else { + old = irq_prs; + asprintf(&irq_prs, "%s,%d", old, irq); + free(old); + } + } + + /* + * A helper method to validate a link register's value. This + * duplicates pirq_valid_irq(). + */ + dsdt_line(""); + dsdt_line("Method (PIRV, 1, NotSerialized)"); + dsdt_line("{"); + dsdt_line(" If (And (Arg0, 0x%02X))", PIRQ_DIS); + dsdt_line(" {"); + dsdt_line(" Return (0x00)"); + dsdt_line(" }"); + dsdt_line(" And (Arg0, 0x%02X, Local0)", PIRQ_IRQ); + dsdt_line(" If (LLess (Local0, 0x03))"); + dsdt_line(" {"); + dsdt_line(" Return (0x00)"); + dsdt_line(" }"); + dsdt_line(" If (LEqual (Local0, 0x08))"); + dsdt_line(" {"); + dsdt_line(" Return (0x00)"); + dsdt_line(" }"); + dsdt_line(" If (LEqual (Local0, 0x0D))"); + dsdt_line(" {"); + dsdt_line(" Return (0x00)"); + dsdt_line(" }"); + dsdt_line(" Return (0x01)"); + dsdt_line("}"); + + for (pin = 0; pin < nitems(pirqs); pin++) { + dsdt_line(""); + dsdt_line("Device (LNK%c)", 'A' + pin); + dsdt_line("{"); + dsdt_line(" Name (_HID, EisaId (\"PNP0C0F\"))"); + dsdt_line(" Name (_UID, 0x%02X)", pin + 1); + dsdt_line(" Method (_STA, 0, NotSerialized)"); + dsdt_line(" {"); + dsdt_line(" If (PIRV (PIR%c))", 'A' + pin); + dsdt_line(" {"); + dsdt_line(" Return (0x0B)"); + dsdt_line(" }"); + dsdt_line(" Else"); + dsdt_line(" {"); + dsdt_line(" Return (0x09)"); + dsdt_line(" }"); + dsdt_line(" }"); + dsdt_line(" Name (_PRS, ResourceTemplate ()"); + dsdt_line(" {"); + dsdt_line(" IRQ (Level, ActiveLow, Shared, )"); + dsdt_line(" {%s}", irq_prs); + dsdt_line(" })"); + dsdt_line(" Name (CB%02X, ResourceTemplate ()", pin + 1); + dsdt_line(" {"); + dsdt_line(" IRQ (Level, ActiveLow, Shared, )"); + dsdt_line(" {}"); + dsdt_line(" })"); + dsdt_line(" CreateWordField (CB%02X, 0x01, CIR%c)", + pin + 1, 'A' + pin); + dsdt_line(" Method (_CRS, 0, NotSerialized)"); + dsdt_line(" {"); + dsdt_line(" And (PIR%c, 0x%02X, Local0)", 'A' + pin, + PIRQ_DIS | PIRQ_IRQ); + dsdt_line(" If (PIRV (Local0))"); + dsdt_line(" {"); + dsdt_line(" ShiftLeft (0x01, Local0, CIR%c)", 'A' + pin); + dsdt_line(" }"); + dsdt_line(" Else"); + dsdt_line(" {"); + dsdt_line(" Store (0x00, CIR%c)", 'A' + pin); + dsdt_line(" }"); + dsdt_line(" Return (CB%02X)", pin + 1); + dsdt_line(" }"); + dsdt_line(" Method (_DIS, 0, NotSerialized)"); + dsdt_line(" {"); + dsdt_line(" Store (0x80, PIR%c)", 'A' + pin); + dsdt_line(" }"); + dsdt_line(" Method (_SRS, 1, NotSerialized)"); + dsdt_line(" {"); + dsdt_line(" CreateWordField (Arg0, 0x01, SIR%c)", 'A' + pin); + dsdt_line(" FindSetRightBit (SIR%c, Local0)", 'A' + pin); + dsdt_line(" Store (Decrement (Local0), PIR%c)", 'A' + pin); + dsdt_line(" }"); + dsdt_line("}"); + } + free(irq_prs); +} +LPC_DSDT(pirq_dsdt); diff --git a/usr.sbin/bhyve/pci_irq.h b/usr.sbin/bhyve/pci_irq.h new file mode 100644 index 0000000..9d331a5 --- /dev/null +++ b/usr.sbin/bhyve/pci_irq.h @@ -0,0 +1,45 @@ +/*- + * Copyright (c) 2014 Advanced Computing Technologies LLC + * Written by: John H. Baldwin <jhb@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef __PCI_IRQ_H__ +#define __PCI_IRQ_H__ + +struct pci_devinst; + +void pci_irq_assert(struct pci_devinst *pi); +void pci_irq_deassert(struct pci_devinst *pi); +void pci_irq_init(struct vmctx *ctx); +void pci_irq_reserve(int irq); +void pci_irq_use(int irq); +int pirq_alloc_pin(struct vmctx *ctx); +int pirq_irq(int pin); +uint8_t pirq_read(int pin); +void pirq_write(struct vmctx *ctx, int pin, uint8_t val); + +#endif diff --git a/usr.sbin/bhyve/pci_lpc.c b/usr.sbin/bhyve/pci_lpc.c index f5e4a69..6b61b7a 100644 --- a/usr.sbin/bhyve/pci_lpc.c +++ b/usr.sbin/bhyve/pci_lpc.c @@ -43,6 +43,7 @@ __FBSDID("$FreeBSD$"); #include "acpi.h" #include "inout.h" #include "pci_emul.h" +#include "pci_irq.h" #include "pci_lpc.h" #include "uart_emul.h" @@ -173,6 +174,7 @@ lpc_init(void) "LPC device %s\n", name); return (-1); } + pci_irq_reserve(sc->irq); sc->uart_softc = uart_init(lpc_uart_intr_assert, lpc_uart_intr_deassert, sc); @@ -208,7 +210,21 @@ pci_lpc_write_dsdt(struct pci_devinst *pi) dsdt_line("Device (ISA)"); dsdt_line("{"); dsdt_line(" Name (_ADR, 0x%04X%04X)", pi->pi_slot, pi->pi_func); - dsdt_line(" OperationRegion (P40C, PCI_Config, 0x60, 0x04)"); + dsdt_line(" OperationRegion (LPCR, PCI_Config, 0x00, 0x100)"); + dsdt_line(" Field (LPCR, AnyAcc, NoLock, Preserve)"); + dsdt_line(" {"); + dsdt_line(" Offset (0x60),"); + dsdt_line(" PIRA, 8,"); + dsdt_line(" PIRB, 8,"); + dsdt_line(" PIRC, 8,"); + dsdt_line(" PIRD, 8,"); + dsdt_line(" Offset (0x68),"); + dsdt_line(" PIRE, 8,"); + dsdt_line(" PIRF, 8,"); + dsdt_line(" PIRG, 8,"); + dsdt_line(" PIRH, 8"); + dsdt_line(" }"); + dsdt_line(""); dsdt_indent(1); SET_FOREACH(ldpp, lpc_dsdt_set) { @@ -305,13 +321,34 @@ pci_lpc_uart_dsdt(void) } LPC_DSDT(pci_lpc_uart_dsdt); +static int +pci_lpc_cfgwrite(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, + int coff, int bytes, uint32_t val) +{ + int pirq_pin; + + if (bytes == 1) { + pirq_pin = 0; + if (coff >= 0x60 && coff <= 0x63) + pirq_pin = coff - 0x60 + 1; + if (coff >= 0x68 && coff <= 0x6b) + pirq_pin = coff - 0x68 + 5; + if (pirq_pin != 0) { + pirq_write(ctx, pirq_pin, val); + pci_set_cfgdata8(pi, coff, pirq_read(pirq_pin)); + return (0); + } + } + return (-1); +} + static void pci_lpc_write(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, int baridx, uint64_t offset, int size, uint64_t value) { } -uint64_t +static uint64_t pci_lpc_read(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, int baridx, uint64_t offset, int size) { @@ -324,6 +361,7 @@ pci_lpc_read(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, static int pci_lpc_init(struct vmctx *ctx, struct pci_devinst *pi, char *opts) { + /* * Do not allow more than one LPC bridge to be configured. */ @@ -356,10 +394,36 @@ pci_lpc_init(struct vmctx *ctx, struct pci_devinst *pi, char *opts) return (0); } +char * +lpc_pirq_name(int pin) +{ + char *name; + + if (lpc_bridge == NULL) + return (NULL); + asprintf(&name, "\\_SB.PC00.ISA.LNK%c,", 'A' + pin - 1); + return (name); +} + +void +lpc_pirq_routed(void) +{ + int pin; + + if (lpc_bridge == NULL) + return; + + for (pin = 0; pin < 4; pin++) + pci_set_cfgdata8(lpc_bridge, 0x60 + pin, pirq_read(pin + 1)); + for (pin = 0; pin < 4; pin++) + pci_set_cfgdata8(lpc_bridge, 0x68 + pin, pirq_read(pin + 5)); +} + struct pci_devemu pci_de_lpc = { .pe_emu = "lpc", .pe_init = pci_lpc_init, .pe_write_dsdt = pci_lpc_write_dsdt, + .pe_cfgwrite = pci_lpc_cfgwrite, .pe_barwrite = pci_lpc_write, .pe_barread = pci_lpc_read }; diff --git a/usr.sbin/bhyve/pci_lpc.h b/usr.sbin/bhyve/pci_lpc.h index e45bcb9..55a5865 100644 --- a/usr.sbin/bhyve/pci_lpc.h +++ b/usr.sbin/bhyve/pci_lpc.h @@ -66,5 +66,7 @@ struct lpc_sysres { #define SYSRES_MEM(base, length) LPC_SYSRES(LPC_SYSRES_MEM, base, length) int lpc_device_parse(const char *opt); +char *lpc_pirq_name(int pin); +void lpc_pirq_routed(void); #endif diff --git a/usr.sbin/bhyve/pm.c b/usr.sbin/bhyve/pm.c index 99087e4..67126d8 100644 --- a/usr.sbin/bhyve/pm.c +++ b/usr.sbin/bhyve/pm.c @@ -39,6 +39,7 @@ __FBSDID("$FreeBSD$"); #include "acpi.h" #include "inout.h" #include "mevent.h" +#include "pci_irq.h" #include "pci_lpc.h" static pthread_mutex_t pm_lock = PTHREAD_MUTEX_INITIALIZER; @@ -289,3 +290,15 @@ smi_cmd_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes, } INOUT_PORT(smi_cmd, SMI_CMD, IOPORT_F_OUT, smi_cmd_handler); SYSRES_IO(SMI_CMD, 1); + +void +sci_init(struct vmctx *ctx) +{ + + /* + * Mark ACPI's SCI as level trigger and bump its use count + * in the PIRQ router. + */ + pci_irq_use(SCI_INT); + vm_isa_set_irq_trigger(ctx, SCI_INT, LEVEL_TRIGGER); +} |