e5ad936b0f
This enables acceleration for MMIO-based TPR registers accesses of 32-bit Windows guest systems. It is mostly useful with KVM enabled, either on older Intel CPUs (without flexpriority feature, can also be manually disabled for testing) or any current AMD processor. The approach introduced here is derived from the original version of qemu-kvm. It was refactored, documented, and extended by support for user space APIC emulation, both with and without KVM acceleration. The VMState format was kept compatible, so was the ABI to the option ROM that implements the guest-side para-virtualized driver service. This enables seamless migration from qemu-kvm to upstream or, one day, between KVM and TCG mode. The basic concept goes like this: - VAPIC PV interface consisting of I/O port 0x7e and (for KVM in-kernel irqchip) a vmcall hypercall is registered - VAPIC option ROM is loaded into guest - option ROM activates TPR MMIO access reporting via port 0x7e - TPR accesses are trapped and patched in the guest to call into option ROM instead, VAPIC support is enabled - option ROM TPR helpers track state in memory and invoke hypercall to poll for pending IRQs if required Signed-off-by: Jan Kiszka <jan.kiszka@siemens.com> Signed-off-by: Avi Kivity <avi@redhat.com>
180 lines
5.1 KiB
C
180 lines
5.1 KiB
C
/*
|
|
* KVM in-kernel APIC support
|
|
*
|
|
* Copyright (c) 2011 Siemens AG
|
|
*
|
|
* Authors:
|
|
* Jan Kiszka <jan.kiszka@siemens.com>
|
|
*
|
|
* This work is licensed under the terms of the GNU GPL version 2.
|
|
* See the COPYING file in the top-level directory.
|
|
*/
|
|
#include "hw/apic_internal.h"
|
|
#include "kvm.h"
|
|
|
|
static inline void kvm_apic_set_reg(struct kvm_lapic_state *kapic,
|
|
int reg_id, uint32_t val)
|
|
{
|
|
*((uint32_t *)(kapic->regs + (reg_id << 4))) = val;
|
|
}
|
|
|
|
static inline uint32_t kvm_apic_get_reg(struct kvm_lapic_state *kapic,
|
|
int reg_id)
|
|
{
|
|
return *((uint32_t *)(kapic->regs + (reg_id << 4)));
|
|
}
|
|
|
|
void kvm_put_apic_state(DeviceState *d, struct kvm_lapic_state *kapic)
|
|
{
|
|
APICCommonState *s = DO_UPCAST(APICCommonState, busdev.qdev, d);
|
|
int i;
|
|
|
|
memset(kapic, 0, sizeof(kapic));
|
|
kvm_apic_set_reg(kapic, 0x2, s->id << 24);
|
|
kvm_apic_set_reg(kapic, 0x8, s->tpr);
|
|
kvm_apic_set_reg(kapic, 0xd, s->log_dest << 24);
|
|
kvm_apic_set_reg(kapic, 0xe, s->dest_mode << 28 | 0x0fffffff);
|
|
kvm_apic_set_reg(kapic, 0xf, s->spurious_vec);
|
|
for (i = 0; i < 8; i++) {
|
|
kvm_apic_set_reg(kapic, 0x10 + i, s->isr[i]);
|
|
kvm_apic_set_reg(kapic, 0x18 + i, s->tmr[i]);
|
|
kvm_apic_set_reg(kapic, 0x20 + i, s->irr[i]);
|
|
}
|
|
kvm_apic_set_reg(kapic, 0x28, s->esr);
|
|
kvm_apic_set_reg(kapic, 0x30, s->icr[0]);
|
|
kvm_apic_set_reg(kapic, 0x31, s->icr[1]);
|
|
for (i = 0; i < APIC_LVT_NB; i++) {
|
|
kvm_apic_set_reg(kapic, 0x32 + i, s->lvt[i]);
|
|
}
|
|
kvm_apic_set_reg(kapic, 0x38, s->initial_count);
|
|
kvm_apic_set_reg(kapic, 0x3e, s->divide_conf);
|
|
}
|
|
|
|
void kvm_get_apic_state(DeviceState *d, struct kvm_lapic_state *kapic)
|
|
{
|
|
APICCommonState *s = DO_UPCAST(APICCommonState, busdev.qdev, d);
|
|
int i, v;
|
|
|
|
s->id = kvm_apic_get_reg(kapic, 0x2) >> 24;
|
|
s->tpr = kvm_apic_get_reg(kapic, 0x8);
|
|
s->arb_id = kvm_apic_get_reg(kapic, 0x9);
|
|
s->log_dest = kvm_apic_get_reg(kapic, 0xd) >> 24;
|
|
s->dest_mode = kvm_apic_get_reg(kapic, 0xe) >> 28;
|
|
s->spurious_vec = kvm_apic_get_reg(kapic, 0xf);
|
|
for (i = 0; i < 8; i++) {
|
|
s->isr[i] = kvm_apic_get_reg(kapic, 0x10 + i);
|
|
s->tmr[i] = kvm_apic_get_reg(kapic, 0x18 + i);
|
|
s->irr[i] = kvm_apic_get_reg(kapic, 0x20 + i);
|
|
}
|
|
s->esr = kvm_apic_get_reg(kapic, 0x28);
|
|
s->icr[0] = kvm_apic_get_reg(kapic, 0x30);
|
|
s->icr[1] = kvm_apic_get_reg(kapic, 0x31);
|
|
for (i = 0; i < APIC_LVT_NB; i++) {
|
|
s->lvt[i] = kvm_apic_get_reg(kapic, 0x32 + i);
|
|
}
|
|
s->initial_count = kvm_apic_get_reg(kapic, 0x38);
|
|
s->divide_conf = kvm_apic_get_reg(kapic, 0x3e);
|
|
|
|
v = (s->divide_conf & 3) | ((s->divide_conf >> 1) & 4);
|
|
s->count_shift = (v + 1) & 7;
|
|
|
|
s->initial_count_load_time = qemu_get_clock_ns(vm_clock);
|
|
apic_next_timer(s, s->initial_count_load_time);
|
|
}
|
|
|
|
static void kvm_apic_set_base(APICCommonState *s, uint64_t val)
|
|
{
|
|
s->apicbase = val;
|
|
}
|
|
|
|
static void kvm_apic_set_tpr(APICCommonState *s, uint8_t val)
|
|
{
|
|
s->tpr = (val & 0x0f) << 4;
|
|
}
|
|
|
|
static uint8_t kvm_apic_get_tpr(APICCommonState *s)
|
|
{
|
|
return s->tpr >> 4;
|
|
}
|
|
|
|
static void kvm_apic_enable_tpr_reporting(APICCommonState *s, bool enable)
|
|
{
|
|
struct kvm_tpr_access_ctl ctl = {
|
|
.enabled = enable
|
|
};
|
|
|
|
kvm_vcpu_ioctl(s->cpu_env, KVM_TPR_ACCESS_REPORTING, &ctl);
|
|
}
|
|
|
|
static void kvm_apic_vapic_base_update(APICCommonState *s)
|
|
{
|
|
struct kvm_vapic_addr vapid_addr = {
|
|
.vapic_addr = s->vapic_paddr,
|
|
};
|
|
int ret;
|
|
|
|
ret = kvm_vcpu_ioctl(s->cpu_env, KVM_SET_VAPIC_ADDR, &vapid_addr);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "KVM: setting VAPIC address failed (%s)\n",
|
|
strerror(-ret));
|
|
abort();
|
|
}
|
|
}
|
|
|
|
static void do_inject_external_nmi(void *data)
|
|
{
|
|
APICCommonState *s = data;
|
|
CPUState *env = s->cpu_env;
|
|
uint32_t lvt;
|
|
int ret;
|
|
|
|
cpu_synchronize_state(env);
|
|
|
|
lvt = s->lvt[APIC_LVT_LINT1];
|
|
if (!(lvt & APIC_LVT_MASKED) && ((lvt >> 8) & 7) == APIC_DM_NMI) {
|
|
ret = kvm_vcpu_ioctl(env, KVM_NMI);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "KVM: injection failed, NMI lost (%s)\n",
|
|
strerror(-ret));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void kvm_apic_external_nmi(APICCommonState *s)
|
|
{
|
|
run_on_cpu(s->cpu_env, do_inject_external_nmi, s);
|
|
}
|
|
|
|
static void kvm_apic_init(APICCommonState *s)
|
|
{
|
|
memory_region_init_reservation(&s->io_memory, "kvm-apic-msi",
|
|
MSI_SPACE_SIZE);
|
|
}
|
|
|
|
static void kvm_apic_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
APICCommonClass *k = APIC_COMMON_CLASS(klass);
|
|
|
|
k->init = kvm_apic_init;
|
|
k->set_base = kvm_apic_set_base;
|
|
k->set_tpr = kvm_apic_set_tpr;
|
|
k->get_tpr = kvm_apic_get_tpr;
|
|
k->enable_tpr_reporting = kvm_apic_enable_tpr_reporting;
|
|
k->vapic_base_update = kvm_apic_vapic_base_update;
|
|
k->external_nmi = kvm_apic_external_nmi;
|
|
}
|
|
|
|
static TypeInfo kvm_apic_info = {
|
|
.name = "kvm-apic",
|
|
.parent = TYPE_APIC_COMMON,
|
|
.instance_size = sizeof(APICCommonState),
|
|
.class_init = kvm_apic_class_init,
|
|
};
|
|
|
|
static void kvm_apic_register_types(void)
|
|
{
|
|
type_register_static(&kvm_apic_info);
|
|
}
|
|
|
|
type_init(kvm_apic_register_types)
|