提交 e5ad936b 编写于 作者: J Jan Kiszka 提交者: Avi Kivity

kvmvapic: Introduce TPR access optimization for Windows guests

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: NJan Kiszka <jan.kiszka@siemens.com>
Signed-off-by: NAvi Kivity <avi@redhat.com>
上级 2a2af967
......@@ -236,7 +236,8 @@ obj-y += device-hotplug.o
# Hardware support
obj-i386-y += mc146818rtc.o pc.o
obj-i386-y += sga.o apic_common.o apic.o ioapic_common.o ioapic.o piix_pci.o
obj-i386-y += apic_common.o apic.o kvmvapic.o
obj-i386-y += sga.o ioapic_common.o ioapic.o piix_pci.o
obj-i386-y += vmport.o
obj-i386-y += pci-hotplug.o smbios.o wdt_ib700.o
obj-i386-y += debugcon.o multiboot.o
......
......@@ -35,6 +35,10 @@
#define MSI_ADDR_DEST_ID_SHIFT 12
#define MSI_ADDR_DEST_ID_MASK 0x00ffff0
#define SYNC_FROM_VAPIC 0x1
#define SYNC_TO_VAPIC 0x2
#define SYNC_ISR_IRR_TO_VAPIC 0x4
static APICCommonState *local_apics[MAX_APICS + 1];
static void apic_set_irq(APICCommonState *s, int vector_num, int trigger_mode);
......@@ -78,6 +82,70 @@ static inline int get_bit(uint32_t *tab, int index)
return !!(tab[i] & mask);
}
/* return -1 if no bit is set */
static int get_highest_priority_int(uint32_t *tab)
{
int i;
for (i = 7; i >= 0; i--) {
if (tab[i] != 0) {
return i * 32 + fls_bit(tab[i]);
}
}
return -1;
}
static void apic_sync_vapic(APICCommonState *s, int sync_type)
{
VAPICState vapic_state;
size_t length;
off_t start;
int vector;
if (!s->vapic_paddr) {
return;
}
if (sync_type & SYNC_FROM_VAPIC) {
cpu_physical_memory_rw(s->vapic_paddr, (void *)&vapic_state,
sizeof(vapic_state), 0);
s->tpr = vapic_state.tpr;
}
if (sync_type & (SYNC_TO_VAPIC | SYNC_ISR_IRR_TO_VAPIC)) {
start = offsetof(VAPICState, isr);
length = offsetof(VAPICState, enabled) - offsetof(VAPICState, isr);
if (sync_type & SYNC_TO_VAPIC) {
assert(qemu_cpu_is_self(s->cpu_env));
vapic_state.tpr = s->tpr;
vapic_state.enabled = 1;
start = 0;
length = sizeof(VAPICState);
}
vector = get_highest_priority_int(s->isr);
if (vector < 0) {
vector = 0;
}
vapic_state.isr = vector & 0xf0;
vapic_state.zero = 0;
vector = get_highest_priority_int(s->irr);
if (vector < 0) {
vector = 0;
}
vapic_state.irr = vector & 0xff;
cpu_physical_memory_write_rom(s->vapic_paddr + start,
((void *)&vapic_state) + start, length);
}
}
static void apic_vapic_base_update(APICCommonState *s)
{
apic_sync_vapic(s, SYNC_TO_VAPIC);
}
static void apic_local_deliver(APICCommonState *s, int vector)
{
uint32_t lvt = s->lvt[vector];
......@@ -239,20 +307,17 @@ static void apic_set_base(APICCommonState *s, uint64_t val)
static void apic_set_tpr(APICCommonState *s, uint8_t val)
{
s->tpr = (val & 0x0f) << 4;
apic_update_irq(s);
/* Updates from cr8 are ignored while the VAPIC is active */
if (!s->vapic_paddr) {
s->tpr = val << 4;
apic_update_irq(s);
}
}
/* return -1 if no bit is set */
static int get_highest_priority_int(uint32_t *tab)
static uint8_t apic_get_tpr(APICCommonState *s)
{
int i;
for(i = 7; i >= 0; i--) {
if (tab[i] != 0) {
return i * 32 + fls_bit(tab[i]);
}
}
return -1;
apic_sync_vapic(s, SYNC_FROM_VAPIC);
return s->tpr >> 4;
}
static int apic_get_ppr(APICCommonState *s)
......@@ -312,6 +377,14 @@ static void apic_update_irq(APICCommonState *s)
}
}
void apic_poll_irq(DeviceState *d)
{
APICCommonState *s = APIC_COMMON(d);
apic_sync_vapic(s, SYNC_FROM_VAPIC);
apic_update_irq(s);
}
static void apic_set_irq(APICCommonState *s, int vector_num, int trigger_mode)
{
apic_report_irq_delivered(!get_bit(s->irr, vector_num));
......@@ -321,6 +394,16 @@ static void apic_set_irq(APICCommonState *s, int vector_num, int trigger_mode)
set_bit(s->tmr, vector_num);
else
reset_bit(s->tmr, vector_num);
if (s->vapic_paddr) {
apic_sync_vapic(s, SYNC_ISR_IRR_TO_VAPIC);
/*
* The vcpu thread needs to see the new IRR before we pull its current
* TPR value. That way, if we miss a lowering of the TRP, the guest
* has the chance to notice the new IRR and poll for IRQs on its own.
*/
smp_wmb();
apic_sync_vapic(s, SYNC_FROM_VAPIC);
}
apic_update_irq(s);
}
......@@ -334,6 +417,7 @@ static void apic_eoi(APICCommonState *s)
if (!(s->spurious_vec & APIC_SV_DIRECTED_IO) && get_bit(s->tmr, isrv)) {
ioapic_eoi_broadcast(isrv);
}
apic_sync_vapic(s, SYNC_FROM_VAPIC | SYNC_TO_VAPIC);
apic_update_irq(s);
}
......@@ -471,15 +555,19 @@ int apic_get_interrupt(DeviceState *d)
if (!(s->spurious_vec & APIC_SV_ENABLE))
return -1;
apic_sync_vapic(s, SYNC_FROM_VAPIC);
intno = apic_irq_pending(s);
if (intno == 0) {
apic_sync_vapic(s, SYNC_TO_VAPIC);
return -1;
} else if (intno < 0) {
apic_sync_vapic(s, SYNC_TO_VAPIC);
return s->spurious_vec & 0xff;
}
reset_bit(s->irr, intno);
set_bit(s->isr, intno);
apic_sync_vapic(s, SYNC_TO_VAPIC);
apic_update_irq(s);
return intno;
}
......@@ -576,6 +664,10 @@ static uint32_t apic_mem_readl(void *opaque, target_phys_addr_t addr)
val = 0x11 | ((APIC_LVT_NB - 1) << 16); /* version 0x11 */
break;
case 0x08:
apic_sync_vapic(s, SYNC_FROM_VAPIC);
if (apic_report_tpr_access) {
cpu_report_tpr_access(s->cpu_env, TPR_ACCESS_READ);
}
val = s->tpr;
break;
case 0x09:
......@@ -675,7 +767,11 @@ static void apic_mem_writel(void *opaque, target_phys_addr_t addr, uint32_t val)
case 0x03:
break;
case 0x08:
if (apic_report_tpr_access) {
cpu_report_tpr_access(s->cpu_env, TPR_ACCESS_WRITE);
}
s->tpr = val;
apic_sync_vapic(s, SYNC_TO_VAPIC);
apic_update_irq(s);
break;
case 0x09:
......@@ -737,6 +833,11 @@ static void apic_mem_writel(void *opaque, target_phys_addr_t addr, uint32_t val)
}
}
static void apic_pre_save(APICCommonState *s)
{
apic_sync_vapic(s, SYNC_FROM_VAPIC);
}
static void apic_post_load(APICCommonState *s)
{
if (s->timer_expiry != -1) {
......@@ -770,7 +871,10 @@ static void apic_class_init(ObjectClass *klass, void *data)
k->init = apic_init;
k->set_base = apic_set_base;
k->set_tpr = apic_set_tpr;
k->get_tpr = apic_get_tpr;
k->vapic_base_update = apic_vapic_base_update;
k->external_nmi = apic_external_nmi;
k->pre_save = apic_pre_save;
k->post_load = apic_post_load;
}
......
......@@ -20,8 +20,10 @@
#include "apic.h"
#include "apic_internal.h"
#include "trace.h"
#include "kvm.h"
static int apic_irq_delivered;
bool apic_report_tpr_access;
void cpu_set_apic_base(DeviceState *d, uint64_t val)
{
......@@ -62,15 +64,46 @@ void cpu_set_apic_tpr(DeviceState *d, uint8_t val)
}
uint8_t cpu_get_apic_tpr(DeviceState *d)
{
APICCommonState *s;
APICCommonClass *info;
if (!d) {
return 0;
}
s = APIC_COMMON(d);
info = APIC_COMMON_GET_CLASS(s);
return info->get_tpr(s);
}
void apic_enable_tpr_access_reporting(DeviceState *d, bool enable)
{
APICCommonState *s = DO_UPCAST(APICCommonState, busdev.qdev, d);
APICCommonClass *info = APIC_COMMON_GET_CLASS(s);
apic_report_tpr_access = enable;
if (info->enable_tpr_reporting) {
info->enable_tpr_reporting(s, enable);
}
}
void apic_enable_vapic(DeviceState *d, target_phys_addr_t paddr)
{
APICCommonState *s = DO_UPCAST(APICCommonState, busdev.qdev, d);
APICCommonClass *info = APIC_COMMON_GET_CLASS(s);
return s ? s->tpr >> 4 : 0;
s->vapic_paddr = paddr;
info->vapic_base_update(s);
}
void apic_handle_tpr_access_report(DeviceState *d, target_ulong ip,
TPRAccess access)
{
APICCommonState *s = DO_UPCAST(APICCommonState, busdev.qdev, d);
vapic_report_tpr_access(s->vapic, s->cpu_env, ip, access);
}
void apic_report_irq_delivered(int delivered)
......@@ -171,12 +204,16 @@ void apic_init_reset(DeviceState *d)
static void apic_reset_common(DeviceState *d)
{
APICCommonState *s = DO_UPCAST(APICCommonState, busdev.qdev, d);
APICCommonClass *info = APIC_COMMON_GET_CLASS(s);
bool bsp;
bsp = cpu_is_bsp(s->cpu_env);
s->apicbase = 0xfee00000 |
(bsp ? MSR_IA32_APICBASE_BSP : 0) | MSR_IA32_APICBASE_ENABLE;
s->vapic_paddr = 0;
info->vapic_base_update(s);
apic_init_reset(d);
if (bsp) {
......@@ -239,6 +276,7 @@ static int apic_init_common(SysBusDevice *dev)
{
APICCommonState *s = APIC_COMMON(dev);
APICCommonClass *info;
static DeviceState *vapic;
static int apic_no;
if (apic_no >= MAX_APICS) {
......@@ -249,10 +287,29 @@ static int apic_init_common(SysBusDevice *dev)
info = APIC_COMMON_GET_CLASS(s);
info->init(s);
sysbus_init_mmio(&s->busdev, &s->io_memory);
sysbus_init_mmio(dev, &s->io_memory);
if (!vapic && s->vapic_control & VAPIC_ENABLE_MASK) {
vapic = sysbus_create_simple("kvmvapic", -1, NULL);
}
s->vapic = vapic;
if (apic_report_tpr_access && info->enable_tpr_reporting) {
info->enable_tpr_reporting(s, true);
}
return 0;
}
static void apic_dispatch_pre_save(void *opaque)
{
APICCommonState *s = APIC_COMMON(opaque);
APICCommonClass *info = APIC_COMMON_GET_CLASS(s);
if (info->pre_save) {
info->pre_save(s);
}
}
static int apic_dispatch_post_load(void *opaque, int version_id)
{
APICCommonState *s = APIC_COMMON(opaque);
......@@ -270,6 +327,7 @@ static const VMStateDescription vmstate_apic_common = {
.minimum_version_id = 3,
.minimum_version_id_old = 1,
.load_state_old = apic_load_old,
.pre_save = apic_dispatch_pre_save,
.post_load = apic_dispatch_post_load,
.fields = (VMStateField[]) {
VMSTATE_UINT32(apicbase, APICCommonState),
......@@ -299,6 +357,8 @@ static const VMStateDescription vmstate_apic_common = {
static Property apic_properties_common[] = {
DEFINE_PROP_UINT8("id", APICCommonState, id, -1),
DEFINE_PROP_PTR("cpu_env", APICCommonState, cpu_env),
DEFINE_PROP_BIT("vapic", APICCommonState, vapic_control, VAPIC_ENABLE_BIT,
true),
DEFINE_PROP_END_OF_LIST(),
};
......
......@@ -61,6 +61,9 @@
#define APIC_SV_DIRECTED_IO (1<<12)
#define APIC_SV_ENABLE (1<<8)
#define VAPIC_ENABLE_BIT 0
#define VAPIC_ENABLE_MASK (1 << VAPIC_ENABLE_BIT)
#define MAX_APICS 255
#define MSI_SPACE_SIZE 0x100000
......@@ -82,7 +85,11 @@ typedef struct APICCommonClass
void (*init)(APICCommonState *s);
void (*set_base)(APICCommonState *s, uint64_t val);
void (*set_tpr)(APICCommonState *s, uint8_t val);
uint8_t (*get_tpr)(APICCommonState *s);
void (*enable_tpr_reporting)(APICCommonState *s, bool enable);
void (*vapic_base_update)(APICCommonState *s);
void (*external_nmi)(APICCommonState *s);
void (*pre_save)(APICCommonState *s);
void (*post_load)(APICCommonState *s);
} APICCommonClass;
......@@ -114,9 +121,29 @@ struct APICCommonState {
int64_t timer_expiry;
int sipi_vector;
int wait_for_sipi;
uint32_t vapic_control;
DeviceState *vapic;
target_phys_addr_t vapic_paddr; /* note: persistence via kvmvapic */
};
typedef struct VAPICState {
uint8_t tpr;
uint8_t isr;
uint8_t zero;
uint8_t irr;
uint8_t enabled;
} QEMU_PACKED VAPICState;
extern bool apic_report_tpr_access;
void apic_report_irq_delivered(int delivered);
bool apic_next_timer(APICCommonState *s, int64_t current_time);
void apic_enable_tpr_access_reporting(DeviceState *d, bool enable);
void apic_enable_vapic(DeviceState *d, target_phys_addr_t paddr);
void apic_poll_irq(DeviceState *d);
void vapic_report_tpr_access(DeviceState *dev, void *cpu, target_ulong ip,
TPRAccess access);
#endif /* !QEMU_APIC_INTERNAL_H */
......@@ -92,6 +92,35 @@ 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;
......@@ -129,6 +158,9 @@ static void kvm_apic_class_init(ObjectClass *klass, void *data)
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;
}
......
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册