From: Santosh Shukla Emulate reads and writes to AMD Extended APIC registers: APIC_EFEAT (0x400), APIC_ECTRL (0x410), and APIC_EILVTn (0x500-0x530). Without emulation, Instruction Based Sampling (IBS) driver fails to initialize when it tries to access APIC_EILVT(0). Extend the LAPIC register read and write paths to allow accesses beyond the standard 0x3f0 offset when the guest has X86_FEATURE_EXTAPIC. The valid range is determined by kvm->arch.nr_extlvt, which userspace configures via KVM_CAP_LAPIC2. Initialize extended APIC registers in both kvm_vcpu_after_set_cpuid() and kvm_lapic_reset(). The initial kvm_lapic_reset() occurs before userspace configures CPUID via KVM_SET_CPUID2, so extended LVT registers can't be initialized until X86_FEATURE_EXTAPIC is set. Handle the initial setup in kvm_vcpu_after_set_cpuid() and subsequent resets in kvm_lapic_reset(). Initialize APIC_EFEAT to report the number of extended LVTs (read-only). Initialize APIC_ECTRL to zero (read-write). Initialize APIC_EILVTn entries to masked (bit 16 set), matching hardware reset behavior. Please refer to Section 16.4.5 in AMD Programmer's Manual Volume 2 at https://bugzilla.kernel.org/attachment.cgi?id=306250 for more details on Extended LVT. Signed-off-by: Santosh Shukla Co-developed-by: Manali Shukla Signed-off-by: Manali Shukla --- arch/x86/include/asm/apicdef.h | 18 ++++++++++++++ arch/x86/kvm/cpuid.c | 10 +++++++- arch/x86/kvm/lapic.c | 43 ++++++++++++++++++++++++++++++++++ arch/x86/kvm/lapic.h | 8 +++++++ 4 files changed, 78 insertions(+), 1 deletion(-) diff --git a/arch/x86/include/asm/apicdef.h b/arch/x86/include/asm/apicdef.h index be39a543fbe5..5c5e9db1e27d 100644 --- a/arch/x86/include/asm/apicdef.h +++ b/arch/x86/include/asm/apicdef.h @@ -148,6 +148,24 @@ #define APIC_EILVT_MSG_EXT 0x7 #define APIC_EILVT_MASKED (1 << 16) +/* + * Initialize extended APIC registers to the default value when guest + * is started and EXTAPIC feature is enabled on the guest. + * + * APIC_EFEAT is a read only Extended APIC feature register, whose bits + * 0, 1, and 2 represent features that are not currently emulated by KVM. + * Therefore, these bits must be cleared during initialization. As a result, the + * default value used for APIC_EFEAT in KVM is set based on number of extended + * LVT registers supported by the guest. + * + * APIC_ECTRL is a read-write Extended APIC control register, whose + * default value is 0x0. + */ + +#define APIC_EFEAT_MASK 0x00FF0000 +#define APIC_EFEAT_DEFAULT(n) ((n << 16) & APIC_EFEAT_MASK) +#define APIC_ECTRL_DEFAULT 0x0 + #define APIC_BASE (fix_to_virt(FIX_APIC_BASE)) #define APIC_BASE_MSR 0x800 #define APIC_X2APIC_ID_MSR 0x802 diff --git a/arch/x86/kvm/cpuid.c b/arch/x86/kvm/cpuid.c index baa1cf473d45..4574149d137b 100644 --- a/arch/x86/kvm/cpuid.c +++ b/arch/x86/kvm/cpuid.c @@ -435,6 +435,14 @@ void kvm_vcpu_after_set_cpuid(struct kvm_vcpu *vcpu) kvm_apic_set_version(vcpu); } + /* + * Initialize extended APIC registers after CPUID is set. The initial + * reset occurs before userspace configures CPUID, so extended LVT + * registers (which require X86_FEATURE_EXTAPIC) can't be initialized + * until after KVM_SET_CPUID2. + */ + kvm_apic_init_extlvt_regs(vcpu); + vcpu->arch.guest_supported_xcr0 = cpuid_get_supported_xcr0(vcpu); vcpu->arch.guest_supported_xss = cpuid_get_supported_xss(vcpu); @@ -1076,7 +1084,7 @@ void kvm_set_cpu_caps(void) F(LAHF_LM), F(CMP_LEGACY), VENDOR_F(SVM), - /* ExtApicSpace */ + F(EXTAPIC), F(CR8_LEGACY), F(ABM), F(SSE4A), diff --git a/arch/x86/kvm/lapic.c b/arch/x86/kvm/lapic.c index 4ed6abb414e4..a04c808289c3 100644 --- a/arch/x86/kvm/lapic.c +++ b/arch/x86/kvm/lapic.c @@ -1687,6 +1687,7 @@ static inline struct kvm_lapic *to_lapic(struct kvm_io_device *dev) test_bit(APIC_REG_TO_BIT(reg), (unsigned long *)(mask)) #define APIC_LAST_REG_OFFSET 0x3f0 +#define APIC_EXT_LAST_REG_OFFSET(n) APIC_EILVTn((n)) void kvm_lapic_readable_reg_mask(struct kvm_lapic *apic, u64 mask[2]) { @@ -1722,6 +1723,12 @@ void kvm_lapic_readable_reg_mask(struct kvm_lapic *apic, u64 mask[2]) APIC_REG_MASK(APIC_DFR, mask); APIC_REG_MASK(APIC_ICR2, mask); } + + if (guest_cpu_cap_has(apic->vcpu, X86_FEATURE_EXTAPIC)) { + APIC_REG_MASK(APIC_EFEAT, mask); + APIC_REG_MASK(APIC_ECTRL, mask); + APIC_REGS_MASK(APIC_EILVTn(0), apic->vcpu->kvm->arch.nr_extlvt, mask); + } } EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_lapic_readable_reg_mask); @@ -1739,6 +1746,13 @@ static int kvm_lapic_reg_read(struct kvm_lapic *apic, u32 offset, int len, */ WARN_ON_ONCE(apic_x2apic_mode(apic) && offset == APIC_ICR); + if (guest_cpu_cap_has(apic->vcpu, X86_FEATURE_EXTAPIC)) { + u8 nr_extlvt = apic->vcpu->kvm->arch.nr_extlvt; + + if (nr_extlvt > 0) + last_reg = APIC_EXT_LAST_REG_OFFSET(nr_extlvt - 1); + } + if (alignment + len > 4) return 1; @@ -2500,7 +2514,15 @@ static int kvm_lapic_reg_write(struct kvm_lapic *apic, u32 reg, u32 val) else kvm_apic_send_ipi(apic, APIC_DEST_SELF | val, 0); break; + default: + if (guest_cpu_cap_has(apic->vcpu, X86_FEATURE_EXTAPIC)) { + if (reg == APIC_ECTRL || + kvm_is_extlvt_offset(reg, apic->vcpu->kvm->arch.nr_extlvt)) { + kvm_lapic_set_reg(apic, reg, val); + break; + } + } ret = 1; break; } @@ -2866,6 +2888,26 @@ void kvm_inhibit_apic_access_page(struct kvm_vcpu *vcpu) kvm_vcpu_srcu_read_lock(vcpu); } +/* + * Initialize extended APIC registers to the default value when guest is + * started. The extended APIC registers should only be initialized when the + * EXTAPIC feature is enabled on the guest. + */ +void kvm_apic_init_extlvt_regs(struct kvm_vcpu *vcpu) +{ + struct kvm_lapic *apic = vcpu->arch.apic; + int i, max_extlvt; + + max_extlvt = vcpu->kvm->arch.nr_extlvt; + + if (guest_cpu_cap_has(vcpu, X86_FEATURE_EXTAPIC)) { + kvm_lapic_set_reg(apic, APIC_EFEAT, APIC_EFEAT_DEFAULT(max_extlvt)); + kvm_lapic_set_reg(apic, APIC_ECTRL, APIC_ECTRL_DEFAULT); + for (i = 0; i < max_extlvt; i++) + kvm_lapic_set_reg(apic, APIC_EILVTn(i), APIC_EILVT_MASKED); + } +} + void kvm_lapic_reset(struct kvm_vcpu *vcpu, bool init_event) { struct kvm_lapic *apic = vcpu->arch.apic; @@ -2927,6 +2969,7 @@ void kvm_lapic_reset(struct kvm_vcpu *vcpu, bool init_event) kvm_lapic_set_reg(apic, APIC_ISR + 0x10 * i, 0); kvm_lapic_set_reg(apic, APIC_TMR + 0x10 * i, 0); } + kvm_apic_init_extlvt_regs(vcpu); kvm_apic_update_apicv(vcpu); update_divide_count(apic); atomic_set(&apic->lapic_timer.pending, 0); diff --git a/arch/x86/kvm/lapic.h b/arch/x86/kvm/lapic.h index c6ac40c76f62..6716828b65e5 100644 --- a/arch/x86/kvm/lapic.h +++ b/arch/x86/kvm/lapic.h @@ -98,6 +98,7 @@ void kvm_apic_ack_interrupt(struct kvm_vcpu *vcpu, int vector); int kvm_apic_accept_pic_intr(struct kvm_vcpu *vcpu); int kvm_apic_accept_events(struct kvm_vcpu *vcpu); void kvm_lapic_reset(struct kvm_vcpu *vcpu, bool init_event); +void kvm_apic_init_extlvt_regs(struct kvm_vcpu *vcpu); u64 kvm_lapic_get_cr8(struct kvm_vcpu *vcpu); void kvm_lapic_set_tpr(struct kvm_vcpu *vcpu, unsigned long cr8); void kvm_lapic_set_eoi(struct kvm_vcpu *vcpu); @@ -266,4 +267,11 @@ static inline u8 kvm_xapic_id(struct kvm_lapic *apic) return kvm_lapic_get_reg(apic, APIC_ID) >> 24; } +static inline bool kvm_is_extlvt_offset(u32 offset, u8 nr_extlvt) +{ + if ((offset < APIC_EILVTn(0)) || (offset & 0xf)) + return false; + return ((offset - APIC_EILVTn(0)) >> 4) < nr_extlvt; +} + #endif -- 2.43.0