Implement a PCI interrupt router to route PCI legacy INTx interrupts to

the legacy 8259A PICs.
- Implement an ICH-comptabile PCI interrupt router on the lpc device with
  8 steerable pins configured via config space access to byte-wide
  registers at 0x60-63 and 0x68-6b.
- For each configured PCI INTx interrupt, route it to both an I/O APIC
  pin and a PCI interrupt router pin.  When a PCI INTx interrupt is
  asserted, ensure that both pins are asserted.
- Provide an initial routing of PCI interrupt router (PIRQ) pins to
  8259A pins (ISA IRQs) and initialize the interrupt line config register
  for the corresponding PCI function with the ISA IRQ as this matches
  existing hardware.
- Add a global _PIC method for OSPM to select the desired interrupt routing
  configuration.
- Update the _PRT methods for PCI bridges to provide both APIC and legacy
  PRT tables and return the appropriate table based on the configured
  routing configuration.  Note that if the lpc device is not configured, no
  routing information is provided.
- When the lpc device is enabled, provide ACPI PCI link devices corresponding
  to each PIRQ pin.
- Add a VMM ioctl to adjust the trigger mode (edge vs level) for 8259A
  pins via the ELCR.
- Mark the power management SCI as level triggered.
- Don't hardcode the number of elements in Packages in the source for
  the DSDT.  iasl(8) will fill in the actual number of elements, and
  this makes it simpler to generate a Package with a variable number of
  elements.

Reviewed by:	tycho
This commit is contained in:
John Baldwin 2014-05-15 14:16:55 +00:00
parent 4f3ddacf9b
commit b3e9732a76
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=266125
19 changed files with 682 additions and 61 deletions

View File

@ -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;
@ -514,6 +515,19 @@ vm_isa_pulse_irq(struct vmctx *ctx, int atpic_irq, int ioapic_irq)
return (ioctl(ctx->fd, VM_ISA_PULSE_IRQ, &isa_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)
{

View File

@ -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);

View File

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

View File

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

View File

@ -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)
{

View File

@ -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);

View File

@ -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);

View File

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

View File

@ -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,");

View File

@ -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_ */

View File

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

View File

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

View File

@ -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;
@ -697,6 +700,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);
@ -1066,6 +1070,27 @@ init_pci(struct vmctx *ctx)
bi->memlimit64 = pci_emul_membase64;
}
/*
* 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
@ -1093,19 +1118,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);
}
/*
@ -1118,7 +1160,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.
@ -1133,9 +1175,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\"))");
@ -1228,10 +1267,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);
}
@ -1247,8 +1301,6 @@ pci_bus_write_dsdt(int bus)
dsdt_unindent(2);
done:
dsdt_line(" }");
dsdt_line("}");
dsdt_unindent(1);
}
void
@ -1256,8 +1308,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
@ -1330,18 +1393,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;
@ -1353,26 +1417,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
@ -1385,8 +1466,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;
}
@ -1402,7 +1482,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);
@ -1414,11 +1494,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);
}
@ -1458,7 +1538,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);
}
}
}
@ -1755,20 +1836,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
/*

View File

@ -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);

349
usr.sbin/bhyve/pci_irq.c Normal file
View File

@ -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);

45
usr.sbin/bhyve/pci_irq.h Normal file
View File

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

View File

@ -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
};

View File

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

View File

@ -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);
}