blob: 03943afef542943d66f8977889aa7fee6befaf48 [file] [log] [blame]
/* @TAG(OTHER_GPL) */
/*
* Local APIC virtualization
*
* Copyright (C) 2006 Qumranet, Inc.
* Copyright (C) 2007 Novell
* Copyright (C) 2007 Intel
* Copyright 2009 Red Hat, Inc. and/or its affiliates.
*
* Authors:
* Dor Laor <dor.laor@qumranet.com>
* Gregory Haskins <ghaskins@novell.com>
* Yaozu (Eddie) Dong <eddie.dong@intel.com>
*
* Based on Xen 3.1 code, Copyright (c) 2004, Intel Corporation.
*
* This work is licensed under the terms of the GNU GPL, version 2. See
* the COPYING file in the top-level directory.
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <utils/util.h>
#include "vmm/debug.h"
#include "vmm/processor/lapic.h"
#include "vmm/processor/apicdef.h"
#include "vmm/processor/msr.h"
#include "vmm/mmio.h"
#define APIC_BUS_CYCLE_NS 1
#define APIC_DEBUG 0
#define apic_debug(lvl,...) do{ if(lvl < APIC_DEBUG){printf(__VA_ARGS__);fflush(stdout);}}while (0)
#define APIC_LVT_NUM 6
/* 14 is the version for Xeon and Pentium 8.4.8*/
#define APIC_VERSION (0x14UL | ((APIC_LVT_NUM - 1) << 16))
#define LAPIC_MMIO_LENGTH (BIT(12))
/* followed define is not in apicdef.h */
#define APIC_SHORT_MASK 0xc0000
#define APIC_DEST_NOSHORT 0x0
#define APIC_DEST_MASK 0x800
#define MAX_APIC_VECTOR 256
#define APIC_VECTORS_PER_REG 32
inline static int pic_get_interrupt(vmm_t *vmm)
{
return vmm->plat_callbacks.get_interrupt();
}
inline static int pic_has_interrupt(vmm_t *vmm)
{
return vmm->plat_callbacks.has_interrupt();
}
struct vmm_lapic_irq {
uint32_t vector;
uint32_t delivery_mode;
uint32_t dest_mode;
uint32_t level;
uint32_t trig_mode;
uint32_t shorthand;
uint32_t dest_id;
};
/* Generic bit operations; TODO move these elsewhere */
static inline int fls(int x)
{
int r = 32;
if (!x)
return 0;
if (!(x & 0xffff0000u)) {
x <<= 16;
r -= 16;
}
if (!(x & 0xff000000u)) {
x <<= 8;
r -= 8;
}
if (!(x & 0xf0000000u)) {
x <<= 4;
r -= 4;
}
if (!(x & 0xc0000000u)) {
x <<= 2;
r -= 2;
}
if (!(x & 0x80000000u)) {
x <<= 1;
r -= 1;
}
return r;
}
static uint32_t hweight32(unsigned int w)
{
uint32_t res = w - ((w >> 1) & 0x55555555);
res = (res & 0x33333333) + ((res >> 2) & 0x33333333);
res = (res + (res >> 4)) & 0x0F0F0F0F;
res = res + (res >> 8);
return (res + (res >> 16)) & 0x000000FF;
}
/* End generic bit ops */
void vmm_lapic_reset(vmm_vcpu_t *vcpu);
// Returns whether the irq delivery mode is lowest prio
inline static bool vmm_is_dm_lowest_prio(struct vmm_lapic_irq *irq)
{
return irq->delivery_mode == APIC_DM_LOWEST;
}
// Access register field
static inline void apic_set_reg(vmm_lapic_t *apic, int reg_off, uint32_t val)
{
*((uint32_t *) (apic->regs + reg_off)) = val;
}
static inline uint32_t vmm_apic_get_reg(vmm_lapic_t *apic, int reg_off)
{
return *((uint32_t *) (apic->regs + reg_off));
}
static inline int apic_test_vector(int vec, void *bitmap)
{
return ((1UL << (vec & 31)) & ((uint32_t *)bitmap)[vec >> 5]) != 0;
}
bool vmm_apic_pending_eoi(vmm_vcpu_t *vcpu, int vector)
{
vmm_lapic_t *apic = vcpu->lapic;
return apic_test_vector(vector, apic->regs + APIC_ISR) ||
apic_test_vector(vector, apic->regs + APIC_IRR);
}
static inline void apic_set_vector(int vec, void *bitmap)
{
((uint32_t *)bitmap)[vec >> 5] |= 1UL << (vec & 31);
}
static inline void apic_clear_vector(int vec, void *bitmap)
{
((uint32_t *)bitmap)[vec >> 5] &= ~(1UL << (vec & 31));
}
static inline int vmm_apic_sw_enabled(vmm_lapic_t *apic)
{
return vmm_apic_get_reg(apic, APIC_SPIV) & APIC_SPIV_APIC_ENABLED;
}
static inline int vmm_apic_hw_enabled(vmm_lapic_t *apic)
{
return apic->apic_base & MSR_IA32_APICBASE_ENABLE;
}
inline int vmm_apic_enabled(vmm_lapic_t *apic)
{
return vmm_apic_sw_enabled(apic) && vmm_apic_hw_enabled(apic);
}
#define LVT_MASK \
(APIC_LVT_MASKED | APIC_SEND_PENDING | APIC_VECTOR_MASK)
#define LINT_MASK \
(LVT_MASK | APIC_MODE_MASK | APIC_INPUT_POLARITY | \
APIC_LVT_REMOTE_IRR | APIC_LVT_LEVEL_TRIGGER)
static inline int vmm_apic_id(vmm_lapic_t *apic)
{
return (vmm_apic_get_reg(apic, APIC_ID) >> 24) & 0xff;
}
static inline void apic_set_spiv(vmm_lapic_t *apic, uint32_t val)
{
apic_set_reg(apic, APIC_SPIV, val);
}
static const unsigned int apic_lvt_mask[APIC_LVT_NUM] = {
LVT_MASK , /* part LVTT mask, timer mode mask added at runtime */
LVT_MASK | APIC_MODE_MASK, /* LVTTHMR */
LVT_MASK | APIC_MODE_MASK, /* LVTPC */
LINT_MASK, LINT_MASK, /* LVT0-1 */
LVT_MASK /* LVTERR */
};
static inline void vmm_apic_set_id(vmm_lapic_t *apic, uint8_t id)
{
apic_set_reg(apic, APIC_ID, id << 24);
}
static inline void vmm_apic_set_ldr(vmm_lapic_t *apic, uint32_t id)
{
apic_set_reg(apic, APIC_LDR, id);
}
static inline int apic_lvt_enabled(vmm_lapic_t *apic, int lvt_type)
{
return !(vmm_apic_get_reg(apic, lvt_type) & APIC_LVT_MASKED);
}
static inline int apic_lvt_vector(vmm_lapic_t *apic, int lvt_type)
{
return vmm_apic_get_reg(apic, lvt_type) & APIC_VECTOR_MASK;
}
static inline int vmm_vcpu_is_bsp(vmm_vcpu_t *vcpu)
{
return vcpu->vcpu_id == BOOT_VCPU;
}
static inline int apic_lvt_nmi_mode(uint32_t lvt_val)
{
return (lvt_val & (APIC_MODE_MASK | APIC_LVT_MASKED)) == APIC_DM_NMI;
}
int vmm_apic_compare_prio(vmm_vcpu_t *vcpu1, vmm_vcpu_t *vcpu2)
{
return vcpu1->lapic->arb_prio - vcpu2->lapic->arb_prio;
}
static void UNUSED dump_vector(const char *name, void *bitmap)
{
int vec;
uint32_t *reg = bitmap;
printf("%s = 0x", name);
for (vec = MAX_APIC_VECTOR - APIC_VECTORS_PER_REG;
vec >= 0; vec -= APIC_VECTORS_PER_REG) {
printf("%08x", reg[vec >> 5]);
}
printf("\n");
}
static int find_highest_vector(void *bitmap)
{
int vec;
uint32_t *reg = bitmap;
for (vec = MAX_APIC_VECTOR - APIC_VECTORS_PER_REG;
vec >= 0; vec -= APIC_VECTORS_PER_REG) {
if (reg[vec >> 5])
return fls(reg[vec >> 5]) - 1 + vec;
}
return -1;
}
static uint8_t UNUSED count_vectors(void *bitmap)
{
int vec;
uint32_t *reg = bitmap;
uint8_t count = 0;
for (vec = 0; vec < MAX_APIC_VECTOR; vec += APIC_VECTORS_PER_REG) {
count += hweight32(reg[vec >> 5]);
}
return count;
}
static inline int apic_search_irr(vmm_lapic_t *apic)
{
return find_highest_vector(apic->regs + APIC_IRR);
}
static inline int apic_find_highest_irr(vmm_lapic_t *apic)
{
int result;
if (!apic->irr_pending)
return -1;
result = apic_search_irr(apic);
assert(result == -1 || result >= 16);
return result;
}
static inline void apic_set_irr(int vec, vmm_lapic_t *apic)
{
if (vec != 0x30) {
apic_debug(5, "!settting irr 0x%x\n", vec);
}
apic->irr_pending = true;
apic_set_vector(vec, apic->regs + APIC_IRR);
}
static inline void apic_clear_irr(int vec, vmm_lapic_t *apic)
{
apic_clear_vector(vec, apic->regs + APIC_IRR);
vec = apic_search_irr(apic);
apic->irr_pending = (vec != -1);
}
static inline void apic_set_isr(int vec, vmm_lapic_t *apic)
{
if (apic_test_vector(vec, apic->regs + APIC_ISR)) {
return;
}
apic_set_vector(vec, apic->regs + APIC_ISR);
++apic->isr_count;
/*
* ISR (in service register) bit is set when injecting an interrupt.
* The highest vector is injected. Thus the latest bit set matches
* the highest bit in ISR.
*/
}
static inline int apic_find_highest_isr(vmm_lapic_t *apic)
{
int result;
/*
* Note that isr_count is always 1, and highest_isr_cache
* is always -1, with APIC virtualization enabled.
*/
if (!apic->isr_count)
return -1;
if (apic->highest_isr_cache != -1)
return apic->highest_isr_cache;
result = find_highest_vector(apic->regs + APIC_ISR);
assert(result == -1 || result >= 16);
return result;
}
static inline void apic_clear_isr(int vec, vmm_lapic_t *apic)
{
if (!apic_test_vector(vec, apic->regs + APIC_ISR)) {
return;
}
apic_clear_vector(vec, apic->regs + APIC_ISR);
--apic->isr_count;
apic->highest_isr_cache = -1;
}
int vmm_lapic_find_highest_irr(vmm_vcpu_t *vcpu)
{
int highest_irr;
highest_irr = apic_find_highest_irr(vcpu->lapic);
return highest_irr;
}
static int __apic_accept_irq(vmm_vcpu_t *vcpu, int delivery_mode,
int vector, int level, int trig_mode,
unsigned long *dest_map);
int vmm_apic_set_irq(vmm_vcpu_t *vcpu, struct vmm_lapic_irq *irq,
unsigned long *dest_map)
{
return __apic_accept_irq(vcpu, irq->delivery_mode, irq->vector,
irq->level, irq->trig_mode, dest_map);
}
void vmm_apic_update_tmr(vmm_vcpu_t *vcpu, uint32_t *tmr)
{
vmm_lapic_t *apic = vcpu->lapic;
int i;
for (i = 0; i < 8; i++)
apic_set_reg(apic, APIC_TMR + 0x10 * i, tmr[i]);
}
static void apic_update_ppr(vmm_vcpu_t *vcpu)
{
uint32_t tpr, isrv, ppr, old_ppr;
int isr;
vmm_lapic_t *apic = vcpu->lapic;
old_ppr = vmm_apic_get_reg(apic, APIC_PROCPRI);
tpr = vmm_apic_get_reg(apic, APIC_TASKPRI);
isr = apic_find_highest_isr(apic);
isrv = (isr != -1) ? isr : 0;
if ((tpr & 0xf0) >= (isrv & 0xf0))
ppr = tpr & 0xff;
else
ppr = isrv & 0xf0;
apic_debug(6, "vlapic %p, ppr 0x%x, isr 0x%x, isrv 0x%x\n",
apic, ppr, isr, isrv);
if (old_ppr != ppr) {
apic_set_reg(apic, APIC_PROCPRI, ppr);
if (ppr < old_ppr) {
/* Might have unmasked some pending interrupts */
vmm_vcpu_accept_interrupt(vcpu);
}
}
}
static void apic_set_tpr(vmm_vcpu_t *vcpu, uint32_t tpr)
{
apic_set_reg(vcpu->lapic, APIC_TASKPRI, tpr);
apic_debug(3, "vcpu %d lapic TPR set to %d\n", vcpu->vcpu_id, tpr);
apic_update_ppr(vcpu);
}
int vmm_apic_match_physical_addr(vmm_lapic_t *apic, uint16_t dest)
{
return dest == 0xff || vmm_apic_id(apic) == dest;
}
int vmm_apic_match_logical_addr(vmm_lapic_t *apic, uint8_t mda)
{
int result = 0;
uint32_t logical_id;
logical_id = GET_APIC_LOGICAL_ID(vmm_apic_get_reg(apic, APIC_LDR));
switch (vmm_apic_get_reg(apic, APIC_DFR)) {
case APIC_DFR_FLAT:
if (logical_id & mda)
result = 1;
break;
case APIC_DFR_CLUSTER:
if (((logical_id >> 4) == (mda >> 0x4))
&& (logical_id & mda & 0xf))
result = 1;
break;
default:
apic_debug(1, "Bad DFR: %08x\n", vmm_apic_get_reg(apic, APIC_DFR));
break;
}
return result;
}
int vmm_apic_match_dest(vmm_vcpu_t *vcpu, vmm_lapic_t *source,
int short_hand, int dest, int dest_mode)
{
int result = 0;
vmm_lapic_t *target = vcpu->lapic;
assert(target);
switch (short_hand) {
case APIC_DEST_NOSHORT:
if (dest_mode == 0)
/* Physical mode. */
result = vmm_apic_match_physical_addr(target, dest);
else
/* Logical mode. */
result = vmm_apic_match_logical_addr(target, dest);
break;
case APIC_DEST_SELF:
result = (target == source);
break;
case APIC_DEST_ALLINC:
result = 1;
break;
case APIC_DEST_ALLBUT:
result = (target != source);
break;
default:
apic_debug(2, "apic: Bad dest shorthand value %x\n",
short_hand);
break;
}
apic_debug(4, "target %p, source %p, dest 0x%x, "
"dest_mode 0x%x, short_hand 0x%x",
target, source, dest, dest_mode, short_hand);
if (result) {
apic_debug(4, " MATCH\n");
} else {
apic_debug(4, "\n");
}
return result;
}
int vmm_irq_delivery_to_apic(vmm_vcpu_t *src_vcpu, struct vmm_lapic_irq *irq, unsigned long *dest_map)
{
int i, r = -1;
vmm_lapic_t *src = src_vcpu->lapic;
vmm_t *vmm = src_vcpu->vmm;
vmm_vcpu_t *lowest = NULL;
if (irq->shorthand == APIC_DEST_SELF) {
return vmm_apic_set_irq(src_vcpu, irq, dest_map);
}
for (i = 0; i < vmm->num_vcpus; i++) {
vmm_vcpu_t *dest_vcpu = &vmm->vcpus[i];
if (!vmm_apic_hw_enabled(dest_vcpu->lapic)) {
continue;
}
if (!vmm_apic_match_dest(dest_vcpu, src, irq->shorthand,
irq->dest_id, irq->dest_mode)) {
continue;
}
if (!vmm_is_dm_lowest_prio(irq)) {
// Normal delivery
if (r < 0) {
r = 0;
}
r += vmm_apic_set_irq(dest_vcpu, irq, dest_map);
} else if (vmm_apic_enabled(dest_vcpu->lapic)) {
// Pick vcpu with lowest priority to deliver to
if (!lowest) {
lowest = dest_vcpu;
} else if (vmm_apic_compare_prio(dest_vcpu, lowest) < 0) {
lowest = dest_vcpu;
}
}
}
if (lowest) {
r = vmm_apic_set_irq(lowest, irq, dest_map);
}
return r;
}
/*
* Add a pending IRQ into lapic.
* Return 1 if successfully added and 0 if discarded.
*/
static int __apic_accept_irq(vmm_vcpu_t *vcpu, int delivery_mode,
int vector, int level, int trig_mode,
unsigned long *dest_map)
{
int result = 0;
vmm_lapic_t *apic = vcpu->lapic;
switch (delivery_mode) {
case APIC_DM_LOWEST:
apic->arb_prio++;
case APIC_DM_FIXED:
/* FIXME add logic for vcpu on reset */
if (!vmm_apic_enabled(apic)) {
break;
}
apic_debug(4, "####fixed ipi 0x%x to vcpu %d\n", vector, vcpu->vcpu_id);
result = 1;
apic_set_irr(vector, apic);
vmm_vcpu_accept_interrupt(vcpu);
break;
case APIC_DM_NMI:
case APIC_DM_REMRD:
result = 1;
vmm_vcpu_accept_interrupt(vcpu);
break;
case APIC_DM_SMI:
apic_debug(2, "Ignoring guest SMI\n");
break;
case APIC_DM_INIT:
apic_debug(2, "Got init ipi on vcpu %d\n", vcpu->vcpu_id);
if (!trig_mode || level) {
if (apic->state == LAPIC_STATE_RUN) {
/* Already running, ignore inits */
break;
}
result = 1;
vmm_lapic_reset(vcpu);
apic->arb_prio = vmm_apic_id(apic);
apic->state = LAPIC_STATE_WAITSIPI;
} else {
apic_debug(2, "Ignoring de-assert INIT to vcpu %d\n",
vcpu->vcpu_id);
}
break;
case APIC_DM_STARTUP:
if (apic->state != LAPIC_STATE_WAITSIPI) {
apic_debug(1, "Received SIPI while processor was not in wait for SIPI state\n");
} else {
apic_debug(2, "SIPI to vcpu %d vector 0x%02x\n",
vcpu->vcpu_id, vector);
result = 1;
apic->sipi_vector = vector;
apic->state = LAPIC_STATE_RUN;
/* Start the VCPU thread. */
vmm_start_ap_vcpu(vcpu, vector);
}
break;
case APIC_DM_EXTINT:
/* extints are handled by vmm_apic_consume_extints */
printf("extint should not come to this function. vcpu %d\n", vcpu->vcpu_id);
assert(0);
break;
default:
printf("TODO: unsupported lapic ipi delivery mode %x", delivery_mode);
assert(0);
break;
}
return result;
}
static int apic_set_eoi(vmm_vcpu_t *vcpu)
{
vmm_lapic_t *apic = vcpu->lapic;
int vector = apic_find_highest_isr(apic);
/*
* Not every write EOI will has corresponding ISR,
* one example is when Kernel check timer on setup_IO_APIC
*/
if (vector == -1)
return vector;
apic_clear_isr(vector, apic);
apic_update_ppr(vcpu);
/* If another interrupt is pending, raise it */
vmm_vcpu_accept_interrupt(vcpu);
return vector;
}
static void apic_send_ipi(vmm_vcpu_t *vcpu)
{
vmm_lapic_t *apic = vcpu->lapic;
uint32_t icr_low = vmm_apic_get_reg(apic, APIC_ICR);
uint32_t icr_high = vmm_apic_get_reg(apic, APIC_ICR2);
struct vmm_lapic_irq irq;
irq.vector = icr_low & APIC_VECTOR_MASK;
irq.delivery_mode = icr_low & APIC_MODE_MASK;
irq.dest_mode = icr_low & APIC_DEST_MASK;
irq.level = icr_low & APIC_INT_ASSERT;
irq.trig_mode = icr_low & APIC_INT_LEVELTRIG;
irq.shorthand = icr_low & APIC_SHORT_MASK;
irq.dest_id = GET_APIC_DEST_FIELD(icr_high);
apic_debug(3, "icr_high 0x%x, icr_low 0x%x, "
"short_hand 0x%x, dest 0x%x, trig_mode 0x%x, level 0x%x, "
"dest_mode 0x%x, delivery_mode 0x%x, vector 0x%x\n",
icr_high, icr_low, irq.shorthand, irq.dest_id,
irq.trig_mode, irq.level, irq.dest_mode, irq.delivery_mode,
irq.vector);
vmm_irq_delivery_to_apic(vcpu, &irq, NULL);
}
static uint32_t __apic_read(vmm_lapic_t *apic, unsigned int offset)
{
uint32_t val = 0;
if (offset >= LAPIC_MMIO_LENGTH)
return 0;
switch (offset) {
case APIC_ID:
val = vmm_apic_id(apic) << 24;
break;
case APIC_ARBPRI:
apic_debug(2, "Access APIC ARBPRI register which is for P6\n");
break;
case APIC_TMCCT: /* Timer CCR */
break;
case APIC_PROCPRI:
val = vmm_apic_get_reg(apic, offset);
break;
default:
val = vmm_apic_get_reg(apic, offset);
break;
}
return val;
}
static void apic_manage_nmi_watchdog(vmm_lapic_t *apic, uint32_t lvt0_val)
{
int nmi_wd_enabled = apic_lvt_nmi_mode(vmm_apic_get_reg(apic, APIC_LVT0));
if (apic_lvt_nmi_mode(lvt0_val)) {
if (!nmi_wd_enabled) {
apic_debug(4, "Receive NMI setting on APIC_LVT0 \n");
}
}
}
static int apic_reg_write(vmm_vcpu_t *vcpu, uint32_t reg, uint32_t val)
{
vmm_lapic_t *apic = vcpu->lapic;
int ret = 0;
switch (reg) {
case APIC_ID: /* Local APIC ID */
vmm_apic_set_id(apic, val >> 24);
break;
case APIC_TASKPRI:
apic_set_tpr(vcpu, val & 0xff);
break;
case APIC_EOI:
apic_set_eoi(vcpu);
break;
case APIC_LDR:
vmm_apic_set_ldr(apic, val & APIC_LDR_MASK);
break;
case APIC_DFR:
apic_set_reg(apic, APIC_DFR, val | 0x0FFFFFFF);
break;
case APIC_SPIV: {
uint32_t mask = 0x3ff;
if (vmm_apic_get_reg(apic, APIC_LVR) & APIC_LVR_DIRECTED_EOI)
mask |= APIC_SPIV_DIRECTED_EOI;
apic_set_spiv(apic, val & mask);
if (!(val & APIC_SPIV_APIC_ENABLED)) {
int i;
uint32_t lvt_val;
for (i = 0; i < APIC_LVT_NUM; i++) {
lvt_val = vmm_apic_get_reg(apic,
APIC_LVTT + 0x10 * i);
apic_set_reg(apic, APIC_LVTT + 0x10 * i,
lvt_val | APIC_LVT_MASKED);
}
// atomic_set(&apic->lapic_timer.pending, 0);
}
break;
}
case APIC_ICR:
/* No delay here, so we always clear the pending bit */
apic_set_reg(apic, APIC_ICR, val & ~(BIT(12)));
apic_send_ipi(vcpu);
break;
case APIC_ICR2:
val &= 0xff000000;
apic_set_reg(apic, APIC_ICR2, val);
break;
case APIC_LVT0:
apic_manage_nmi_watchdog(apic, val);
case APIC_LVTTHMR:
case APIC_LVTPC:
case APIC_LVT1:
case APIC_LVTERR:
/* TODO: Check vector */
if (!vmm_apic_sw_enabled(apic))
val |= APIC_LVT_MASKED;
val &= apic_lvt_mask[(reg - APIC_LVTT) >> 4];
apic_set_reg(apic, reg, val);
break;
case APIC_LVTT:
apic_set_reg(apic, APIC_LVTT, val);
break;
case APIC_TMICT:
apic_set_reg(apic, APIC_TMICT, val);
break;
case APIC_TDCR:
apic_set_reg(apic, APIC_TDCR, val);
break;
default:
ret = 1;
break;
}
if (ret)
apic_debug(2, "Local APIC Write to read-only register %x\n", reg);
return ret;
}
void vmm_apic_mmio_write(vmm_vcpu_t *vcpu, void *cookie, uint32_t offset,
int len, const uint32_t data)
{
(void)cookie;
/*
* APIC register must be aligned on 128-bits boundary.
* 32/64/128 bits registers must be accessed thru 32 bits.
* Refer SDM 8.4.1
*/
if (len != 4 || (offset & 0xf)) {
apic_debug(1, "apic write: bad size=%d %x\n", len, offset);
return;
}
/* too common printing */
if (offset != APIC_EOI)
apic_debug(6, "lapic mmio write at %s: offset 0x%x with length 0x%x, and value is "
"0x%x\n", __func__, offset, len, data);
apic_reg_write(vcpu, offset & 0xff0, data);
}
static int apic_reg_read(vmm_lapic_t *apic, uint32_t offset, int len,
void *data)
{
unsigned char alignment = offset & 0xf;
uint32_t result;
/* this bitmask has a bit cleared for each reserved register */
static const uint64_t rmask = 0x43ff01ffffffe70cULL;
if ((alignment + len) > 4) {
apic_debug(2, "APIC READ: alignment error %x %d\n",
offset, len);
return 1;
}
if (offset > 0x3f0 || !(rmask & (1ULL << (offset >> 4)))) {
apic_debug(2, "APIC_READ: read reserved register %x\n",
offset);
return 1;
}
result = __apic_read(apic, offset & ~0xf);
switch (len) {
case 1:
case 2:
case 4:
memcpy(data, (char *)&result + alignment, len);
break;
default:
apic_debug(2, "Local APIC read with len = %x, "
"should be 1,2, or 4 instead\n", len);
break;
}
return 0;
}
void vmm_apic_mmio_read(vmm_vcpu_t *vcpu, void *cookie, uint32_t offset,
int len, uint32_t *data)
{
vmm_lapic_t *apic = vcpu->lapic;
(void)cookie;
apic_reg_read(apic, offset, len, data);
apic_debug(6, "lapic mmio read on vcpu %d, reg %08x = %08x\n", vcpu->vcpu_id, offset, *data);
return;
}
void vmm_free_lapic(vmm_vcpu_t *vcpu)
{
vmm_lapic_t *apic = vcpu->lapic;
if (!apic)
return;
if (apic->regs) {
free(apic->regs);
}
free(apic);
}
void vmm_lapic_set_base_msr(vmm_vcpu_t *vcpu, uint32_t value)
{
apic_debug(2, "IA32_APIC_BASE MSR set to %08x on vcpu %d\n", value, vcpu->vcpu_id);
if (!(value & MSR_IA32_APICBASE_ENABLE)) {
printf("Warning! Local apic has been disabled by MSR on vcpu %d. "
"This will probably not work!\n", vcpu->vcpu_id);
}
vcpu->lapic->apic_base = value;
}
uint32_t vmm_lapic_get_base_msr(vmm_vcpu_t *vcpu)
{
uint32_t value = vcpu->lapic->apic_base;
if (vmm_vcpu_is_bsp(vcpu)) {
value |= MSR_IA32_APICBASE_BSP;
} else {
value &= ~MSR_IA32_APICBASE_BSP;
}
apic_debug(2, "Read from IA32_APIC_BASE MSR returns %08x on vcpu %d\n", value, vcpu->vcpu_id);
return value;
}
void vmm_lapic_reset(vmm_vcpu_t *vcpu)
{
vmm_lapic_t *apic;
int i;
apic_debug(4, "%s\n", __func__);
assert(vcpu);
apic = vcpu->lapic;
assert(apic != NULL);
/* Stop the timer in case it's a reset to an active apic */
vmm_apic_set_id(apic, vcpu->vcpu_id); /* In agreement with ACPI code */
apic_set_reg(apic, APIC_LVR, APIC_VERSION);
for (i = 0; i < APIC_LVT_NUM; i++)
apic_set_reg(apic, APIC_LVTT + 0x10 * i, APIC_LVT_MASKED);
apic_set_reg(apic, APIC_DFR, 0xffffffffU);
apic_set_spiv(apic, 0xff);
apic_set_reg(apic, APIC_TASKPRI, 0);
vmm_apic_set_ldr(apic, 0);
apic_set_reg(apic, APIC_ESR, 0);
apic_set_reg(apic, APIC_ICR, 0);
apic_set_reg(apic, APIC_ICR2, 0);
apic_set_reg(apic, APIC_TDCR, 0);
apic_set_reg(apic, APIC_TMICT, 0);
for (i = 0; i < 8; i++) {
apic_set_reg(apic, APIC_IRR + 0x10 * i, 0);
apic_set_reg(apic, APIC_ISR + 0x10 * i, 0);
apic_set_reg(apic, APIC_TMR + 0x10 * i, 0);
}
apic->irr_pending = 0;
apic->isr_count = 0;
apic->highest_isr_cache = -1;
apic_update_ppr(vcpu);
vcpu->lapic->arb_prio = 0;
apic_debug(4, "%s: vcpu=%p, id=%d, base_msr="
"0x%016x\n", __func__,
vcpu, vmm_apic_id(apic),
apic->apic_base);
if (vcpu->vcpu_id == BOOT_VCPU) {
/* Bootstrap boot vcpu lapic in virtual wire mode */
apic_set_reg(apic, APIC_LVT0,
SET_APIC_DELIVERY_MODE(0, APIC_MODE_EXTINT));
apic_set_reg(apic, APIC_SPIV, APIC_SPIV_APIC_ENABLED);
assert(vmm_apic_sw_enabled(apic));
} else {
apic_set_reg(apic, APIC_SPIV, 0);
}
}
int vmm_create_lapic(vmm_vcpu_t *vcpu, int enabled)
{
vmm_lapic_t *apic;
assert(vcpu != NULL);
apic_debug(2, "apic_init %d\n", vcpu->vcpu_id);
apic = malloc(sizeof(*apic));
if (!apic)
goto nomem;
vcpu->lapic = apic;
apic->regs = malloc(sizeof(struct local_apic_regs)); // TODO this is a page; allocate a page
if (!apic->regs) {
printf("malloc apic regs error for vcpu %x\n",
vcpu->vcpu_id);
goto nomem_free_apic;
}
if (enabled) {
vmm_lapic_set_base_msr(vcpu, APIC_DEFAULT_PHYS_BASE | MSR_IA32_APICBASE_ENABLE);
} else {
vmm_lapic_set_base_msr(vcpu, APIC_DEFAULT_PHYS_BASE);
}
/* mainly init registers */
vmm_lapic_reset(vcpu);
return 0;
nomem_free_apic:
free(apic);
nomem:
return -1;
}
/* Return 1 if this vcpu should accept a PIC interrupt */
int vmm_apic_accept_pic_intr(vmm_vcpu_t *vcpu)
{
vmm_lapic_t *apic = vcpu->lapic;
uint32_t lvt0 = vmm_apic_get_reg(apic, APIC_LVT0);
return ((lvt0 & APIC_LVT_MASKED) == 0 &&
GET_APIC_DELIVERY_MODE(lvt0) == APIC_MODE_EXTINT &&
vmm_apic_sw_enabled(vcpu->lapic));
}
/* Service an interrupt */
int vmm_apic_get_interrupt(vmm_vcpu_t *vcpu)
{
vmm_lapic_t *apic = vcpu->lapic;
int vector = vmm_apic_has_interrupt(vcpu);
if (vector == 1) {
return pic_get_interrupt(vcpu->vmm);
} else if (vector == -1) {
return -1;
}
apic_set_isr(vector, apic);
apic_update_ppr(vcpu);
apic_clear_irr(vector, apic);
return vector;
}
/* Return which vector is next up for servicing */
int vmm_apic_has_interrupt(vmm_vcpu_t *vcpu)
{
vmm_lapic_t *apic = vcpu->lapic;
int highest_irr;
if (vmm_apic_accept_pic_intr(vcpu) && pic_has_interrupt(vcpu->vmm)) {
return 1;
}
highest_irr = apic_find_highest_irr(apic);
if ((highest_irr == -1) ||
((highest_irr & 0xF0) <= vmm_apic_get_reg(apic, APIC_PROCPRI))) {
return -1;
}
return highest_irr;
}
#if 0
int vmm_apic_local_deliver(vmm_vcpu_t *vcpu, int lvt_type)
{
vmm_lapic_t *apic = vcpu->lapic;
uint32_t reg = vmm_apic_get_reg(apic, lvt_type);
int vector, mode, trig_mode;
if (!(reg & APIC_LVT_MASKED)) {
vector = reg & APIC_VECTOR_MASK;
mode = reg & APIC_MODE_MASK;
trig_mode = reg & APIC_LVT_LEVEL_TRIGGER;
return __apic_accept_irq(vcpu, mode, vector, 1, trig_mode, NULL);
}
return 0;
}
#endif