]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
x86/apic: Populate .read()/.write() callbacks of Secure AVIC driver
authorNeeraj Upadhyay <Neeraj.Upadhyay@amd.com>
Thu, 28 Aug 2025 11:02:40 +0000 (16:32 +0530)
committerBorislav Petkov (AMD) <bp@alien8.de>
Sun, 31 Aug 2025 20:07:35 +0000 (22:07 +0200)
Add read() and write() APIC callback functions to read and write the x2APIC
registers directly from the guest APIC backing page of a vCPU.

The x2APIC registers are mapped at an offset within the guest APIC backing
page which is the same as their x2APIC MMIO offset. Secure AVIC adds new
registers such as ALLOWED_IRRs (which are at 4-byte offset within the IRR
register offset range) and NMI_REQ to the APIC register space.

When Secure AVIC is enabled, accessing the guest's APIC registers through
RD/WRMSR results in a #VC exception (for non-accelerated register accesses)
with error code VMEXIT_AVIC_NOACCEL.

The #VC exception handler can read/write the x2APIC register in the guest APIC
backing page to complete the RDMSR/WRMSR. Since doing this would increase the
latency of accessing the x2APIC registers, instead of doing RDMSR/WRMSR based
register accesses and handling reads/writes in the #VC exception, directly
read/write the APIC registers from/to the guest APIC backing page of the vCPU
in read() and write() callbacks of the Secure AVIC APIC driver.

  [ bp: Massage commit message. ]

Co-developed-by: Kishon Vijay Abraham I <kvijayab@amd.com>
Signed-off-by: Kishon Vijay Abraham I <kvijayab@amd.com>
Signed-off-by: Neeraj Upadhyay <Neeraj.Upadhyay@amd.com>
Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de>
Reviewed-by: Tianyu Lan <tiala@microsoft.com>
Link: https://lore.kernel.org/20250828110255.208779-1-Neeraj.Upadhyay@amd.com
arch/x86/include/asm/apicdef.h
arch/x86/kernel/apic/x2apic_savic.c

index 094106b6a5384f65355f5bac0f2a7dc0769d35a1..be39a543fbe5d06334da4904375f0a3a1958d789 100644 (file)
 #define                APIC_TDR_DIV_128        0xA
 #define        APIC_EFEAT      0x400
 #define        APIC_ECTRL      0x410
+#define APIC_SEOI      0x420
+#define APIC_IER       0x480
 #define APIC_EILVTn(n) (0x500 + 0x10 * n)
 #define                APIC_EILVT_NR_AMD_K8    1       /* # of extended interrupts */
 #define                APIC_EILVT_NR_AMD_10H   4
index 948d89497baafdd026b9171c986ec894c3aefccf..5479605429c1a8fe06a7cf6e769c01bc4ade96c9 100644 (file)
@@ -9,6 +9,7 @@
 
 #include <linux/cc_platform.h>
 #include <linux/percpu-defs.h>
+#include <linux/align.h>
 
 #include <asm/apic.h>
 #include <asm/sev.h>
@@ -26,6 +27,123 @@ static int savic_acpi_madt_oem_check(char *oem_id, char *oem_table_id)
        return x2apic_enabled() && cc_platform_has(CC_ATTR_SNP_SECURE_AVIC);
 }
 
