]> git.hungrycats.org Git - linux/commitdiff
KVM: x86: Re-split x2APIC ICR into ICR+ICR2 for AMD (x2AVIC)
authorSean Christopherson <seanjc@google.com>
Fri, 19 Jul 2024 23:51:00 +0000 (16:51 -0700)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 4 Oct 2024 14:33:43 +0000 (16:33 +0200)
[ Upstream commit 73b42dc69be8564d4951a14d00f827929fe5ef79 ]

Re-introduce the "split" x2APIC ICR storage that KVM used prior to Intel's
IPI virtualization support, but only for AMD.  While not stated anywhere
in the APM, despite stating the ICR is a single 64-bit register, AMD CPUs
store the 64-bit ICR as two separate 32-bit values in ICR and ICR2.  When
IPI virtualization (IPIv on Intel, all AVIC flavors on AMD) is enabled,
KVM needs to match CPU behavior as some ICR ICR writes will be handled by
the CPU, not by KVM.

Add a kvm_x86_ops knob to control the underlying format used by the CPU to
store the x2APIC ICR, and tune it to AMD vs. Intel regardless of whether
or not x2AVIC is enabled.  If KVM is handling all ICR writes, the storage
format for x2APIC mode doesn't matter, and having the behavior follow AMD
versus Intel will provide better test coverage and ease debugging.

Fixes: 4d1d7942e36a ("KVM: SVM: Introduce logic to (de)activate x2AVIC mode")
Cc: stable@vger.kernel.org
Cc: Maxim Levitsky <mlevitsk@redhat.com>
Cc: Suravee Suthikulpanit <suravee.suthikulpanit@amd.com>
Link: https://lore.kernel.org/r/20240719235107.3023592-4-seanjc@google.com
Signed-off-by: Sean Christopherson <seanjc@google.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
arch/x86/include/asm/kvm_host.h
arch/x86/kvm/lapic.c
arch/x86/kvm/svm/svm.c
arch/x86/kvm/vmx/main.c

index a571f89db697768a05c53164d4e59820e8b5d2f0..e18399d08fb178a750b7c5b07e4afcfa693cf413 100644 (file)
@@ -1708,6 +1708,8 @@ struct kvm_x86_ops {
        void (*enable_nmi_window)(struct kvm_vcpu *vcpu);
        void (*enable_irq_window)(struct kvm_vcpu *vcpu);
        void (*update_cr8_intercept)(struct kvm_vcpu *vcpu, int tpr, int irr);
+
+       const bool x2apic_icr_is_split;
        const unsigned long required_apicv_inhibits;
        bool allow_apicv_in_x2apic_without_x2apic_virtualization;
        void (*refresh_apicv_exec_ctrl)(struct kvm_vcpu *vcpu);
index 9392d6e3d8e37ac0eb6cf70d3db3f41d5ecc0f8e..523d02c50562f42b18ccc81bac8c66856af84a87 100644 (file)
@@ -2469,11 +2469,25 @@ int kvm_x2apic_icr_write(struct kvm_lapic *apic, u64 data)
        data &= ~APIC_ICR_BUSY;
 
        kvm_apic_send_ipi(apic, (u32)data, (u32)(data >> 32));
-       kvm_lapic_set_reg64(apic, APIC_ICR, data);
+       if (kvm_x86_ops.x2apic_icr_is_split) {
+               kvm_lapic_set_reg(apic, APIC_ICR, data);
+               kvm_lapic_set_reg(apic, APIC_ICR2, data >> 32);
+       } else {
+               kvm_lapic_set_reg64(apic, APIC_ICR, data);
+       }
        trace_kvm_apic_write(APIC_ICR, data);
        return 0;
 }
 
