Merge branch 'x86/apic' into x86/sev, to resolve conflict

Conflicts:
	arch/x86/include/asm/sev-internal.h

Signed-off-by: Ingo Molnar <mingo@kernel.org>
This commit is contained in:
Ingo Molnar
2025-09-05 09:01:42 +02:00
17 changed files with 679 additions and 59 deletions

View File

@@ -483,6 +483,19 @@ config X86_X2APIC
If in doubt, say Y.
config AMD_SECURE_AVIC
bool "AMD Secure AVIC"
depends on AMD_MEM_ENCRYPT && X86_X2APIC
help
Enable this to get AMD Secure AVIC support on guests that have this feature.
AMD Secure AVIC provides hardware acceleration for performance sensitive
APIC accesses and support for managing guest owned APIC state for SEV-SNP
guests. Secure AVIC does not support xAPIC mode. It has functional
dependency on x2apic being enabled in the guest.
If you don't know what to do here, say N.
config X86_POSTED_MSI
bool "Enable MSI and MSI-x delivery by posted interrupts"
depends on X86_64 && IRQ_REMAP

View File

@@ -186,15 +186,23 @@ bool sev_es_check_ghcb_fault(unsigned long address)
MSR_AMD64_SNP_VMSA_REG_PROT | \
MSR_AMD64_SNP_RESERVED_BIT13 | \
MSR_AMD64_SNP_RESERVED_BIT15 | \
MSR_AMD64_SNP_SECURE_AVIC | \
MSR_AMD64_SNP_RESERVED_MASK)
#ifdef CONFIG_AMD_SECURE_AVIC
#define SNP_FEATURE_SECURE_AVIC MSR_AMD64_SNP_SECURE_AVIC
#else
#define SNP_FEATURE_SECURE_AVIC 0
#endif
/*
* SNP_FEATURES_PRESENT is the mask of SNP features that are implemented
* by the guest kernel. As and when a new feature is implemented in the
* guest kernel, a corresponding bit should be added to the mask.
*/
#define SNP_FEATURES_PRESENT (MSR_AMD64_SNP_DEBUG_SWAP | \
MSR_AMD64_SNP_SECURE_TSC)
MSR_AMD64_SNP_SECURE_TSC | \
SNP_FEATURE_SECURE_AVIC)
u64 snp_get_unsupported_features(u64 status)
{

View File

@@ -104,6 +104,9 @@ static bool noinstr amd_cc_platform_has(enum cc_attr attr)
case CC_ATTR_HOST_SEV_SNP:
return cc_flags.host_sev_snp;
case CC_ATTR_SNP_SECURE_AVIC:
return sev_status & MSR_AMD64_SNP_SECURE_AVIC;
default:
return false;
}

View File

