Commit e5ad936b authored by Jan Kiszka's avatar Jan Kiszka Committed by Avi Kivity
Browse files

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: default avatarJan Kiszka <jan.kiszka@siemens.com>
Signed-off-by: default avatarAvi Kivity <avi@redhat.com>
parent 2a2af967
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -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
+115 −11
Original line number Diff line number Diff line
@@ -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;
    /* 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;
}

+62 −2
Original line number Diff line number Diff line
@@ -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(),
};

+27 −0
Original line number Diff line number Diff line
@@ -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 */
+32 −0
Original line number Diff line number Diff line
@@ -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;
}

Loading