277 lines
5.7 KiB
C
277 lines
5.7 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
#include <fcntl.h>
|
|
#include <stdatomic.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/ioctl.h>
|
|
#include <unistd.h>
|
|
|
|
#include "apic.h"
|
|
#include "kvm_util.h"
|
|
#include "processor.h"
|
|
#include "test_util.h"
|
|
|
|
static bool is_x2apic;
|
|
|
|
#define IRQ_VECTOR 0x20
|
|
|
|
/* See also the comment at similar assertion in memslot_perf_test.c */
|
|
static_assert(ATOMIC_INT_LOCK_FREE == 2, "atomic int is not lockless");
|
|
|
|
static atomic_uint tpr_guest_irq_sync_val;
|
|
|
|
static void tpr_guest_irq_sync_flag_reset(void)
|
|
{
|
|
atomic_store_explicit(&tpr_guest_irq_sync_val, 0,
|
|
memory_order_release);
|
|
}
|
|
|
|
static unsigned int tpr_guest_irq_sync_val_get(void)
|
|
{
|
|
return atomic_load_explicit(&tpr_guest_irq_sync_val,
|
|
memory_order_acquire);
|
|
}
|
|
|
|
static void tpr_guest_irq_sync_val_inc(void)
|
|
{
|
|
atomic_fetch_add_explicit(&tpr_guest_irq_sync_val, 1,
|
|
memory_order_acq_rel);
|
|
}
|
|
|
|
static void tpr_guest_irq_handler_xapic(struct ex_regs *regs)
|
|
{
|
|
tpr_guest_irq_sync_val_inc();
|
|
|
|
xapic_write_reg(APIC_EOI, 0);
|
|
}
|
|
|
|
static void tpr_guest_irq_handler_x2apic(struct ex_regs *regs)
|
|
{
|
|
tpr_guest_irq_sync_val_inc();
|
|
|
|
x2apic_write_reg(APIC_EOI, 0);
|
|
}
|
|
|
|
static void tpr_guest_irq_queue(void)
|
|
{
|
|
if (is_x2apic) {
|
|
x2apic_write_reg(APIC_SELF_IPI, IRQ_VECTOR);
|
|
} else {
|
|
u32 icr, icr2;
|
|
|
|
icr = APIC_DEST_SELF | APIC_DEST_PHYSICAL | APIC_DM_FIXED |
|
|
IRQ_VECTOR;
|
|
icr2 = 0;
|
|
|
|
xapic_write_reg(APIC_ICR2, icr2);
|
|
xapic_write_reg(APIC_ICR, icr);
|
|
}
|
|
}
|
|
|
|
static u8 tpr_guest_tpr_get(void)
|
|
{
|
|
u32 taskpri;
|
|
|
|
if (is_x2apic)
|
|
taskpri = x2apic_read_reg(APIC_TASKPRI);
|
|
else
|
|
taskpri = xapic_read_reg(APIC_TASKPRI);
|
|
|
|
return GET_APIC_PRI(taskpri);
|
|
}
|
|
|
|
static u8 tpr_guest_ppr_get(void)
|
|
{
|
|
u32 procpri;
|
|
|
|
if (is_x2apic)
|
|
procpri = x2apic_read_reg(APIC_PROCPRI);
|
|
else
|
|
procpri = xapic_read_reg(APIC_PROCPRI);
|
|
|
|
return GET_APIC_PRI(procpri);
|
|
}
|
|
|
|
static u8 tpr_guest_cr8_get(void)
|
|
{
|
|
u64 cr8;
|
|
|
|
asm volatile ("mov %%cr8, %[cr8]\n\t" : [cr8] "=r"(cr8));
|
|
|
|
return cr8 & GENMASK(3, 0);
|
|
}
|
|
|
|
static void tpr_guest_check_tpr_ppr_cr8_equal(void)
|
|
{
|
|
u8 tpr;
|
|
|
|
tpr = tpr_guest_tpr_get();
|
|
|
|
GUEST_ASSERT_EQ(tpr_guest_ppr_get(), tpr);
|
|
GUEST_ASSERT_EQ(tpr_guest_cr8_get(), tpr);
|
|
}
|
|
|
|
static void tpr_guest_code(void)
|
|
{
|
|
cli();
|
|
|
|
if (is_x2apic)
|
|
x2apic_enable();
|
|
else
|
|
xapic_enable();
|
|
|
|
GUEST_ASSERT_EQ(tpr_guest_tpr_get(), 0);
|
|
tpr_guest_check_tpr_ppr_cr8_equal();
|
|
|
|
tpr_guest_irq_queue();
|
|
|
|
/* TPR = 0 but IRQ masked by IF=0, should not fire */
|
|
udelay(1000);
|
|
GUEST_ASSERT_EQ(tpr_guest_irq_sync_val_get(), 0);
|
|
|
|
sti();
|
|
|
|
/* IF=1 now, IRQ should fire */
|
|
while (tpr_guest_irq_sync_val_get() == 0)
|
|
cpu_relax();
|
|
GUEST_ASSERT_EQ(tpr_guest_irq_sync_val_get(), 1);
|
|
|
|
GUEST_SYNC(true);
|
|
tpr_guest_check_tpr_ppr_cr8_equal();
|
|
|
|
tpr_guest_irq_queue();
|
|
|
|
/* IRQ masked by barely high enough TPR now, should not fire */
|
|
udelay(1000);
|
|
GUEST_ASSERT_EQ(tpr_guest_irq_sync_val_get(), 1);
|
|
|
|
GUEST_SYNC(false);
|
|
tpr_guest_check_tpr_ppr_cr8_equal();
|
|
|
|
/* TPR barely low enough now to unmask IRQ, should fire */
|
|
while (tpr_guest_irq_sync_val_get() == 1)
|
|
cpu_relax();
|
|
GUEST_ASSERT_EQ(tpr_guest_irq_sync_val_get(), 2);
|
|
|
|
GUEST_DONE();
|
|
}
|
|
|
|
static u8 lapic_tpr_get(struct kvm_lapic_state *xapic)
|
|
{
|
|
return GET_APIC_PRI(*((u32 *)&xapic->regs[APIC_TASKPRI]));
|
|
}
|
|
|
|
static void lapic_tpr_set(struct kvm_lapic_state *xapic, u8 val)
|
|
{
|
|
u32 *taskpri = (u32 *)&xapic->regs[APIC_TASKPRI];
|
|
|
|
*taskpri = SET_APIC_PRI(*taskpri, val);
|
|
}
|
|
|
|
static u8 sregs_tpr(struct kvm_sregs *sregs)
|
|
{
|
|
return sregs->cr8 & GENMASK(3, 0);
|
|
}
|
|
|
|
static void test_tpr_check_tpr_zero(struct kvm_vcpu *vcpu)
|
|
{
|
|
struct kvm_lapic_state xapic;
|
|
|
|
vcpu_ioctl(vcpu, KVM_GET_LAPIC, &xapic);
|
|
|
|
TEST_ASSERT_EQ(lapic_tpr_get(&xapic), 0);
|
|
}
|
|
|
|
static void test_tpr_check_tpr_cr8_equal(struct kvm_vcpu *vcpu)
|
|
{
|
|
struct kvm_sregs sregs;
|
|
struct kvm_lapic_state xapic;
|
|
|
|
vcpu_sregs_get(vcpu, &sregs);
|
|
vcpu_ioctl(vcpu, KVM_GET_LAPIC, &xapic);
|
|
|
|
TEST_ASSERT_EQ(sregs_tpr(&sregs), lapic_tpr_get(&xapic));
|
|
}
|
|
|
|
static void test_tpr_set_tpr_for_irq(struct kvm_vcpu *vcpu, bool mask)
|
|
{
|
|
struct kvm_lapic_state xapic;
|
|
u8 tpr;
|
|
|
|
static_assert(IRQ_VECTOR >= 16, "invalid IRQ vector number");
|
|
tpr = IRQ_VECTOR / 16;
|
|
if (!mask)
|
|
tpr--;
|
|
|
|
vcpu_ioctl(vcpu, KVM_GET_LAPIC, &xapic);
|
|
lapic_tpr_set(&xapic, tpr);
|
|
vcpu_ioctl(vcpu, KVM_SET_LAPIC, &xapic);
|
|
}
|
|
|
|
static void test_tpr(bool __is_x2apic)
|
|
{
|
|
struct kvm_vcpu *vcpu;
|
|
struct kvm_vm *vm;
|
|
bool done = false;
|
|
|
|
is_x2apic = __is_x2apic;
|
|
|
|
vm = vm_create_with_one_vcpu(&vcpu, tpr_guest_code);
|
|
if (is_x2apic) {
|
|
vm_install_exception_handler(vm, IRQ_VECTOR,
|
|
tpr_guest_irq_handler_x2apic);
|
|
} else {
|
|
vm_install_exception_handler(vm, IRQ_VECTOR,
|
|
tpr_guest_irq_handler_xapic);
|
|
vcpu_clear_cpuid_feature(vcpu, X86_FEATURE_X2APIC);
|
|
virt_pg_map(vm, APIC_DEFAULT_GPA, APIC_DEFAULT_GPA);
|
|
}
|
|
|
|
sync_global_to_guest(vcpu->vm, is_x2apic);
|
|
|
|
/* According to the SDM/APM the TPR value at reset is 0 */
|
|
test_tpr_check_tpr_zero(vcpu);
|
|
test_tpr_check_tpr_cr8_equal(vcpu);
|
|
|
|
tpr_guest_irq_sync_flag_reset();
|
|
sync_global_to_guest(vcpu->vm, tpr_guest_irq_sync_val);
|
|
|
|
while (!done) {
|
|
struct ucall uc;
|
|
|
|
alarm(2);
|
|
vcpu_run(vcpu);
|
|
alarm(0);
|
|
|
|
switch (get_ucall(vcpu, &uc)) {
|
|
case UCALL_ABORT:
|
|
REPORT_GUEST_ASSERT(uc);
|
|
break;
|
|
case UCALL_DONE:
|
|
test_tpr_check_tpr_cr8_equal(vcpu);
|
|
done = true;
|
|
break;
|
|
case UCALL_SYNC:
|
|
test_tpr_check_tpr_cr8_equal(vcpu);
|
|
test_tpr_set_tpr_for_irq(vcpu, uc.args[1]);
|
|
break;
|
|
default:
|
|
TEST_FAIL("Unknown ucall result 0x%lx", uc.cmd);
|
|
break;
|
|
}
|
|
}
|
|
kvm_vm_free(vm);
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
/*
|
|
* Use separate VMs for the xAPIC and x2APIC tests so that x2APIC can
|
|
* be fully hidden from the guest. KVM disallows changing CPUID after
|
|
* KVM_RUN and AVIC is disabled if _any_ vCPU is allowed to use x2APIC.
|
|
*/
|
|
test_tpr(false);
|
|
test_tpr(true);
|
|
}
|