[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20250923050942.206116-26-Neeraj.Upadhyay@amd.com>
Date: Tue, 23 Sep 2025 10:39:32 +0530
From: Neeraj Upadhyay <Neeraj.Upadhyay@....com>
To: <kvm@...r.kernel.org>, <seanjc@...gle.com>, <pbonzini@...hat.com>
CC: <linux-kernel@...r.kernel.org>, <Thomas.Lendacky@....com>,
<nikunj@....com>, <Santosh.Shukla@....com>, <Vasant.Hegde@....com>,
<Suravee.Suthikulpanit@....com>, <bp@...en8.de>, <David.Kaplan@....com>,
<huibo.wang@....com>, <naveen.rao@....com>, <pgonda@...gle.com>,
<linux-kselftest@...r.kernel.org>, <shuah@...nel.org>, <tiala@...rosoft.com>
Subject: [RFC PATCH v2 25/35] KVM: selftests: Add #VC handler for unaccelerated Secure AVIC MSRs
When Secure AVIC (SAVIC) is enabled for an SEV-SNP guest, most APIC
register accesses are accelerated via a guest-visible backing page.
However, not all APIC registers are handled this way. Accesses to
certain "unaccelerated" registers via RDMSR or WRMSR are designed
to trigger a #VC (Virtualization Exception) with the new exit code
SVM_EXIT_AVIC_UNACCELERATED_ACCESS.
Without a handler for this exit code, any guest attempt to access
these MSRs would result in an unhandled fault, crashing the guest.
Implement the necessary #VC handler to emulate these accesses.
The new handler (handle_savic_unaccel_access) emulates the MSR operation.
For MSR reads/writes, it applies different strategies based on the
register:
- Read/Write APIC timer, APIC Spurious Interrupt Vector and LVT registers'
from the hypervisor using the existing paravirtual MSR interface as LVT
and APIC timers are emulated by the hypervisor.
- Propagate EOI write to the hypervisor. This is required for
acknowledging emulated IOAPIC interrupts.
- Keep APIC_ID in-sync between the guest and the hypervisor.
- For rest of the valid APIC register writes, write the value only in the
APIC backing page of the guest vCPU.
This emulation provides the necessary glue to allow guest code to run
seamlessly when SAVIC is active, enabling comprehensive testing of
APIC functionality in this mode.
Signed-off-by: Neeraj Upadhyay <Neeraj.Upadhyay@....com>
---
.../testing/selftests/kvm/include/x86/apic.h | 1 +
.../testing/selftests/kvm/include/x86/savic.h | 1 +
tools/testing/selftests/kvm/lib/x86/savic.c | 145 ++++++++++++++++++
3 files changed, 147 insertions(+)
diff --git a/tools/testing/selftests/kvm/include/x86/apic.h b/tools/testing/selftests/kvm/include/x86/apic.h
index 6ba5d0545bf8..aa3a5d54c404 100644
--- a/tools/testing/selftests/kvm/include/x86/apic.h
+++ b/tools/testing/selftests/kvm/include/x86/apic.h
@@ -33,6 +33,7 @@
#define APIC_SPIV 0xF0
#define APIC_SPIV_FOCUS_DISABLED (1 << 9)
#define APIC_SPIV_APIC_ENABLED (1 << 8)
+#define APIC_TMR 0x180
#define APIC_IRR 0x200
#define APIC_ICR 0x300
#define APIC_LVTCMCI 0x2f0
diff --git a/tools/testing/selftests/kvm/include/x86/savic.h b/tools/testing/selftests/kvm/include/x86/savic.h
index 1ab92dad00c1..238d7450ab6e 100644
--- a/tools/testing/selftests/kvm/include/x86/savic.h
+++ b/tools/testing/selftests/kvm/include/x86/savic.h
@@ -16,4 +16,5 @@ void savic_hv_write_reg(uint32_t reg, uint64_t val);
uint64_t savic_hv_read_reg(uint32_t reg);
void savic_enable(void);
int savic_nr_pages_required(uint64_t page_size);
+void savic_vc_handler(struct ex_regs *regs);
#endif
diff --git a/tools/testing/selftests/kvm/lib/x86/savic.c b/tools/testing/selftests/kvm/lib/x86/savic.c
index 72ad43d4797e..9b1ea5d15338 100644
--- a/tools/testing/selftests/kvm/lib/x86/savic.c
+++ b/tools/testing/selftests/kvm/lib/x86/savic.c
@@ -41,6 +41,8 @@ enum lapic_lvt_entry {
#define MSR_AMD64_SECURE_AVIC_EN_BIT 0
#define MSR_AMD64_SECURE_AVIC_ALLOWED_NMI_BIT 1
+#define SVM_EXIT_AVIC_UNACCELERATED_ACCESS 0x402
+
/*
* Initial pool of guest apic backing page.
*/
@@ -203,3 +205,146 @@ void savic_enable(void)
"SAVIC Control msr unexpected val : 0x%lx, expected : 0x%lx",
savic_ctrl_msr_val, exp_msr_val);
}
+
+static bool savic_reg_access_is_trapped(uint32_t reg)
+{
+ switch (reg) {
+ case APIC_ID:
+ case APIC_TASKPRI:
+ case APIC_EOI:
+ case APIC_LDR:
+ case APIC_SPIV:
+ case APIC_ICR:
+ case APIC_ICR2:
+ case APIC_LVTT:
+ case APIC_LVTTHMR:
+ case APIC_LVTPC:
+ case APIC_LVT0:
+ case APIC_LVT1:
+ case APIC_LVTERR:
+ case APIC_TMICT:
+ case APIC_TDCR:
+ return true;
+ case APIC_LVR:
+ case APIC_PROCPRI:
+ case APIC_TMR:
+ case APIC_IRR ... APIC_IRR + 0x70:
+ case APIC_TMCCT:
+ return false;
+ default:
+ return false;
+ }
+}
+
+static void savic_unaccel_apic_msrs_read(struct guest_apic_page *apic_page,
+ uint32_t reg, uint64_t *val)
+{
+ switch (reg) {
+ case APIC_TMICT:
+ case APIC_TMCCT:
+ case APIC_TDCR:
+ case APIC_LVTT:
+ case APIC_LVTTHMR:
+ case APIC_LVTPC:
+ case APIC_LVT0:
+ case APIC_LVT1:
+ case APIC_LVTERR:
+ *val = savic_hv_read_reg(reg);
+ break;
+ default:
+ __GUEST_ASSERT(0, "Unexpected unaccelerated read trap for reg: %x\n", reg);
+ }
+}
+
+static void savic_unaccel_apic_msrs_write(struct guest_apic_page *apic_page,
+ uint32_t reg, uint64_t val)
+{
+ switch (reg) {
+ /*
+ * APIC_ID value is in sync between guest apic backing page and
+ * hv.
+ * LVT* registers and APIC timer register updates are propagated to hv.
+ */
+ case APIC_ID:
+ case APIC_LVTT:
+ case APIC_LVTTHMR:
+ case APIC_LVTPC:
+ case APIC_LVT0:
+ case APIC_LVT1:
+ case APIC_LVTERR:
+ case APIC_SPIV:
+ case APIC_TMICT:
+ case APIC_TMCCT:
+ case APIC_TDCR:
+ savic_write_reg(apic_page, reg, val);
+ savic_hv_write_reg(reg, val);
+ break;
+ /*
+ * LDR is derived in hv from APIC_ID.
+ * TPR, IRR information is not propagated to hv.
+ */
+ case APIC_LDR:
+ case APIC_TASKPRI:
+ case APIC_IRR:
+ savic_write_reg(apic_page, reg, val);
+ break;
+ /*
+ * EOI write need to be propagated to hv for level-triggered
+ * interrupts.
+ */
+ case APIC_EOI:
+ savic_hv_write_reg(reg, val);
+ break;
+ default:
+ __GUEST_ASSERT(0, "Write not permitted for reg: %x\n", reg);
+ }
+}
+
+static void handle_savic_unaccel_access(struct ex_regs *regs)
+{
+ bool write;;
+ uint64_t msr = regs->rcx;
+ uint32_t reg = (msr - APIC_BASE_MSR) << 4;
+ struct guest_apic_page *apic_page;
+ uint64_t low = regs->rax;
+ uint64_t high = regs->rdx;
+ uint64_t val = 0;
+
+ apic_page = &apic_page_pool->guest_apic_page[x2apic_read_reg(APIC_ID)];
+
+ switch (msr) {
+ case APIC_BASE_MSR ... APIC_BASE_MSR + 0xff:
+ if (savic_reg_access_is_trapped(reg))
+ write = *((uint8_t *)regs->rip - 1) == 0x30;
+ else
+ write = *((uint8_t *)regs->rip + 1) == 0x30;
+ if (write) {
+ savic_unaccel_apic_msrs_write(apic_page, reg,
+ high << 32 | low);
+ } else {
+ savic_unaccel_apic_msrs_read(apic_page, reg, &val);
+ regs->rax = val & ((1ULL << 32) - 1);
+ regs->rdx = val >> 32;
+ }
+ if (!savic_reg_access_is_trapped(reg))
+ regs->rip += 2;
+ break;
+ default:
+ __GUEST_ASSERT(0, "Unknown unaccelerated msr: %lx\n", msr);
+ break;
+ }
+}
+
+void savic_vc_handler(struct ex_regs *regs)
+{
+ uint64_t exit_code = regs->error_code;
+
+ switch (exit_code) {
+ case SVM_EXIT_AVIC_UNACCELERATED_ACCESS:
+ handle_savic_unaccel_access(regs);
+ break;
+ default:
+ sev_es_vc_handler(regs);
+ break;
+ }
+}
--
2.34.1
Powered by blists - more mailing lists