+static u64 kvm_x2apic_icr_read(struct kvm_lapic *apic)
+{
+       if (kvm_x86_ops.x2apic_icr_is_split)
+               return (u64)kvm_lapic_get_reg(apic, APIC_ICR) |
+                      (u64)kvm_lapic_get_reg(apic, APIC_ICR2) << 32;
+
+       return kvm_lapic_get_reg64(apic, APIC_ICR);
+}
+
 /* emulate APIC access in a trap manner */
 void kvm_apic_write_nodecode(struct kvm_vcpu *vcpu, u32 offset)
 {
@@ -2491,7 +2505,7 @@ void kvm_apic_write_nodecode(struct kvm_vcpu *vcpu, u32 offset)
         * maybe-unecessary write, and both are in the noise anyways.
         */
        if (apic_x2apic_mode(apic) && offset == APIC_ICR)
-               WARN_ON_ONCE(kvm_x2apic_icr_write(apic, kvm_lapic_get_reg64(apic, APIC_ICR)));
+               WARN_ON_ONCE(kvm_x2apic_icr_write(apic, kvm_x2apic_icr_read(apic)));
        else
                kvm_lapic_reg_write(apic, offset, kvm_lapic_get_reg(apic, offset));
 }
@@ -3011,18 +3025,22 @@ static int kvm_apic_state_fixup(struct kvm_vcpu *vcpu,
 
                /*
                 * In x2APIC mode, the LDR is fixed and based on the id.  And
-                * ICR is internally a single 64-bit register, but needs to be
-                * split to ICR+ICR2 in userspace for backwards compatibility.
+                * if the ICR is _not_ split, ICR is internally a single 64-bit
+                * register, but needs to be split to ICR+ICR2 in userspace for
+                * backwards compatibility.
                 */
-               if (set) {
+               if (set)
                        *ldr = kvm_apic_calc_x2apic_ldr(x2apic_id);
 
-                       icr = __kvm_lapic_get_reg(s->regs, APIC_ICR) |
-                             (u64)__kvm_lapic_get_reg(s->regs, APIC_ICR2) << 32;
-                       __kvm_lapic_set_reg64(s->regs, APIC_ICR, icr);
-               } else {
-                       icr = __kvm_lapic_get_reg64(s->regs, APIC_ICR);
-                       __kvm_lapic_set_reg(s->regs, APIC_ICR2, icr >> 32);
+               if (!kvm_x86_ops.x2apic_icr_is_split) {
+                       if (set) {
+                               icr = __kvm_lapic_get_reg(s->regs, APIC_ICR) |
+                                     (u64)__kvm_lapic_get_reg(s->regs, APIC_ICR2) << 32;
+                               __kvm_lapic_set_reg64(s->regs, APIC_ICR, icr);
+                       } else {
+                               icr = __kvm_lapic_get_reg64(s->regs, APIC_ICR);
+                               __kvm_lapic_set_reg(s->regs, APIC_ICR2, icr >> 32);
+                       }
                }
        }
 
@@ -3219,7 +3237,7 @@ static int kvm_lapic_msr_read(struct kvm_lapic *apic, u32 reg, u64 *data)
        u32 low;
 
        if (reg == APIC_ICR) {
-               *data = kvm_lapic_get_reg64(apic, APIC_ICR);
+               *data = kvm_x2apic_icr_read(apic);
                return 0;
        }
 
index 0357f7af559665f896f00d8137cd0918097b8f9f..6d5da700268a5bead886c0dc06f6b298d748f9aa 100644 (file)
@@ -5051,6 +5051,8 @@ static struct kvm_x86_ops svm_x86_ops __initdata = {
        .enable_nmi_window = svm_enable_nmi_window,
        .enable_irq_window = svm_enable_irq_window,
        .update_cr8_intercept = svm_update_cr8_intercept,
+
+       .x2apic_icr_is_split = true,
        .set_virtual_apic_mode = avic_refresh_virtual_apic_mode,
        .refresh_apicv_exec_ctrl = avic_refresh_apicv_exec_ctrl,
        .apicv_post_state_restore = avic_apicv_post_state_restore,
index 547fca3709febdfaf2d676a4daaf2d28288a55b0..35c2c004dacd2b7ee16211c169155d38afe3c19a 100644 (file)
@@ -89,6 +89,8 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
        .enable_nmi_window = vmx_enable_nmi_window,
        .enable_irq_window = vmx_enable_irq_window,
        .update_cr8_intercept = vmx_update_cr8_intercept,
+
+       .x2apic_icr_is_split = false,
        .set_virtual_apic_mode = vmx_set_virtual_apic_mode,
        .set_apic_access_page_addr = vmx_set_apic_access_page_addr,
        .refresh_apicv_exec_ctrl = vmx_refresh_apicv_exec_ctrl,