@@ -121,6 +121,7 @@ static const char * const sev_status_feat_names[] = {
[MSR_AMD64_SNP_IBS_VIRT_BIT] = "IBSVirt",
[MSR_AMD64_SNP_VMSA_REG_PROT_BIT] = "VMSARegProt",
[MSR_AMD64_SNP_SMT_PROT_BIT] = "SMTProt",
[MSR_AMD64_SNP_SECURE_AVIC_BIT] = "SecureAVIC",
};
/*
@@ -1105,6 +1106,9 @@ static int wakeup_cpu_via_vmgexit(u32 apic_id, unsigned long start_ip, unsigned
vmsa->x87_ftw = AP_INIT_X87_FTW_DEFAULT;
vmsa->x87_fcw = AP_INIT_X87_FCW_DEFAULT;
if (cc_platform_has(CC_ATTR_SNP_SECURE_AVIC))
vmsa->vintr_ctrl |= V_GIF_MASK | V_NMI_ENABLE_MASK;
/* SVME must be set. */
vmsa->efer = EFER_SVME;
@@ -1239,6 +1243,105 @@ int __init sev_es_efi_map_ghcbs_cas(pgd_t *pgd)
return 0;
}
u64 savic_ghcb_msr_read(u32 reg)
{
u64 msr = APIC_BASE_MSR + (reg >> 4);
struct pt_regs regs = { .cx = msr };
struct es_em_ctxt ctxt = { .regs = &regs };
struct ghcb_state state;
enum es_result res;
struct ghcb *ghcb;
guard(irqsave)();
ghcb = __sev_get_ghcb(&state);
vc_ghcb_invalidate(ghcb);
res = sev_es_ghcb_handle_msr(ghcb, &ctxt, false);
if (res != ES_OK) {
pr_err("Secure AVIC MSR (0x%llx) read returned error (%d)\n", msr, res);
/* MSR read failures are treated as fatal errors */
sev_es_terminate(SEV_TERM_SET_LINUX, GHCB_TERM_SAVIC_FAIL);
}
__sev_put_ghcb(&state);
return regs.ax | regs.dx << 32;
}
void savic_ghcb_msr_write(u32 reg, u64 value)
{
u64 msr = APIC_BASE_MSR + (reg >> 4);
struct pt_regs regs = {
.cx = msr,
.ax = lower_32_bits(value),
.dx = upper_32_bits(value)
};
struct es_em_ctxt ctxt = { .regs = &regs };
struct ghcb_state state;
enum es_result res;
struct ghcb *ghcb;
guard(irqsave)();
ghcb = __sev_get_ghcb(&state);
vc_ghcb_invalidate(ghcb);
res = sev_es_ghcb_handle_msr(ghcb, &ctxt, true);
if (res != ES_OK) {
pr_err("Secure AVIC MSR (0x%llx) write returned error (%d)\n", msr, res);
/* MSR writes should never fail. Any failure is fatal error for SNP guest */
sev_es_terminate(SEV_TERM_SET_LINUX, GHCB_TERM_SAVIC_FAIL);
}
__sev_put_ghcb(&state);
}
enum es_result savic_register_gpa(u64 gpa)
{
struct ghcb_state state;
struct es_em_ctxt ctxt;
enum es_result res;
struct ghcb *ghcb;
guard(irqsave)();
ghcb = __sev_get_ghcb(&state);
vc_ghcb_invalidate(ghcb);
ghcb_set_rax(ghcb, SVM_VMGEXIT_SAVIC_SELF_GPA);
ghcb_set_rbx(ghcb, gpa);
res = sev_es_ghcb_hv_call(ghcb, &ctxt, SVM_VMGEXIT_SAVIC,
SVM_VMGEXIT_SAVIC_REGISTER_GPA, 0);
__sev_put_ghcb(&state);
return res;
}
enum es_result savic_unregister_gpa(u64 *gpa)
{
struct ghcb_state state;
struct es_em_ctxt ctxt;
enum es_result res;
struct ghcb *ghcb;
guard(irqsave)();
ghcb = __sev_get_ghcb(&state);
vc_ghcb_invalidate(ghcb);
ghcb_set_rax(ghcb, SVM_VMGEXIT_SAVIC_SELF_GPA);
res = sev_es_ghcb_hv_call(ghcb, &ctxt, SVM_VMGEXIT_SAVIC,
SVM_VMGEXIT_SAVIC_UNREGISTER_GPA, 0);
if (gpa && res == ES_OK)
*gpa = ghcb->save.rbx;
__sev_put_ghcb(&state);
return res;
}
static void snp_register_per_cpu_ghcb(void)
{
struct sev_es_runtime_data *data;

View File

@@ -404,14 +404,10 @@ static enum es_result __vc_handle_secure_tsc_msrs(struct es_em_ctxt *ctxt, bool
return ES_OK;
}
static enum es_result vc_handle_msr(struct ghcb *ghcb, struct es_em_ctxt *ctxt)
enum es_result sev_es_ghcb_handle_msr(struct ghcb *ghcb, struct es_em_ctxt *ctxt, bool write)
{
struct pt_regs *regs = ctxt->regs;
enum es_result ret;
bool write;
/* Is it a WRMSR? */
write = ctxt->insn.opcode.bytes[1] == 0x30;
switch (regs->cx) {
case MSR_SVSM_CAA:
@@ -421,6 +417,15 @@ static enum es_result vc_handle_msr(struct ghcb *ghcb, struct es_em_ctxt *ctxt)
if (sev_status & MSR_AMD64_SNP_SECURE_TSC)
return __vc_handle_secure_tsc_msrs(ctxt, write);
break;
case MSR_AMD64_SAVIC_CONTROL:
/*
* AMD64_SAVIC_CONTROL should not be intercepted when
* Secure AVIC is enabled. Terminate the Secure AVIC guest
* if the interception is enabled.
*/
if (cc_platform_has(CC_ATTR_SNP_SECURE_AVIC))
return ES_VMM_ERROR;
break;
default:
break;
}
@@ -441,6 +446,11 @@ static enum es_result vc_handle_msr(struct ghcb *ghcb, struct es_em_ctxt *ctxt)
return ret;
}
static enum es_result vc_handle_msr(struct ghcb *ghcb, struct es_em_ctxt *ctxt)
{
return sev_es_ghcb_handle_msr(ghcb, ctxt, ctxt->insn.opcode.bytes[1] == 0x30);
}
static void __init vc_early_forward_exception(struct es_em_ctxt *ctxt)
{
int trapnr = ctxt->fi.vector;

View File

@@ -305,6 +305,8 @@ struct apic {
/* Probe, setup and smpboot functions */
int (*probe)(void);
void (*setup)(void);
void (*teardown)(void);
int (*acpi_madt_oem_check)(char *oem_id, char *oem_table_id);
void (*init_apic_ldr)(void);
@@ -317,6 +319,8 @@ struct apic {
/* wakeup secondary CPU using 64-bit wakeup point */
int (*wakeup_secondary_cpu_64)(u32 apicid, unsigned long start_eip, unsigned int cpu);
void (*update_vector)(unsigned int cpu, unsigned int vector, bool set);
char *name;
};
@@ -470,6 +474,12 @@ static __always_inline bool apic_id_valid(u32 apic_id)
return apic_id <= apic->max_apic_id;
}
static __always_inline void apic_update_vector(unsigned int cpu, unsigned int vector, bool set)
{
if (apic->update_vector)
apic->update_vector(cpu, vector, set);
}
#else /* CONFIG_X86_LOCAL_APIC */
static inline u32 apic_read(u32 reg) { return 0; }
@@ -481,6 +491,7 @@ static inline void apic_wait_icr_idle(void) { }
static inline u32 safe_apic_wait_icr_idle(void) { return 0; }
static inline void apic_native_eoi(void) { WARN_ON_ONCE(1); }
static inline void apic_setup_apic_calls(void) { }
static inline void apic_update_vector(unsigned int cpu, unsigned int vector, bool set) { }
#define apic_update_callback(_callback, _fn) do { } while (0)

View File

@@ -135,6 +135,8 @@
#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

View File

@@ -699,8 +699,15 @@
#define MSR_AMD64_SNP_VMSA_REG_PROT BIT_ULL(MSR_AMD64_SNP_VMSA_REG_PROT_BIT)
#define MSR_AMD64_SNP_SMT_PROT_BIT 17
#define MSR_AMD64_SNP_SMT_PROT BIT_ULL(MSR_AMD64_SNP_SMT_PROT_BIT)
#define MSR_AMD64_SNP_RESV_BIT 18
#define MSR_AMD64_SNP_SECURE_AVIC_BIT 18
#define MSR_AMD64_SNP_SECURE_AVIC BIT_ULL(MSR_AMD64_SNP_SECURE_AVIC_BIT)
#define MSR_AMD64_SNP_RESV_BIT 19
#define MSR_AMD64_SNP_RESERVED_MASK GENMASK_ULL(63, MSR_AMD64_SNP_RESV_BIT)
#define MSR_AMD64_SAVIC_CONTROL 0xc0010138
#define MSR_AMD64_SAVIC_EN_BIT 0
#define MSR_AMD64_SAVIC_EN BIT_ULL(MSR_AMD64_SAVIC_EN_BIT)
#define MSR_AMD64_SAVIC_ALLOWEDNMI_BIT 1
#define MSR_AMD64_SAVIC_ALLOWEDNMI BIT_ULL(MSR_AMD64_SAVIC_ALLOWEDNMI_BIT)
#define MSR_AMD64_RMP_BASE 0xc0010132
#define MSR_AMD64_RMP_END 0xc0010133
#define MSR_AMD64_RMP_CFG 0xc0010136

View File

@@ -208,6 +208,7 @@ struct snp_psc_desc {
#define GHCB_TERM_SVSM_CAA 9 /* SVSM is present but CAA is not page aligned */
#define GHCB_TERM_SECURE_TSC 10 /* Secure TSC initialization failed */
#define GHCB_TERM_SVSM_CA_REMAP_FAIL 11 /* SVSM is present but CA could not be remapped */
#define GHCB_TERM_SAVIC_FAIL 12 /* Secure AVIC-specific failure */
#define GHCB_RESP_CODE(v) ((v) & GHCB_MSR_INFO_MASK)

View File

@@ -80,6 +80,8 @@ static __always_inline void sev_es_wr_ghcb_msr(u64 val)
native_wrmsr(MSR_AMD64_SEV_ES_GHCB, low, high);
}
enum es_result sev_es_ghcb_handle_msr(struct ghcb *ghcb, struct es_em_ctxt *ctxt, bool write);
u64 get_hv_features(void);
const struct snp_cpuid_table *snp_cpuid_get_table(void);

View File

@@ -532,6 +532,10 @@ int snp_svsm_vtpm_send_command(u8 *buffer);
void __init snp_secure_tsc_prepare(void);
void __init snp_secure_tsc_init(void);
enum es_result savic_register_gpa(u64 gpa);
enum es_result savic_unregister_gpa(u64 *gpa);
u64 savic_ghcb_msr_read(u32 reg);
void savic_ghcb_msr_write(u32 reg, u64 value);
static __always_inline void vc_ghcb_invalidate(struct ghcb *ghcb)
{
@@ -617,6 +621,10 @@ static inline int snp_send_guest_request(struct snp_msg_desc *mdesc,
static inline int snp_svsm_vtpm_send_command(u8 *buffer) { return -ENODEV; }
static inline void __init snp_secure_tsc_prepare(void) { }
static inline void __init snp_secure_tsc_init(void) { }
static inline enum es_result savic_register_gpa(u64 gpa) { return ES_UNSUPPORTED; }
static inline enum es_result savic_unregister_gpa(u64 *gpa) { return ES_UNSUPPORTED; }
static inline void savic_ghcb_msr_write(u32 reg, u64 value) { }
static inline u64 savic_ghcb_msr_read(u32 reg) { return 0; }
#endif /* CONFIG_AMD_MEM_ENCRYPT */

View File

@@ -118,6 +118,10 @@
#define SVM_VMGEXIT_AP_CREATE 1
#define SVM_VMGEXIT_AP_DESTROY 2
#define SVM_VMGEXIT_SNP_RUN_VMPL 0x80000018
#define SVM_VMGEXIT_SAVIC 0x8000001a
#define SVM_VMGEXIT_SAVIC_REGISTER_GPA 0
#define SVM_VMGEXIT_SAVIC_UNREGISTER_GPA 1
#define SVM_VMGEXIT_SAVIC_SELF_GPA ~0ULL
#define SVM_VMGEXIT_HV_FEATURES 0x8000fffd
#define SVM_VMGEXIT_TERM_REQUEST 0x8000fffe
#define SVM_VMGEXIT_TERM_REASON(reason_set, reason_code) \

View File

@@ -18,6 +18,7 @@ ifeq ($(CONFIG_X86_64),y)
# APIC probe will depend on the listing order here
obj-$(CONFIG_X86_NUMACHIP) += apic_numachip.o
obj-$(CONFIG_X86_UV) += x2apic_uv_x.o
obj-$(CONFIG_AMD_SECURE_AVIC) += x2apic_savic.o
obj-$(CONFIG_X86_X2APIC) += x2apic_phys.o
obj-$(CONFIG_X86_X2APIC) += x2apic_cluster.o
obj-y += apic_flat_64.o

View File

@@ -592,6 +592,8 @@ static void setup_APIC_timer(void)
0xF, ~0UL);
} else
clockevents_register_device(levt);
apic_update_vector(smp_processor_id(), LOCAL_TIMER_VECTOR, true);
}
/*
@@ -1168,6 +1170,9 @@ void disable_local_APIC(void)
if (!apic_accessible())
return;
if (apic->teardown)
apic->teardown();
apic_soft_disable();
#ifdef CONFIG_X86_32
@@ -1428,63 +1433,61 @@ union apic_ir {
u32 regs[APIC_IR_REGS];
};
static bool apic_check_and_ack(union apic_ir *irr, union apic_ir *isr)
static bool apic_check_and_eoi_isr(union apic_ir *isr)
{
int i, bit;
/* Read the IRRs */
for (i = 0; i < APIC_IR_REGS; i++)
irr->regs[i] = apic_read(APIC_IRR + i * 0x10);
/* Read the ISRs */
for (i = 0; i < APIC_IR_REGS; i++)
isr->regs[i] = apic_read(APIC_ISR + i * 0x10);
/*
* If the ISR map is not empty. ACK the APIC and run another round
* to verify whether a pending IRR has been unblocked and turned
* into a ISR.
*/
if (!bitmap_empty(isr->map, APIC_IR_BITS)) {
/*
* There can be multiple ISR bits set when a high priority
* interrupt preempted a lower priority one. Issue an ACK
* per set bit.
*/
for_each_set_bit(bit, isr->map, APIC_IR_BITS)
apic_eoi();
/* If the ISR map empty, nothing to do here. */
if (bitmap_empty(isr->map, APIC_IR_BITS))
return true;
}
return !bitmap_empty(irr->map, APIC_IR_BITS);
/*
* There can be multiple ISR bits set when a high priority
* interrupt preempted a lower priority one. Issue an EOI for each
* set bit. The priority traversal order does not matter as there
* can't be new ISR bits raised at this point. What matters is that
* an EOI is issued for each ISR bit.
*/
for_each_set_bit(bit, isr->map, APIC_IR_BITS)
apic_eoi();
/* Reread the ISRs, they should be empty now */
for (i = 0; i < APIC_IR_REGS; i++)
isr->regs[i] = apic_read(APIC_ISR + i * 0x10);
return bitmap_empty(isr->map, APIC_IR_BITS);
}
/*
* After a crash, we no longer service the interrupts and a pending
* interrupt from previous kernel might still have ISR bit set.
* If a CPU services an interrupt and crashes before issuing EOI to the
* local APIC, the corresponding ISR bit is still set when the crashing CPU
* jumps into a crash kernel. Read the ISR and issue an EOI for each set
* bit to acknowledge it as otherwise these slots would be locked forever
* waiting for an EOI.
*
* Most probably by now the CPU has serviced that pending interrupt and it
* might not have done the apic_eoi() because it thought, interrupt
* came from i8259 as ExtInt. LAPIC did not get EOI so it does not clear
* the ISR bit and cpu thinks it has already serviced the interrupt. Hence
* a vector might get locked. It was noticed for timer irq (vector
* 0x31). Issue an extra EOI to clear ISR.
* If there are pending bits in the IRR, then they won't be converted into
* ISR bits as the CPU has interrupts disabled. They will be delivered once
* the CPU enables interrupts and there is nothing which can prevent that.
*
* If there are pending IRR bits they turn into ISR bits after a higher
* priority ISR bit has been acked.
* In the worst case this results in spurious interrupt warnings.
*/
static void apic_pending_intr_clear(void)
static void apic_clear_isr(void)
{
union apic_ir irr, isr;
union apic_ir ir;
unsigned int i;
/* 512 loops are way oversized and give the APIC a chance to obey. */
for (i = 0; i < 512; i++) {
if (!apic_check_and_ack(&irr, &isr))
return;
}
/* Dump the IRR/ISR content if that failed */
pr_warn("APIC: Stale IRR: %256pb ISR: %256pb\n", irr.map, isr.map);
if (!apic_check_and_eoi_isr(&ir))
pr_warn("APIC: Stale ISR: %256pb\n", ir.map);
for (i = 0; i < APIC_IR_REGS; i++)
ir.regs[i] = apic_read(APIC_IRR + i * 0x10);
if (!bitmap_empty(ir.map, APIC_IR_BITS))
pr_warn("APIC: Stale IRR: %256pb\n", ir.map);
}
/**
@@ -1503,6 +1506,9 @@ static void setup_local_APIC(void)
return;
}
if (apic->setup)
apic->setup();
/*
* If this comes from kexec/kcrash the APIC might be enabled in
* SPIV. Soft disable it before doing further initialization.
@@ -1541,8 +1547,7 @@ static void setup_local_APIC(void)
value |= 0x10;
apic_write(APIC_TASKPRI, value);
/* Clear eventually stale ISR/IRR bits */
apic_pending_intr_clear();
apic_clear_isr();
/*
* Now that we are all set up, enable the APIC

View File

@@ -134,13 +134,20 @@ static void apic_update_irq_cfg(struct irq_data *irqd, unsigned int vector,
apicd->hw_irq_cfg.vector = vector;
apicd->hw_irq_cfg.dest_apicid = apic->calc_dest_apicid(cpu);
apic_update_vector(cpu, vector, true);
irq_data_update_effective_affinity(irqd, cpumask_of(cpu));
trace_vector_config(irqd->irq, vector, cpu,
apicd->hw_irq_cfg.dest_apicid);
trace_vector_config(irqd->irq, vector, cpu, apicd->hw_irq_cfg.dest_apicid);
}
static void apic_update_vector(struct irq_data *irqd, unsigned int newvec,
unsigned int newcpu)
static void apic_free_vector(unsigned int cpu, unsigned int vector, bool managed)
{
apic_update_vector(cpu, vector, false);
irq_matrix_free(vector_matrix, cpu, vector, managed);
}
static void chip_data_update(struct irq_data *irqd, unsigned int newvec, unsigned int newcpu)
{
struct apic_chip_data *apicd = apic_chip_data(irqd);
struct irq_desc *desc = irq_data_to_desc(irqd);
@@ -174,8 +181,7 @@ static void apic_update_vector(struct irq_data *irqd, unsigned int newvec,
apicd->prev_cpu = apicd->cpu;
WARN_ON_ONCE(apicd->cpu == newcpu);
} else {
irq_matrix_free(vector_matrix, apicd->cpu, apicd->vector,
managed);
apic_free_vector(apicd->cpu, apicd->vector, managed);
}
setnew:
@@ -261,7 +267,7 @@ assign_vector_locked(struct irq_data *irqd, const struct cpumask *dest)
trace_vector_alloc(irqd->irq, vector, resvd, vector);
if (vector < 0)
return vector;
apic_update_vector(irqd, vector, cpu);
chip_data_update(irqd, vector, cpu);
return 0;
}
@@ -337,7 +343,7 @@ assign_managed_vector(struct irq_data *irqd, const struct cpumask *dest)
trace_vector_alloc_managed(irqd->irq, vector, vector);
if (vector < 0)
return vector;
apic_update_vector(irqd, vector, cpu);
chip_data_update(irqd, vector, cpu);
return 0;
}
@@ -357,7 +363,7 @@ static void clear_irq_vector(struct irq_data *irqd)
apicd->prev_cpu);
per_cpu(vector_irq, apicd->cpu)[vector] = VECTOR_SHUTDOWN;
irq_matrix_free(vector_matrix, apicd->cpu, vector, managed);
apic_free_vector(apicd->cpu, vector, managed);
apicd->vector = 0;
/* Clean up move in progress */
@@ -366,7 +372,7 @@ static void clear_irq_vector(struct irq_data *irqd)
return;
per_cpu(vector_irq, apicd->prev_cpu)[vector] = VECTOR_SHUTDOWN;
irq_matrix_free(vector_matrix, apicd->prev_cpu, vector, managed);
apic_free_vector(apicd->prev_cpu, vector, managed);
apicd->prev_vector = 0;
apicd->move_in_progress = 0;
hlist_del_init(&apicd->clist);
@@ -905,7 +911,7 @@ static void free_moved_vector(struct apic_chip_data *apicd)
* affinity mask comes online.
*/
trace_vector_free_moved(apicd->irq, cpu, vector, managed);
irq_matrix_free(vector_matrix, cpu, vector, managed);
apic_free_vector(cpu, vector, managed);
per_cpu(vector_irq, cpu)[vector] = VECTOR_UNUSED;
hlist_del_init(&apicd->clist);
apicd->prev_vector = 0;

