Modify handling of writes to the vlapic LVT registers.
The handler is now called after the register value is updated in the virtual APIC page. This will make it easier to handle APIC-write VM-exits with APIC register virtualization turned on. This also implies that we need to keep a snapshot of the last value written to a LVT register. We can no longer rely on the LVT registers in the APIC page to be "clean" because the guest can write anything to it before the hypervisor has had a chance to sanitize it.
This commit is contained in:
parent
b2c730e011
commit
7c05bc3124
@ -94,7 +94,6 @@ do { \
|
||||
#define PRIO(x) ((x) >> 4)
|
||||
|
||||
#define VLAPIC_VERSION (16)
|
||||
#define VLAPIC_MAXLVT_ENTRIES (APIC_LVT_CMCI)
|
||||
|
||||
#define x2apic(vlapic) (((vlapic)->msr_apicbase & APICBASE_X2APIC) ? 1 : 0)
|
||||
|
||||
@ -212,20 +211,6 @@ vlapic_timer_divisor(uint32_t dcr)
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
vlapic_mask_lvts(struct vlapic *vlapic)
|
||||
{
|
||||
struct LAPIC *lapic = vlapic->apic_page;
|
||||
|
||||
lapic->lvt_cmci |= APIC_LVT_M;
|
||||
lapic->lvt_timer |= APIC_LVT_M;
|
||||
lapic->lvt_thermal |= APIC_LVT_M;
|
||||
lapic->lvt_pcint |= APIC_LVT_M;
|
||||
lapic->lvt_lint0 |= APIC_LVT_M;
|
||||
lapic->lvt_lint1 |= APIC_LVT_M;
|
||||
lapic->lvt_error |= APIC_LVT_M;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static inline void
|
||||
vlapic_dump_lvt(uint32_t offset, uint32_t *lvt)
|
||||
@ -304,32 +289,6 @@ vlapic_esr_write_handler(struct vlapic *vlapic)
|
||||
vlapic->esr_pending = 0;
|
||||
}
|
||||
|
||||
static void
|
||||
vlapic_reset(struct vlapic *vlapic)
|
||||
{
|
||||
struct LAPIC *lapic;
|
||||
|
||||
lapic = vlapic->apic_page;
|
||||
bzero(lapic, sizeof(struct LAPIC));
|
||||
|
||||
lapic->id = vlapic_get_id(vlapic);
|
||||
lapic->version = VLAPIC_VERSION;
|
||||
lapic->version |= (VLAPIC_MAXLVT_ENTRIES << MAXLVTSHIFT);
|
||||
lapic->dfr = 0xffffffff;
|
||||
lapic->svr = APIC_SVR_VECTOR;
|
||||
vlapic_mask_lvts(vlapic);
|
||||
|
||||
lapic->dcr_timer = 0;
|
||||
vlapic_dcr_write_handler(vlapic);
|
||||
|
||||
if (vlapic->vcpuid == 0)
|
||||
vlapic->boot_state = BS_RUNNING; /* BSP */
|
||||
else
|
||||
vlapic->boot_state = BS_INIT; /* AP */
|
||||
|
||||
vlapic->svr_last = lapic->svr;
|
||||
}
|
||||
|
||||
void
|
||||
vlapic_set_intr_ready(struct vlapic *vlapic, int vector, bool level)
|
||||
{
|
||||
@ -388,24 +347,65 @@ vlapic_get_lvtptr(struct vlapic *vlapic, uint32_t offset)
|
||||
}
|
||||
}
|
||||
|
||||
static __inline int
|
||||
lvt_off_to_idx(uint32_t offset)
|
||||
{
|
||||
int index;
|
||||
|
||||
switch (offset) {
|
||||
case APIC_OFFSET_CMCI_LVT:
|
||||
index = APIC_LVT_CMCI;
|
||||
break;
|
||||
case APIC_OFFSET_TIMER_LVT:
|
||||
index = APIC_LVT_TIMER;
|
||||
break;
|
||||
case APIC_OFFSET_THERM_LVT:
|
||||
index = APIC_LVT_THERMAL;
|
||||
break;
|
||||
case APIC_OFFSET_PERF_LVT:
|
||||
index = APIC_LVT_PMC;
|
||||
break;
|
||||
case APIC_OFFSET_LINT0_LVT:
|
||||
index = APIC_LVT_LINT0;
|
||||
break;
|
||||
case APIC_OFFSET_LINT1_LVT:
|
||||
index = APIC_LVT_LINT1;
|
||||
break;
|
||||
case APIC_OFFSET_ERROR_LVT:
|
||||
index = APIC_LVT_ERROR;
|
||||
break;
|
||||
default:
|
||||
index = -1;
|
||||
break;
|
||||
}
|
||||
KASSERT(index >= 0 && index <= VLAPIC_MAXLVT_INDEX, ("lvt_off_to_idx: "
|
||||
"invalid lvt index %d for offset %#x", index, offset));
|
||||
|
||||
return (index);
|
||||
}
|
||||
|
||||
static __inline uint32_t
|
||||
vlapic_get_lvt(struct vlapic *vlapic, uint32_t offset)
|
||||
{
|
||||
int idx;
|
||||
uint32_t val;
|
||||
|
||||
return (*vlapic_get_lvtptr(vlapic, offset));
|
||||
idx = lvt_off_to_idx(offset);
|
||||
val = atomic_load_acq_32(&vlapic->lvt_last[idx]);
|
||||
return (val);
|
||||
}
|
||||
|
||||
static void
|
||||
vlapic_set_lvt(struct vlapic *vlapic, uint32_t offset, uint32_t val)
|
||||
void
|
||||
vlapic_lvt_write_handler(struct vlapic *vlapic, uint32_t offset)
|
||||
{
|
||||
uint32_t *lvtptr, mask;
|
||||
uint32_t *lvtptr, mask, val;
|
||||
struct LAPIC *lapic;
|
||||
int idx;
|
||||
|
||||
lapic = vlapic->apic_page;
|
||||
lvtptr = vlapic_get_lvtptr(vlapic, offset);
|
||||
|
||||
if (offset == APIC_OFFSET_TIMER_LVT)
|
||||
VLAPIC_TIMER_LOCK(vlapic);
|
||||
val = *lvtptr;
|
||||
idx = lvt_off_to_idx(offset);
|
||||
|
||||
if (!(lapic->svr & APIC_SVR_ENABLE))
|
||||
val |= APIC_LVT_M;
|
||||
@ -424,10 +424,36 @@ vlapic_set_lvt(struct vlapic *vlapic, uint32_t offset, uint32_t val)
|
||||
mask |= APIC_LVT_DM;
|
||||
break;
|
||||
}
|
||||
*lvtptr = val & mask;
|
||||
val &= mask;
|
||||
*lvtptr = val;
|
||||
atomic_store_rel_32(&vlapic->lvt_last[idx], val);
|
||||
}
|
||||
|
||||
if (offset == APIC_OFFSET_TIMER_LVT)
|
||||
VLAPIC_TIMER_UNLOCK(vlapic);
|
||||
static void
|
||||
vlapic_mask_lvts(struct vlapic *vlapic)
|
||||
{
|
||||
struct LAPIC *lapic = vlapic->apic_page;
|
||||
|
||||
lapic->lvt_cmci |= APIC_LVT_M;
|
||||
vlapic_lvt_write_handler(vlapic, APIC_OFFSET_CMCI_LVT);
|
||||
|
||||
lapic->lvt_timer |= APIC_LVT_M;
|
||||
vlapic_lvt_write_handler(vlapic, APIC_OFFSET_TIMER_LVT);
|
||||
|
||||
lapic->lvt_thermal |= APIC_LVT_M;
|
||||
vlapic_lvt_write_handler(vlapic, APIC_OFFSET_THERM_LVT);
|
||||
|
||||
lapic->lvt_pcint |= APIC_LVT_M;
|
||||
vlapic_lvt_write_handler(vlapic, APIC_OFFSET_PERF_LVT);
|
||||
|
||||
lapic->lvt_lint0 |= APIC_LVT_M;
|
||||
vlapic_lvt_write_handler(vlapic, APIC_OFFSET_LINT0_LVT);
|
||||
|
||||
lapic->lvt_lint1 |= APIC_LVT_M;
|
||||
vlapic_lvt_write_handler(vlapic, APIC_OFFSET_LINT1_LVT);
|
||||
|
||||
lapic->lvt_error |= APIC_LVT_M;
|
||||
vlapic_lvt_write_handler(vlapic, APIC_OFFSET_ERROR_LVT);
|
||||
}
|
||||
|
||||
static int
|
||||
@ -648,7 +674,7 @@ vlapic_fire_cmci(struct vlapic *vlapic)
|
||||
}
|
||||
}
|
||||
|
||||
static VMM_STAT_ARRAY(LVTS_TRIGGERRED, VLAPIC_MAXLVT_ENTRIES,
|
||||
static VMM_STAT_ARRAY(LVTS_TRIGGERRED, VLAPIC_MAXLVT_INDEX + 1,
|
||||
"lvts triggered");
|
||||
|
||||
int
|
||||
@ -1166,6 +1192,11 @@ vlapic_read(struct vlapic *vlapic, uint64_t offset, uint64_t *data, bool *retu)
|
||||
case APIC_OFFSET_CMCI_LVT:
|
||||
case APIC_OFFSET_TIMER_LVT ... APIC_OFFSET_ERROR_LVT:
|
||||
*data = vlapic_get_lvt(vlapic, offset);
|
||||
#ifdef INVARIANTS
|
||||
reg = vlapic_get_lvtptr(vlapic, offset);
|
||||
KASSERT(*data == *reg, ("inconsistent lvt value at "
|
||||
"offset %#lx: %#lx/%#x", offset, *data, *reg));
|
||||
#endif
|
||||
break;
|
||||
case APIC_OFFSET_TIMER_ICR:
|
||||
*data = lapic->icr_timer;
|
||||
@ -1190,6 +1221,7 @@ int
|
||||
vlapic_write(struct vlapic *vlapic, uint64_t offset, uint64_t data, bool *retu)
|
||||
{
|
||||
struct LAPIC *lapic = vlapic->apic_page;
|
||||
uint32_t *regptr;
|
||||
int retval;
|
||||
|
||||
KASSERT((offset & 0xf) == 0 && offset < PAGE_SIZE,
|
||||
@ -1238,7 +1270,9 @@ vlapic_write(struct vlapic *vlapic, uint64_t offset, uint64_t data, bool *retu)
|
||||
break;
|
||||
case APIC_OFFSET_CMCI_LVT:
|
||||
case APIC_OFFSET_TIMER_LVT ... APIC_OFFSET_ERROR_LVT:
|
||||
vlapic_set_lvt(vlapic, offset, data);
|
||||
regptr = vlapic_get_lvtptr(vlapic, offset);
|
||||
*regptr = data;
|
||||
vlapic_lvt_write_handler(vlapic, offset);
|
||||
break;
|
||||
case APIC_OFFSET_TIMER_ICR:
|
||||
lapic->icr_timer = data;
|
||||
@ -1269,6 +1303,32 @@ vlapic_write(struct vlapic *vlapic, uint64_t offset, uint64_t data, bool *retu)
|
||||
return (retval);
|
||||
}
|
||||
|
||||
static void
|
||||
vlapic_reset(struct vlapic *vlapic)
|
||||
{
|
||||
struct LAPIC *lapic;
|
||||
|
||||
lapic = vlapic->apic_page;
|
||||
bzero(lapic, sizeof(struct LAPIC));
|
||||
|
||||
lapic->id = vlapic_get_id(vlapic);
|
||||
lapic->version = VLAPIC_VERSION;
|
||||
lapic->version |= (VLAPIC_MAXLVT_INDEX << MAXLVTSHIFT);
|
||||
lapic->dfr = 0xffffffff;
|
||||
lapic->svr = APIC_SVR_VECTOR;
|
||||
vlapic_mask_lvts(vlapic);
|
||||
|
||||
lapic->dcr_timer = 0;
|
||||
vlapic_dcr_write_handler(vlapic);
|
||||
|
||||
if (vlapic->vcpuid == 0)
|
||||
vlapic->boot_state = BS_RUNNING; /* BSP */
|
||||
else
|
||||
vlapic->boot_state = BS_INIT; /* AP */
|
||||
|
||||
vlapic->svr_last = lapic->svr;
|
||||
}
|
||||
|
||||
void
|
||||
vlapic_init(struct vlapic *vlapic)
|
||||
{
|
||||
|
@ -80,4 +80,5 @@ void vlapic_esr_write_handler(struct vlapic *vlapic);
|
||||
int vlapic_icrlo_write_handler(struct vlapic *vlapic, bool *retu);
|
||||
void vlapic_icrtmr_write_handler(struct vlapic *vlapic);
|
||||
void vlapic_dcr_write_handler(struct vlapic *vlapic);
|
||||
void vlapic_lvt_write_handler(struct vlapic *vlapic, uint32_t offset);
|
||||
#endif /* _VLAPIC_H_ */
|
||||
|
@ -29,6 +29,8 @@
|
||||
#ifndef _VLAPIC_PRIV_H_
|
||||
#define _VLAPIC_PRIV_H_
|
||||
|
||||
#include <x86/apicreg.h>
|
||||
|
||||
/*
|
||||
* APIC Register: Offset Description
|
||||
*/
|
||||
@ -91,6 +93,8 @@ enum boot_state {
|
||||
*/
|
||||
#define ISRVEC_STK_SIZE (16 + 1)
|
||||
|
||||
#define VLAPIC_MAXLVT_INDEX APIC_LVT_CMCI
|
||||
|
||||
struct vlapic {
|
||||
struct vm *vm;
|
||||
int vcpuid;
|
||||
@ -111,12 +115,20 @@ struct vlapic {
|
||||
* The vector on the top of the stack is used to compute the
|
||||
* Processor Priority in conjunction with the TPR.
|
||||
*/
|
||||
uint8_t isrvec_stk[ISRVEC_STK_SIZE];
|
||||
int isrvec_stk_top;
|
||||
uint8_t isrvec_stk[ISRVEC_STK_SIZE];
|
||||
int isrvec_stk_top;
|
||||
|
||||
uint64_t msr_apicbase;
|
||||
enum boot_state boot_state;
|
||||
uint32_t svr_last;
|
||||
uint64_t msr_apicbase;
|
||||
enum boot_state boot_state;
|
||||
|
||||
/*
|
||||
* Copies of some registers in the virtual APIC page. We do this for
|
||||
* a couple of different reasons:
|
||||
* - to be able to detect what changed (e.g. svr_last)
|
||||
* - to maintain a coherent snapshot of the register (e.g. lvt_last)
|
||||
*/
|
||||
uint32_t svr_last;
|
||||
uint32_t lvt_last[VLAPIC_MAXLVT_INDEX + 1];
|
||||
};
|
||||
|
||||
void vlapic_init(struct vlapic *vlapic);
|
||||
|
Loading…
Reference in New Issue
Block a user