// SPDX-License-Identifier: GPL-2.0-only #include #include #include #include #include #include #include #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 { 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(); /* 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 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); /* 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); }