View File

@@ -0,0 +1,428 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AMD Secure AVIC Support (SEV-SNP Guests)
*
* Copyright (C) 2024 Advanced Micro Devices, Inc.
*
* Author: Neeraj Upadhyay <Neeraj.Upadhyay@amd.com>
*/
#include <linux/cc_platform.h>
#include <linux/cpumask.h>
#include <linux/percpu-defs.h>
#include <linux/align.h>
#include <asm/apic.h>
#include <asm/sev.h>
#include "local.h"
struct secure_avic_page {
u8 regs[PAGE_SIZE];
} __aligned(PAGE_SIZE);
static struct secure_avic_page __percpu *savic_page __ro_after_init;
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);
}
static inline void *get_reg_bitmap(unsigned int cpu, unsigned int offset)
{
return &per_cpu_ptr(savic_page, cpu)->regs[offset];
}
static inline void update_vector(unsigned int cpu, unsigned int offset,
unsigned int vector, bool set)
{
void *bitmap = get_reg_bitmap(cpu, offset);
if (set)
apic_set_vector(vector, bitmap);
else
apic_clear_vector(vector, bitmap);
}
#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_LVTTHMR:
case APIC_LVTPC:
case APIC_LVT0:
case APIC_LVT1:
case APIC_LVTERR:
return savic_ghcb_msr_read(reg);
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_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
/*
* On WRMSR to APIC_SELF_IPI register by the guest, Secure AVIC hardware
* updates the APIC_IRR in the APIC backing page of the vCPU. In addition,
* hardware evaluates the new APIC_IRR update for interrupt injection to
* the vCPU. So, self IPIs are hardware-accelerated.
*/
static inline void self_ipi_reg_write(unsigned int vector)
{
native_apic_msr_write(APIC_SELF_IPI, vector);
}
static void send_ipi_dest(unsigned int cpu, unsigned int vector, bool nmi)
{
if (nmi)
apic_set_reg(per_cpu_ptr(savic_page, cpu), SAVIC_NMI_REQ, 1);
else
update_vector(cpu, APIC_IRR, vector, true);
}
static void send_ipi_allbut(unsigned int vector, bool nmi)
{
unsigned int cpu, src_cpu;
guard(irqsave)();
src_cpu = raw_smp_processor_id();
for_each_cpu(cpu, cpu_online_mask) {
if (cpu == src_cpu)
continue;
send_ipi_dest(cpu, vector, nmi);
}
}
static inline void self_ipi(unsigned int vector, bool nmi)
{
u32 icr_low = APIC_SELF_IPI | vector;
if (nmi)
icr_low |= APIC_DM_NMI;
native_x2apic_icr_write(icr_low, 0);
}
static void savic_icr_write(u32 icr_low, u32 icr_high)
{
unsigned int dsh, vector;
u64 icr_data;
bool nmi;
dsh = icr_low & APIC_DEST_ALLBUT;
vector = icr_low & APIC_VECTOR_MASK;
nmi = ((icr_low & APIC_DM_FIXED_MASK) == APIC_DM_NMI);
switch (dsh) {
case APIC_DEST_SELF:
self_ipi(vector, nmi);
break;
case APIC_DEST_ALLINC:
self_ipi(vector, nmi);
fallthrough;
case APIC_DEST_ALLBUT:
send_ipi_allbut(vector, nmi);
break;
default:
send_ipi_dest(icr_high, vector, nmi);
break;
}
icr_data = ((u64)icr_high) << 32 | icr_low;
if (dsh != APIC_DEST_SELF)
savic_ghcb_msr_write(APIC_ICR, icr_data);
apic_set_reg64(this_cpu_ptr(savic_page), APIC_ICR, icr_data);
}
static void savic_write(u32 reg, u32 data)
{
void *ap = this_cpu_ptr(savic_page);
switch (reg) {
case APIC_LVTT:
case APIC_TMICT:
case APIC_TDCR:
case APIC_LVT0:
case APIC_LVT1:
case APIC_LVTTHMR:
case APIC_LVTPC:
case APIC_LVTERR:
savic_ghcb_msr_write(reg, data);
break;
case APIC_TASKPRI:
case APIC_EOI:
case APIC_SPIV:
case SAVIC_NMI_REQ:
case APIC_ESR:
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:
savic_icr_write(data, 0);
break;
case APIC_SELF_IPI:
self_ipi_reg_write(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 send_ipi(u32 dest, unsigned int vector, unsigned int dsh)
{
unsigned int icr_low;
icr_low = __prepare_ICR(dsh, vector, APIC_DEST_PHYSICAL);
savic_icr_write(icr_low, dest);
}
static void savic_send_ipi(int cpu, int vector)
{
u32 dest = per_cpu(x86_cpu_to_apicid, cpu);
send_ipi(dest, vector, 0);
}
static void send_ipi_mask(const struct cpumask *mask, unsigned int vector, bool excl_self)
{
unsigned int cpu, this_cpu;
guard(irqsave)();
this_cpu = raw_smp_processor_id();
for_each_cpu(cpu, mask) {
if (excl_self && cpu == this_cpu)
continue;
send_ipi(per_cpu(x86_cpu_to_apicid, cpu), vector, 0);
}
}
static void savic_send_ipi_mask(const struct cpumask *mask, int vector)
{
send_ipi_mask(mask, vector, false);
}
static void savic_send_ipi_mask_allbutself(const struct cpumask *mask, int vector)
{
send_ipi_mask(mask, vector, true);
}
static void savic_send_ipi_allbutself(int vector)
{
send_ipi(0, vector, APIC_DEST_ALLBUT);
}
static void savic_send_ipi_all(int vector)
{
send_ipi(0, vector, APIC_DEST_ALLINC);
}
static void savic_send_ipi_self(int vector)
{
self_ipi_reg_write(vector);
}
static void savic_update_vector(unsigned int cpu, unsigned int vector, bool set)
{
update_vector(cpu, SAVIC_ALLOWED_IRR, vector, set);
}
static void savic_eoi(void)
{
unsigned int cpu;
int vec;
cpu = raw_smp_processor_id();
vec = apic_find_highest_vector(get_reg_bitmap(cpu, APIC_ISR));
if (WARN_ONCE(vec == -1, "EOI write while no active interrupt in APIC_ISR"))
return;
/* Is level-triggered interrupt? */
if (apic_test_vector(vec, get_reg_bitmap(cpu, APIC_TMR))) {
update_vector(cpu, APIC_ISR, vec, false);
/*
* Propagate the EOI write to the hypervisor for level-triggered
* interrupts. Return to the guest from GHCB protocol event takes
* care of re-evaluating interrupt state.
*/
savic_ghcb_msr_write(APIC_EOI, 0);
} else {
/*
* Hardware clears APIC_ISR and re-evaluates the interrupt state
* to determine if there is any pending interrupt which can be
* delivered to CPU.
*/
native_apic_msr_eoi();
}
}
static void savic_teardown(void)
{
/* Disable Secure AVIC */
native_wrmsrq(MSR_AMD64_SAVIC_CONTROL, 0);
savic_unregister_gpa(NULL);
}
static void savic_setup(void)
{
void *ap = this_cpu_ptr(savic_page);
enum es_result res;
unsigned long gpa;
/*
* Before Secure AVIC is enabled, APIC MSR reads are intercepted.
* APIC_ID MSR read returns the value from the hypervisor.
*/
apic_set_reg(ap, APIC_ID, native_apic_msr_read(APIC_ID));
gpa = __pa(ap);
/*
* The NPT entry for a vCPU's APIC backing page must always be
* present when the vCPU is running in order for Secure AVIC to
* function. A VMEXIT_BUSY is returned on VMRUN and the vCPU cannot
* be resumed if the NPT entry for the APIC backing page is not
* present. Notify GPA of the vCPU's APIC backing page to the
* hypervisor by calling savic_register_gpa(). Before executing
* VMRUN, the hypervisor makes use of this information to make sure
* the APIC backing page is mapped in NPT.
*/
res = savic_register_gpa(gpa);
if (res != ES_OK)
sev_es_terminate(SEV_TERM_SET_LINUX, GHCB_TERM_SAVIC_FAIL);
native_wrmsrq(MSR_AMD64_SAVIC_CONTROL,
gpa | MSR_AMD64_SAVIC_EN | MSR_AMD64_SAVIC_ALLOWEDNMI);
}
static int savic_probe(void)
{
if (!cc_platform_has(CC_ATTR_SNP_SECURE_AVIC))
return 0;
if (!x2apic_mode) {
pr_err("Secure AVIC enabled in non x2APIC mode\n");
sev_es_terminate(SEV_TERM_SET_LINUX, GHCB_TERM_SAVIC_FAIL);
/* unreachable */
}
savic_page = alloc_percpu(struct secure_avic_page);
if (!savic_page)
sev_es_terminate(SEV_TERM_SET_LINUX, GHCB_TERM_SAVIC_FAIL);
return 1;
}
static struct apic apic_x2apic_savic __ro_after_init = {
.name = "secure avic x2apic",
.probe = savic_probe,
.acpi_madt_oem_check = savic_acpi_madt_oem_check,
.setup = savic_setup,
.teardown = savic_teardown,
.dest_mode_logical = false,
.disable_esr = 0,
.cpu_present_to_apicid = default_cpu_present_to_apicid,
.max_apic_id = UINT_MAX,
.x2apic_set_max_apicid = true,
.get_apic_id = x2apic_get_apic_id,
.calc_dest_apicid = apic_default_calc_apicid,
.send_IPI = savic_send_ipi,
.send_IPI_mask = savic_send_ipi_mask,
.send_IPI_mask_allbutself = savic_send_ipi_mask_allbutself,
.send_IPI_allbutself = savic_send_ipi_allbutself,
.send_IPI_all = savic_send_ipi_all,
.send_IPI_self = savic_send_ipi_self,
.nmi_to_offline_cpu = true,
.read = savic_read,
.write = savic_write,
.eoi = savic_eoi,
.icr_read = native_x2apic_icr_read,
.icr_write = savic_icr_write,
.update_vector = savic_update_vector,
};
apic_driver(apic_x2apic_savic);

View File

@@ -96,6 +96,14 @@ enum cc_attr {
* enabled to run SEV-SNP guests.
*/
CC_ATTR_HOST_SEV_SNP,
/**
* @CC_ATTR_SNP_SECURE_AVIC: Secure AVIC mode is active.
*
* The host kernel is running with the necessary features enabled
* to run SEV-SNP guests with full Secure AVIC capabilities.
*/
CC_ATTR_SNP_SECURE_AVIC,
};
#ifdef CONFIG_ARCH_HAS_CC_PLATFORM