#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
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 {
uint32_t 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 uint8_t tpr_guest_tpr_get(void)
{
uint32_t taskpri;
if (is_x2apic)
taskpri = x2apic_read_reg(APIC_TASKPRI);
else
taskpri = xapic_read_reg(APIC_TASKPRI);
return GET_APIC_PRI(taskpri);
}
static uint8_t tpr_guest_ppr_get(void)
{
uint32_t procpri;
if (is_x2apic)
procpri = x2apic_read_reg(APIC_PROCPRI);
else
procpri = xapic_read_reg(APIC_PROCPRI);
return GET_APIC_PRI(procpri);
}
static uint8_t tpr_guest_cr8_get(void)
{
uint64_t 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)
{
uint8_t 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();
udelay(1000);
GUEST_ASSERT_EQ(tpr_guest_irq_sync_val_get(), 0);
sti();
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();
udelay(1000);
GUEST_ASSERT_EQ(tpr_guest_irq_sync_val_get(), 1);
GUEST_SYNC(false);
tpr_guest_check_tpr_ppr_cr8_equal();
while (tpr_guest_irq_sync_val_get() == 1)
cpu_relax();
GUEST_ASSERT_EQ(tpr_guest_irq_sync_val_get(), 2);
GUEST_DONE();
}
static uint8_t 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, uint8_t val)
{
u32 *taskpri = (u32 *)&xapic->regs[APIC_TASKPRI];
*taskpri = SET_APIC_PRI(*taskpri, val);
}
static uint8_t 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;
uint8_t 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);
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[])
{
test_tpr(false);
test_tpr(true);
}