📄 lapic.c
字号:
/* * Local APIC virtualization * * Copyright (C) 2006 Qumranet, Inc. * Copyright (C) 2007 Novell * Copyright (C) 2007 Intel * * 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 "kvm.h"#include <linux/kvm.h>#include <linux/mm.h>#include <linux/highmem.h>#include <linux/smp.h>#include <linux/hrtimer.h>#include <linux/io.h>#include <linux/module.h>#include <asm/processor.h>#include <asm/msr.h>#include <asm/page.h>#include <asm/current.h>#include <asm/apicdef.h>#include <asm/atomic.h>#include <asm/div64.h>#include "irq.h"#define PRId64 "d"#define PRIx64 "llx"#define PRIu64 "u"#define PRIo64 "o"#define APIC_BUS_CYCLE_NS 1/* #define apic_debug(fmt,arg...) printk(KERN_WARNING fmt,##arg) */#define apic_debug(fmt, arg...)#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 (1 << 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 VEC_POS(v) ((v) & (32 - 1))#define REG_POS(v) (((v) >> 5) << 4)static inline u32 apic_get_reg(struct kvm_lapic *apic, int reg_off){ return *((u32 *) (apic->regs + reg_off));}static inline void apic_set_reg(struct kvm_lapic *apic, int reg_off, u32 val){ *((u32 *) (apic->regs + reg_off)) = val;}static inline int apic_test_and_set_vector(int vec, void *bitmap){ return test_and_set_bit(VEC_POS(vec), (bitmap) + REG_POS(vec));}static inline int apic_test_and_clear_vector(int vec, void *bitmap){ return test_and_clear_bit(VEC_POS(vec), (bitmap) + REG_POS(vec));}static inline void apic_set_vector(int vec, void *bitmap){ set_bit(VEC_POS(vec), (bitmap) + REG_POS(vec));}static inline void apic_clear_vector(int vec, void *bitmap){ clear_bit(VEC_POS(vec), (bitmap) + REG_POS(vec));}static inline int apic_hw_enabled(struct kvm_lapic *apic){ return (apic)->vcpu->apic_base & MSR_IA32_APICBASE_ENABLE;}static inline int apic_sw_enabled(struct kvm_lapic *apic){ return apic_get_reg(apic, APIC_SPIV) & APIC_SPIV_APIC_ENABLED;}static inline int apic_enabled(struct kvm_lapic *apic){ return apic_sw_enabled(apic) && 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 kvm_apic_id(struct kvm_lapic *apic){ return (apic_get_reg(apic, APIC_ID) >> 24) & 0xff;}static inline int apic_lvt_enabled(struct kvm_lapic *apic, int lvt_type){ return !(apic_get_reg(apic, lvt_type) & APIC_LVT_MASKED);}static inline int apic_lvt_vector(struct kvm_lapic *apic, int lvt_type){ return apic_get_reg(apic, lvt_type) & APIC_VECTOR_MASK;}static inline int apic_lvtt_period(struct kvm_lapic *apic){ return apic_get_reg(apic, APIC_LVTT) & APIC_LVT_TIMER_PERIODIC;}static unsigned int apic_lvt_mask[APIC_LVT_NUM] = { LVT_MASK | APIC_LVT_TIMER_PERIODIC, /* LVTT */ LVT_MASK | APIC_MODE_MASK, /* LVTTHMR */ LVT_MASK | APIC_MODE_MASK, /* LVTPC */ LINT_MASK, LINT_MASK, /* LVT0-1 */ LVT_MASK /* LVTERR */};static int find_highest_vector(void *bitmap){ u32 *word = bitmap; int word_offset = MAX_APIC_VECTOR >> 5; while ((word_offset != 0) && (word[(--word_offset) << 2] == 0)) continue; if (likely(!word_offset && !word[0])) return -1; else return fls(word[word_offset << 2]) - 1 + (word_offset << 5);}static inline int apic_test_and_set_irr(int vec, struct kvm_lapic *apic){ return apic_test_and_set_vector(vec, apic->regs + APIC_IRR);}static inline void apic_clear_irr(int vec, struct kvm_lapic *apic){ apic_clear_vector(vec, apic->regs + APIC_IRR);}static inline int apic_find_highest_irr(struct kvm_lapic *apic){ int result; result = find_highest_vector(apic->regs + APIC_IRR); ASSERT(result == -1 || result >= 16); return result;}int kvm_lapic_find_highest_irr(struct kvm_vcpu *vcpu){ struct kvm_lapic *apic = (struct kvm_lapic *)vcpu->apic; int highest_irr; if (!apic) return 0; highest_irr = apic_find_highest_irr(apic); return highest_irr;}EXPORT_SYMBOL_GPL(kvm_lapic_find_highest_irr);int kvm_apic_set_irq(struct kvm_lapic *apic, u8 vec, u8 trig){ if (!apic_test_and_set_irr(vec, apic)) { /* a new pending irq is set in IRR */ if (trig) apic_set_vector(vec, apic->regs + APIC_TMR); else apic_clear_vector(vec, apic->regs + APIC_TMR); kvm_vcpu_kick(apic->vcpu); return 1; } return 0;}static inline int apic_find_highest_isr(struct kvm_lapic *apic){ int result; result = find_highest_vector(apic->regs + APIC_ISR); ASSERT(result == -1 || result >= 16); return result;}static void apic_update_ppr(struct kvm_lapic *apic){ u32 tpr, isrv, ppr; int isr; tpr = 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("vlapic %p, ppr 0x%x, isr 0x%x, isrv 0x%x", apic, ppr, isr, isrv); apic_set_reg(apic, APIC_PROCPRI, ppr);}static void apic_set_tpr(struct kvm_lapic *apic, u32 tpr){ apic_set_reg(apic, APIC_TASKPRI, tpr); apic_update_ppr(apic);}int kvm_apic_match_physical_addr(struct kvm_lapic *apic, u16 dest){ return kvm_apic_id(apic) == dest;}int kvm_apic_match_logical_addr(struct kvm_lapic *apic, u8 mda){ int result = 0; u8 logical_id; logical_id = GET_APIC_LOGICAL_ID(apic_get_reg(apic, APIC_LDR)); switch (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: printk(KERN_WARNING "Bad DFR vcpu %d: %08x\n", apic->vcpu->vcpu_id, apic_get_reg(apic, APIC_DFR)); break; } return result;}static int apic_match_dest(struct kvm_vcpu *vcpu, struct kvm_lapic *source, int short_hand, int dest, int dest_mode){ int result = 0; struct kvm_lapic *target = vcpu->apic; apic_debug("target %p, source %p, dest 0x%x, " "dest_mode 0x%x, short_hand 0x%x", target, source, dest, dest_mode, short_hand); ASSERT(!target); switch (short_hand) { case APIC_DEST_NOSHORT: if (dest_mode == 0) { /* Physical mode. */ if ((dest == 0xFF) || (dest == kvm_apic_id(target))) result = 1; } else /* Logical mode. */ result = kvm_apic_match_logical_addr(target, dest); break; case APIC_DEST_SELF: if (target == source) result = 1; break; case APIC_DEST_ALLINC: result = 1; break; case APIC_DEST_ALLBUT: if (target != source) result = 1; break; default: printk(KERN_WARNING "Bad dest shorthand value %x\n", short_hand); break; } return result;}/* * Add a pending IRQ into lapic. * Return 1 if successfully added and 0 if discarded. */static int __apic_accept_irq(struct kvm_lapic *apic, int delivery_mode, int vector, int level, int trig_mode){ int orig_irr, result = 0; struct kvm_vcpu *vcpu = apic->vcpu; switch (delivery_mode) { case APIC_DM_FIXED: case APIC_DM_LOWEST: /* FIXME add logic for vcpu on reset */ if (unlikely(!apic_enabled(apic))) break; orig_irr = apic_test_and_set_irr(vector, apic); if (orig_irr && trig_mode) { apic_debug("level trig mode repeatedly for vector %d", vector); break; } if (trig_mode) { apic_debug("level trig mode for vector %d", vector); apic_set_vector(vector, apic->regs + APIC_TMR); } else apic_clear_vector(vector, apic->regs + APIC_TMR); if (vcpu->mp_state == VCPU_MP_STATE_RUNNABLE) kvm_vcpu_kick(vcpu); else if (vcpu->mp_state == VCPU_MP_STATE_HALTED) { vcpu->mp_state = VCPU_MP_STATE_RUNNABLE; if (waitqueue_active(&vcpu->wq)) wake_up_interruptible(&vcpu->wq); } result = (orig_irr == 0); break; case APIC_DM_REMRD: printk(KERN_DEBUG "Ignoring delivery mode 3\n"); break; case APIC_DM_SMI: printk(KERN_DEBUG "Ignoring guest SMI\n"); break; case APIC_DM_NMI: printk(KERN_DEBUG "Ignoring guest NMI\n"); break; case APIC_DM_INIT: if (level) { if (vcpu->mp_state == VCPU_MP_STATE_RUNNABLE) printk(KERN_DEBUG "INIT on a runnable vcpu %d\n", vcpu->vcpu_id); vcpu->mp_state = VCPU_MP_STATE_INIT_RECEIVED; kvm_vcpu_kick(vcpu); } else { printk(KERN_DEBUG "Ignoring de-assert INIT to vcpu %d\n", vcpu->vcpu_id); } break; case APIC_DM_STARTUP: printk(KERN_DEBUG "SIPI to vcpu %d vector 0x%02x\n", vcpu->vcpu_id, vector); if (vcpu->mp_state == VCPU_MP_STATE_INIT_RECEIVED) { vcpu->sipi_vector = vector; vcpu->mp_state = VCPU_MP_STATE_SIPI_RECEIVED; if (waitqueue_active(&vcpu->wq)) wake_up_interruptible(&vcpu->wq); } break; default: printk(KERN_ERR "TODO: unsupported delivery mode %x\n", delivery_mode); break; } return result;}struct kvm_lapic *kvm_apic_round_robin(struct kvm *kvm, u8 vector, unsigned long bitmap){ int vcpu_id; int last; int next; struct kvm_lapic *apic; last = kvm->round_robin_prev_vcpu; next = last; do { if (++next == KVM_MAX_VCPUS) next = 0; if (kvm->vcpus[next] == NULL || !test_bit(next, &bitmap)) continue; apic = kvm->vcpus[next]->apic; if (apic && apic_enabled(apic)) break; apic = NULL; } while (next != last); kvm->round_robin_prev_vcpu = next; if (!apic) { vcpu_id = ffs(bitmap) - 1; if (vcpu_id < 0) { vcpu_id = 0; printk(KERN_DEBUG "vcpu not ready for apic_round_robin\n"); } apic = kvm->vcpus[vcpu_id]->apic; } return apic;}static void apic_set_eoi(struct kvm_lapic *apic){ 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; apic_clear_vector(vector, apic->regs + APIC_ISR); apic_update_ppr(apic); if (apic_test_and_clear_vector(vector, apic->regs + APIC_TMR)) kvm_ioapic_update_eoi(apic->vcpu->kvm, vector);}static void apic_send_ipi(struct kvm_lapic *apic){ u32 icr_low = apic_get_reg(apic, APIC_ICR); u32 icr_high = apic_get_reg(apic, APIC_ICR2); unsigned int dest = GET_APIC_DEST_FIELD(icr_high); unsigned int short_hand = icr_low & APIC_SHORT_MASK; unsigned int trig_mode = icr_low & APIC_INT_LEVELTRIG; unsigned int level = icr_low & APIC_INT_ASSERT; unsigned int dest_mode = icr_low & APIC_DEST_MASK; unsigned int delivery_mode = icr_low & APIC_MODE_MASK; unsigned int vector = icr_low & APIC_VECTOR_MASK; struct kvm_lapic *target; struct kvm_vcpu *vcpu; unsigned long lpr_map = 0; int i; apic_debug("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, short_hand, dest, trig_mode, level, dest_mode, delivery_mode, vector); for (i = 0; i < KVM_MAX_VCPUS; i++) { vcpu = apic->vcpu->kvm->vcpus[i]; if (!vcpu) continue; if (vcpu->apic && apic_match_dest(vcpu, apic, short_hand, dest, dest_mode)) { if (delivery_mode == APIC_DM_LOWEST) set_bit(vcpu->vcpu_id, &lpr_map); else __apic_accept_irq(vcpu->apic, delivery_mode, vector, level, trig_mode); } } if (delivery_mode == APIC_DM_LOWEST) { target = kvm_apic_round_robin(vcpu->kvm, vector, lpr_map); if (target != NULL) __apic_accept_irq(target, delivery_mode, vector, level, trig_mode); }}static u32 apic_get_tmcct(struct kvm_lapic *apic){ u64 counter_passed; ktime_t passed, now; u32 tmcct; ASSERT(apic != NULL); now = apic->timer.dev.base->get_time(); tmcct = apic_get_reg(apic, APIC_TMICT); /* if initial count is 0, current count should also be 0 */ if (tmcct == 0) return 0; if (unlikely(ktime_to_ns(now) <= ktime_to_ns(apic->timer.last_update))) { /* Wrap around */ passed = ktime_add(( { (ktime_t) { .tv64 = KTIME_MAX - (apic->timer.last_update).tv64}; } ), now); apic_debug("time elapsed\n"); } else passed = ktime_sub(now, apic->timer.last_update); counter_passed = div64_64(ktime_to_ns(passed), (APIC_BUS_CYCLE_NS * apic->timer.divide_count)); if (counter_passed > tmcct) { if (unlikely(!apic_lvtt_period(apic))) { /* one-shot timers stick at 0 until reset */ tmcct = 0; } else { /* * periodic timers reset to APIC_TMICT when they * hit 0. The while loop simulates this happening N * times. (counter_passed %= tmcct) would also work, * but might be slower or not work on 32-bit?? */ while (counter_passed > tmcct) counter_passed -= tmcct; tmcct -= counter_passed; } } else { tmcct -= counter_passed;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -