Add support for level triggered interrupt pins on the vioapic. Prior to this

commit level triggered interrupts would work as long as the pin was not shared
among multiple interrupt sources.

The vlapic now keeps track of level triggered interrupts in the trigger mode
register and will forward the EOI for a level triggered interrupt to the
vioapic. The vioapic in turn uses the EOI to sample the level on the pin and
re-inject the vector if the pin is still asserted.

The vhpet is the first consumer of level triggered interrupts and advertises
that it can generate interrupts on pins 20 through 23 of the vioapic.

Discussed with:	grehan@
This commit is contained in:
Neel Natu 2013-11-27 22:18:08 +00:00
parent 10c273397e
commit b5b28fc9dc
12 changed files with 235 additions and 100 deletions

View File

@ -421,7 +421,7 @@ pptintr(void *arg)
vec = pptarg->vec;
if (ppt->vm != NULL)
(void) lapic_set_intr(ppt->vm, pptarg->vcpu, vec);
lapic_intr_edge(ppt->vm, pptarg->vcpu, vec);
else {
/*
* XXX

View File

@ -266,14 +266,14 @@ vhpet_timer_interrupt(struct vhpet *vhpet, int n)
if (apicid != 0xff) {
/* unicast */
vcpuid = vm_apicid2vcpuid(vhpet->vm, apicid);
lapic_set_intr(vhpet->vm, vcpuid, vector);
lapic_intr_edge(vhpet->vm, vcpuid, vector);
} else {
/* broadcast */
dmask = vm_active_cpus(vhpet->vm);
while ((vcpuid = CPU_FFS(&dmask)) != 0) {
vcpuid--;
CPU_CLR(vcpuid, &dmask);
lapic_set_intr(vhpet->vm, vcpuid, vector);
lapic_intr_edge(vhpet->vm, vcpuid, vector);
}
}
return;
@ -725,8 +725,9 @@ vhpet_mmio_read(void *vm, int vcpuid, uint64_t gpa, uint64_t *rval, int size,
struct vhpet *
vhpet_init(struct vm *vm)
{
int i;
int i, pincount;
struct vhpet *vhpet;
uint64_t allowed_irqs;
struct vhpet_callout_arg *arg;
struct bintime bt;
@ -737,12 +738,20 @@ vhpet_init(struct vm *vm)
FREQ2BT(HPET_FREQ, &bt);
vhpet->freq_sbt = bttosbt(bt);
pincount = vioapic_pincount(vm);
if (pincount >= 24)
allowed_irqs = 0x00f00000; /* irqs 20, 21, 22 and 23 */
else
allowed_irqs = 0;
/*
* Initialize HPET timer hardware state.
*/
for (i = 0; i < VHPET_NUM_TIMERS; i++) {
vhpet->timer[i].cap_config = 0UL << 32 |
HPET_TCAP_FSB_INT_DEL | HPET_TCAP_PER_INT;
vhpet->timer[i].cap_config = allowed_irqs << 32;
vhpet->timer[i].cap_config |= HPET_TCAP_PER_INT;
vhpet->timer[i].cap_config |= HPET_TCAP_FSB_INT_DEL;
vhpet->timer[i].compval = 0xffffffff;
callout_init(&vhpet->timer[i].callout, 1);

View File

@ -49,8 +49,8 @@ __FBSDID("$FreeBSD$");
#define IOREGSEL 0x00
#define IOWIN 0x10
#define REDIR_ENTRIES 16
#define INTR_ASSERTED(vioapic, pin) ((vioapic)->rtbl[(pin)].pinstate == true)
#define REDIR_ENTRIES 24
#define RTBL_RO_BITS ((uint64_t)(IOART_REM_IRR | IOART_DELIVS))
struct vioapic {
struct vm *vm;
@ -59,8 +59,7 @@ struct vioapic {
uint32_t ioregsel;
struct {
uint64_t reg;
bool pinstate;
bool pending;
int acnt; /* sum of pin asserts (+1) and deasserts (-1) */
} rtbl[REDIR_ENTRIES];
};
@ -79,6 +78,9 @@ static MALLOC_DEFINE(M_VIOAPIC, "vioapic", "bhyve virtual ioapic");
#define VIOAPIC_CTR3(vioapic, fmt, a1, a2, a3) \
VM_CTR3((vioapic)->vm, fmt, a1, a2, a3)
#define VIOAPIC_CTR4(vioapic, fmt, a1, a2, a3, a4) \
VM_CTR4((vioapic)->vm, fmt, a1, a2, a3, a4)
#ifdef KTR
static const char *
pinstate_str(bool asserted)
@ -89,14 +91,25 @@ pinstate_str(bool asserted)
else
return ("deasserted");
}
static const char *
trigger_str(bool level)
{
if (level)
return ("level");
else
return ("edge");
}
#endif
static void
vioapic_set_pinstate(struct vioapic *vioapic, int pin, bool newstate)
vioapic_send_intr(struct vioapic *vioapic, int pin)
{
int vector, apicid, vcpuid;
uint32_t low, high;
cpuset_t dmask;
bool level;
KASSERT(pin >= 0 && pin < REDIR_ENTRIES,
("vioapic_set_pinstate: invalid pin number %d", pin));
@ -104,60 +117,94 @@ vioapic_set_pinstate(struct vioapic *vioapic, int pin, bool newstate)
KASSERT(VIOAPIC_LOCKED(vioapic),
("vioapic_set_pinstate: vioapic is not locked"));
VIOAPIC_CTR2(vioapic, "ioapic pin%d %s", pin, pinstate_str(newstate));
/* Nothing to do if interrupt pin has not changed state */
if (vioapic->rtbl[pin].pinstate == newstate)
return;
vioapic->rtbl[pin].pinstate = newstate; /* record it */
/* Nothing to do if interrupt pin is deasserted */
if (!INTR_ASSERTED(vioapic, pin))
return;
/*
* XXX
* We only deal with:
* - edge triggered interrupts
* - fixed delivery mode
* Level-triggered sources will work so long as there is no sharing.
*/
low = vioapic->rtbl[pin].reg;
high = vioapic->rtbl[pin].reg >> 32;
if ((low & IOART_INTMASK) == IOART_INTMCLR &&
(low & IOART_DESTMOD) == IOART_DESTPHY &&
(low & IOART_DELMOD) == IOART_DELFIXED) {
vector = low & IOART_INTVEC;
apicid = high >> APIC_ID_SHIFT;
if (apicid != 0xff) {
/* unicast */
vcpuid = vm_apicid2vcpuid(vioapic->vm, apicid);
VIOAPIC_CTR3(vioapic, "ioapic pin%d triggering "
"intr vector %d on vcpuid %d", pin, vector, vcpuid);
lapic_set_intr(vioapic->vm, vcpuid, vector);
} else {
/* broadcast */
VIOAPIC_CTR2(vioapic, "ioapic pin%d triggering intr "
"vector %d on all vcpus", pin, vector);
dmask = vm_active_cpus(vioapic->vm);
while ((vcpuid = CPU_FFS(&dmask)) != 0) {
vcpuid--;
CPU_CLR(vcpuid, &dmask);
lapic_set_intr(vioapic->vm, vcpuid, vector);
}
}
} else if ((low & IOART_INTMASK) != IOART_INTMCLR &&
(low & IOART_TRGRLVL) != 0) {
/*
* For level-triggered interrupts that have been
* masked, set the pending bit so that an interrupt
* will be generated on unmask and if the level is
* still asserted
*/
VIOAPIC_CTR1(vioapic, "ioapic pin%d interrupt pending", pin);
vioapic->rtbl[pin].pending = true;
/*
* XXX We only deal with:
* - physical destination
* - fixed delivery mode
*/
if ((low & IOART_DESTMOD) != IOART_DESTPHY) {
VIOAPIC_CTR2(vioapic, "ioapic pin%d: unsupported dest mode "
"0x%08x", pin, low);
return;
}
if ((low & IOART_DELMOD) != IOART_DELFIXED) {
VIOAPIC_CTR2(vioapic, "ioapic pin%d: unsupported delivery mode "
"0x%08x", pin, low);
return;
}
if ((low & IOART_INTMASK) == IOART_INTMSET) {
VIOAPIC_CTR1(vioapic, "ioapic pin%d: masked", pin);
return;
}
level = low & IOART_TRGRLVL ? true : false;
if (level)
vioapic->rtbl[pin].reg |= IOART_REM_IRR;
vector = low & IOART_INTVEC;
apicid = high >> APIC_ID_SHIFT;
if (apicid != 0xff) {
/* unicast */
vcpuid = vm_apicid2vcpuid(vioapic->vm, apicid);
VIOAPIC_CTR4(vioapic, "ioapic pin%d: %s triggered intr "
"vector %d on vcpuid %d", pin, trigger_str(level),
vector, vcpuid);
lapic_set_intr(vioapic->vm, vcpuid, vector, level);
} else {
/* broadcast */
VIOAPIC_CTR3(vioapic, "ioapic pin%d: %s triggered intr "
"vector %d on all vcpus", pin, trigger_str(level), vector);
dmask = vm_active_cpus(vioapic->vm);
while ((vcpuid = CPU_FFS(&dmask)) != 0) {
vcpuid--;
CPU_CLR(vcpuid, &dmask);
lapic_set_intr(vioapic->vm, vcpuid, vector, level);
}
}
}
static void
vioapic_set_pinstate(struct vioapic *vioapic, int pin, bool newstate)
{
int oldcnt, newcnt;
bool needintr;
KASSERT(pin >= 0 && pin < REDIR_ENTRIES,
("vioapic_set_pinstate: invalid pin number %d", pin));
KASSERT(VIOAPIC_LOCKED(vioapic),
("vioapic_set_pinstate: vioapic is not locked"));
oldcnt = vioapic->rtbl[pin].acnt;
if (newstate)
vioapic->rtbl[pin].acnt++;
else
vioapic->rtbl[pin].acnt--;
newcnt = vioapic->rtbl[pin].acnt;
if (newcnt < 0) {
VIOAPIC_CTR2(vioapic, "ioapic pin%d: bad acnt %d",
pin, newcnt);
}
needintr = false;
if (oldcnt == 0 && newcnt == 1) {
needintr = true;
VIOAPIC_CTR1(vioapic, "ioapic pin%d: asserted", pin);
} else if (oldcnt == 1 && newcnt == 0) {
VIOAPIC_CTR1(vioapic, "ioapic pin%d: deasserted", pin);
} else {
VIOAPIC_CTR3(vioapic, "ioapic pin%d: %s, ignored, acnt %d",
pin, pinstate_str(newstate), newcnt);
}
if (needintr)
vioapic_send_intr(vioapic, pin);
}
enum irqstate {
@ -228,7 +275,7 @@ vioapic_read(struct vioapic *vioapic, uint32_t addr)
return (vioapic->id);
break;
case IOAPIC_VER:
return ((REDIR_ENTRIES << MAXREDIRSHIFT) | 0x11);
return (((REDIR_ENTRIES - 1) << MAXREDIRSHIFT) | 0x11);
break;
case IOAPIC_ARB:
return (vioapic->id);
@ -255,6 +302,7 @@ vioapic_read(struct vioapic *vioapic, uint32_t addr)
static void
vioapic_write(struct vioapic *vioapic, uint32_t addr, uint32_t data)
{
uint64_t data64, mask64;
int regnum, pin, lshift;
regnum = addr & 0xff;
@ -279,30 +327,26 @@ vioapic_write(struct vioapic *vioapic, uint32_t addr, uint32_t data)
else
lshift = 0;
vioapic->rtbl[pin].reg &= ~((uint64_t)0xffffffff << lshift);
vioapic->rtbl[pin].reg |= ((uint64_t)data << lshift);
data64 = (uint64_t)data << lshift;
mask64 = (uint64_t)0xffffffff << lshift;
vioapic->rtbl[pin].reg &= ~mask64 | RTBL_RO_BITS;
vioapic->rtbl[pin].reg |= data64 & ~RTBL_RO_BITS;
VIOAPIC_CTR2(vioapic, "ioapic pin%d redir table entry %#lx",
VIOAPIC_CTR2(vioapic, "ioapic pin%d: redir table entry %#lx",
pin, vioapic->rtbl[pin].reg);
if (vioapic->rtbl[pin].pending &&
((vioapic->rtbl[pin].reg & IOART_INTMASK) ==
IOART_INTMCLR)) {
vioapic->rtbl[pin].pending = false;
/*
* Inject the deferred level-triggered int if it is
* still asserted. Simulate by toggling the pin
* off and then on.
*/
if (vioapic->rtbl[pin].pinstate == true) {
VIOAPIC_CTR1(vioapic, "ioapic pin%d pending "
"interrupt delivered", pin);
vioapic_set_pinstate(vioapic, pin, false);
vioapic_set_pinstate(vioapic, pin, true);
} else {
VIOAPIC_CTR1(vioapic, "ioapic pin%d pending "
"interrupt dismissed", pin);
}
/*
* Generate an interrupt if the following conditions are met:
* - pin is not masked
* - previous interrupt has been EOIed
* - pin level is asserted
*/
if ((vioapic->rtbl[pin].reg & IOART_INTMASK) == IOART_INTMCLR &&
(vioapic->rtbl[pin].reg & IOART_REM_IRR) == 0 &&
(vioapic->rtbl[pin].acnt > 0)) {
VIOAPIC_CTR2(vioapic, "ioapic pin%d: asserted at rtbl "
"write, acnt %d", pin, vioapic->rtbl[pin].acnt);
vioapic_send_intr(vioapic, pin);
}
}
}
@ -366,6 +410,38 @@ vioapic_mmio_write(void *vm, int vcpuid, uint64_t gpa, uint64_t wval,
return (error);
}
void
vioapic_process_eoi(struct vm *vm, int vcpuid, int vector)
{
struct vioapic *vioapic;
int pin;
KASSERT(vector >= 0 && vector < 256,
("vioapic_process_eoi: invalid vector %d", vector));
vioapic = vm_ioapic(vm);
VIOAPIC_CTR1(vioapic, "ioapic processing eoi for vector %d", vector);
/*
* XXX keep track of the pins associated with this vector instead
* of iterating on every single pin each time.
*/
VIOAPIC_LOCK(vioapic);
for (pin = 0; pin < REDIR_ENTRIES; pin++) {
if ((vioapic->rtbl[pin].reg & IOART_REM_IRR) == 0)
continue;
if ((vioapic->rtbl[pin].reg & IOART_INTVEC) != vector)
continue;
vioapic->rtbl[pin].reg &= ~IOART_REM_IRR;
if (vioapic->rtbl[pin].acnt > 0) {
VIOAPIC_CTR2(vioapic, "ioapic pin%d: asserted at eoi, "
"acnt %d", pin, vioapic->rtbl[pin].acnt);
vioapic_send_intr(vioapic, pin);
}
}
VIOAPIC_UNLOCK(vioapic);
}
struct vioapic *
vioapic_init(struct vm *vm)
{
@ -390,3 +466,10 @@ vioapic_cleanup(struct vioapic *vioapic)
free(vioapic, M_VIOAPIC);
}
int
vioapic_pincount(struct vm *vm)
{
return (REDIR_ENTRIES);
}

View File

@ -44,4 +44,7 @@ int vioapic_mmio_write(void *vm, int vcpuid, uint64_t gpa,
uint64_t wval, int size, void *arg);
int vioapic_mmio_read(void *vm, int vcpuid, uint64_t gpa,
uint64_t *rval, int size, void *arg);
int vioapic_pincount(struct vm *vm);
void vioapic_process_eoi(struct vm *vm, int vcpuid, int vector);
#endif

View File

@ -45,6 +45,7 @@ __FBSDID("$FreeBSD$");
#include "vmm_lapic.h"
#include "vmm_ktr.h"
#include "vlapic.h"
#include "vioapic.h"
#define VLAPIC_CTR0(vlapic, format) \
VCPU_CTR0((vlapic)->vm, (vlapic)->vcpuid, format)
@ -211,18 +212,32 @@ vlapic_reset(struct vlapic *vlapic)
}
void
vlapic_set_intr_ready(struct vlapic *vlapic, int vector)
vlapic_set_intr_ready(struct vlapic *vlapic, int vector, bool level)
{
struct LAPIC *lapic = &vlapic->apic;
uint32_t *irrptr;
uint32_t *irrptr, *tmrptr, mask;
int idx;
if (vector < 0 || vector >= 256)
panic("vlapic_set_intr_ready: invalid vector %d\n", vector);
idx = (vector / 32) * 4;
mask = 1 << (vector % 32);
irrptr = &lapic->irr0;
atomic_set_int(&irrptr[idx], 1 << (vector % 32));
atomic_set_int(&irrptr[idx], mask);
/*
* Upon acceptance of an interrupt into the IRR the corresponding
* TMR bit is cleared for edge-triggered interrupts and set for
* level-triggered interrupts.
*/
tmrptr = &lapic->tmr0;
if (level)
atomic_set_int(&tmrptr[idx], mask);
else
atomic_clear_int(&tmrptr[idx], mask);
VLAPIC_CTR_IRR(vlapic, "vlapic_set_intr_ready");
}
@ -350,10 +365,11 @@ static void
vlapic_process_eoi(struct vlapic *vlapic)
{
struct LAPIC *lapic = &vlapic->apic;
uint32_t *isrptr;
int i, idx, bitpos;
uint32_t *isrptr, *tmrptr;
int i, idx, bitpos, vector;
isrptr = &lapic->isr0;
tmrptr = &lapic->tmr0;
/*
* The x86 architecture reserves the the first 32 vectors for use
@ -362,15 +378,20 @@ vlapic_process_eoi(struct vlapic *vlapic)
for (i = 7; i > 0; i--) {
idx = i * 4;
bitpos = fls(isrptr[idx]);
if (bitpos != 0) {
if (bitpos-- != 0) {
if (vlapic->isrvec_stk_top <= 0) {
panic("invalid vlapic isrvec_stk_top %d",
vlapic->isrvec_stk_top);
}
isrptr[idx] &= ~(1 << (bitpos - 1));
isrptr[idx] &= ~(1 << bitpos);
VLAPIC_CTR_ISR(vlapic, "vlapic_process_eoi");
vlapic->isrvec_stk_top--;
vlapic_update_ppr(vlapic);
if ((tmrptr[idx] & (1 << bitpos)) != 0) {
vector = i * 32 + bitpos;
vioapic_process_eoi(vlapic->vm, vlapic->vcpuid,
vector);
}
return;
}
}
@ -405,7 +426,7 @@ vlapic_fire_timer(struct vlapic *vlapic)
if (!vlapic_get_lvt_field(lvt, APIC_LVTT_M)) {
vmm_stat_incr(vlapic->vm, vlapic->vcpuid, VLAPIC_INTR_TIMER, 1);
vector = vlapic_get_lvt_field(lvt,APIC_LVTT_VECTOR);
vlapic_set_intr_ready(vlapic, vector);
vlapic_set_intr_ready(vlapic, vector, false);
}
}
@ -451,7 +472,7 @@ lapic_process_icr(struct vlapic *vlapic, uint64_t icrval)
i--;
CPU_CLR(i, &dmask);
if (mode == APIC_DELMODE_FIXED) {
lapic_set_intr(vlapic->vm, i, vec);
lapic_intr_edge(vlapic->vm, i, vec);
vmm_stat_array_incr(vlapic->vm, vlapic->vcpuid,
IPIS_SENT, i, 1);
} else

View File

@ -94,7 +94,7 @@ int vlapic_write(struct vlapic *vlapic, uint64_t offset, uint64_t data);
int vlapic_read(struct vlapic *vlapic, uint64_t offset, uint64_t *data);
int vlapic_pending_intr(struct vlapic *vlapic);
void vlapic_intr_accepted(struct vlapic *vlapic, int vector);
void vlapic_set_intr_ready(struct vlapic *vlapic, int vector);
void vlapic_set_intr_ready(struct vlapic *vlapic, int vector, bool level);
int vlapic_timer_tick(struct vlapic *vlapic);
uint64_t vlapic_get_apicbase(struct vlapic *vlapic);

View File

@ -294,7 +294,7 @@ vmmdev_ioctl(struct cdev *cdev, u_long cmd, caddr_t data, int fflag,
break;
case VM_LAPIC_IRQ:
vmirq = (struct vm_lapic_irq *)data;
error = lapic_set_intr(sc->vm, vmirq->cpuid, vmirq->vector);
error = lapic_intr_edge(sc->vm, vmirq->cpuid, vmirq->vector);
break;
case VM_IOAPIC_ASSERT_IRQ:
ioapic_irq = (struct vm_ioapic_irq *)data;

View File

@ -59,4 +59,7 @@ CTR3(KTR_VMM, "vm %s: " format, vm_name((vm)), (p1), (p2))
#define VM_CTR3(vm, format, p1, p2, p3) \
CTR4(KTR_VMM, "vm %s: " format, vm_name((vm)), (p1), (p2), (p3))
#define VM_CTR4(vm, format, p1, p2, p3, p4) \
CTR5(KTR_VMM, "vm %s: " format, vm_name((vm)), (p1), (p2), (p3), (p4))
#endif

View File

@ -62,7 +62,7 @@ lapic_intr_accepted(struct vm *vm, int cpu, int vector)
}
int
lapic_set_intr(struct vm *vm, int cpu, int vector)
lapic_set_intr(struct vm *vm, int cpu, int vector, bool level)
{
struct vlapic *vlapic;
@ -73,7 +73,7 @@ lapic_set_intr(struct vm *vm, int cpu, int vector)
return (EINVAL);
vlapic = vm_lapic(vm, cpu);
vlapic_set_intr_ready(vlapic, vector);
vlapic_set_intr_ready(vlapic, vector, level);
vm_interrupt_hostcpu(vm, cpu);

View File

@ -66,6 +66,22 @@ void lapic_intr_accepted(struct vm *vm, int cpu, int vector);
* Signals to the LAPIC that an interrupt at 'vector' needs to be generated
* to the 'cpu', the state is recorded in IRR.
*/
int lapic_set_intr(struct vm *vm, int cpu, int vector);
int lapic_set_intr(struct vm *vm, int cpu, int vector, bool trig);
#define LAPIC_TRIG_LEVEL true
#define LAPIC_TRIG_EDGE false
static __inline int
lapic_intr_level(struct vm *vm, int cpu, int vector)
{
return (lapic_set_intr(vm, cpu, vector, LAPIC_TRIG_LEVEL));
}
static __inline int
lapic_intr_edge(struct vm *vm, int cpu, int vector)
{
return (lapic_set_intr(vm, cpu, vector, LAPIC_TRIG_EDGE));
}
#endif

View File

@ -259,7 +259,7 @@ basl_fwrite_madt(FILE *fp)
EFPRINTF(fp, "\n");
}
/* Always a single IOAPIC entry, with ID ncpu+1 */
/* Always a single IOAPIC entry, with ID 0 */
EFPRINTF(fp, "[0001]\t\tSubtable Type : 01\n");
EFPRINTF(fp, "[0001]\t\tLength : 0C\n");
/* iasl expects a hex value for the i/o apic id */

View File

@ -72,7 +72,7 @@ __FBSDID("$FreeBSD$");
#define MPEP_FEATURES (0xBFEBFBFF) /* XXX Intel i7 */
/* Number of i/o intr entries */
#define MPEII_MAX_IRQ 16
#define MPEII_MAX_IRQ 24
/* Define processor entry struct since <x86/mptable.h> gets it wrong */
typedef struct BPROCENTRY {