+#define SAVIC_ALLOWED_IRR      0x204
+
+/*
+ * When Secure AVIC is enabled, RDMSR/WRMSR of the APIC registers
+ * result in #VC exception (for non-accelerated register accesses)
+ * with VMEXIT_AVIC_NOACCEL error code. The #VC exception handler
+ * can read/write the x2APIC register in the guest APIC backing page.
+ *
+ * Since doing this would increase the latency of accessing x2APIC
+ * registers, instead of doing RDMSR/WRMSR based accesses and
+ * handling the APIC register reads/writes in the #VC exception handler,
+ * the read() and write() callbacks directly read/write the APIC register
+ * from/to the vCPU's APIC backing page.
+ */
+static u32 savic_read(u32 reg)
+{
+       void *ap = this_cpu_ptr(savic_page);
+
+       switch (reg) {
+       case APIC_LVTT:
+       case APIC_TMICT:
+       case APIC_TMCCT:
+       case APIC_TDCR:
+       case APIC_ID:
+       case APIC_LVR:
+       case APIC_TASKPRI:
+       case APIC_ARBPRI:
+       case APIC_PROCPRI:
+       case APIC_LDR:
+       case APIC_SPIV:
+       case APIC_ESR:
+       case APIC_LVTTHMR:
+       case APIC_LVTPC:
+       case APIC_LVT0:
+       case APIC_LVT1:
+       case APIC_LVTERR:
+       case APIC_EFEAT:
+       case APIC_ECTRL:
+       case APIC_SEOI:
+       case APIC_IER:
+       case APIC_EILVTn(0) ... APIC_EILVTn(3):
+               return apic_get_reg(ap, reg);
+       case APIC_ICR:
+               return (u32)apic_get_reg64(ap, reg);
+       case APIC_ISR ... APIC_ISR + 0x70:
+       case APIC_TMR ... APIC_TMR + 0x70:
+               if (WARN_ONCE(!IS_ALIGNED(reg, 16),
+                             "APIC register read offset 0x%x not aligned at 16 bytes", reg))
+                       return 0;
+               return apic_get_reg(ap, reg);
+       /* IRR and ALLOWED_IRR offset range */
+       case APIC_IRR ... APIC_IRR + 0x74:
+               /*
+                * Valid APIC_IRR/SAVIC_ALLOWED_IRR registers are at 16 bytes strides from
+                * their respective base offset. APIC_IRRs are in the range
+                *
+                * (0x200, 0x210,  ..., 0x270)
+                *
+                * while the SAVIC_ALLOWED_IRR range starts 4 bytes later, in the range
+                *
+                * (0x204, 0x214, ..., 0x274).
+                *
+                * Filter out everything else.
+                */
+               if (WARN_ONCE(!(IS_ALIGNED(reg, 16) ||
+                               IS_ALIGNED(reg - 4, 16)),
+                             "Misaligned APIC_IRR/ALLOWED_IRR APIC register read offset 0x%x", reg))
+                       return 0;
+               return apic_get_reg(ap, reg);
+       default:
+               pr_err("Error reading unknown Secure AVIC reg offset 0x%x\n", reg);
+               return 0;
+       }
+}
+
+#define SAVIC_NMI_REQ          0x278
+
+static void savic_write(u32 reg, u32 data)
+{
+       void *ap = this_cpu_ptr(savic_page);
+
+       switch (reg) {
+       case APIC_LVTT:
+       case APIC_LVT0:
+       case APIC_LVT1:
+       case APIC_TMICT:
+       case APIC_TDCR:
+       case APIC_SELF_IPI:
+       case APIC_TASKPRI:
+       case APIC_EOI:
+       case APIC_SPIV:
+       case SAVIC_NMI_REQ:
+       case APIC_ESR:
+       case APIC_LVTTHMR:
+       case APIC_LVTPC:
+       case APIC_LVTERR:
+       case APIC_ECTRL:
+       case APIC_SEOI:
+       case APIC_IER:
+       case APIC_EILVTn(0) ... APIC_EILVTn(3):
+               apic_set_reg(ap, reg, data);
+               break;
+       case APIC_ICR:
+               apic_set_reg64(ap, reg, (u64)data);
+               break;
+       /* ALLOWED_IRR offsets are writable */
+       case SAVIC_ALLOWED_IRR ... SAVIC_ALLOWED_IRR + 0x70:
+               if (IS_ALIGNED(reg - 4, 16)) {
+                       apic_set_reg(ap, reg, data);
+                       break;
+               }
+               fallthrough;
+       default:
+               pr_err("Error writing unknown Secure AVIC reg offset 0x%x\n", reg);
+       }
+}
+
 static void savic_setup(void)
 {
        void *ap = this_cpu_ptr(savic_page);
@@ -88,8 +206,8 @@ static struct apic apic_x2apic_savic __ro_after_init = {
 
        .nmi_to_offline_cpu             = true,
 
-       .read                           = native_apic_msr_read,
-       .write                          = native_apic_msr_write,
+       .read                           = savic_read,
+       .write                          = savic_write,
        .eoi                            = native_apic_msr_eoi,
        .icr_read                       = native_x2apic_icr_read,
        .icr_write                      = native_x2apic_icr_write,