With PMC virtualization, an SEV-ES or SNP guest writes its event-selects directly, and the hypervisor filters events in hardware: a write that enables an event missing from its allow list raises a #VC. Forward such writes with SVM_VMGEXIT_PMC_FILTER, along with every event-select, and program the value the hypervisor returns. Write the requested value ANDed with the value the hypervisor returns. This way the hypervisor can only turn bits off, e.g. ENABLE for a denied event, and never turn bits on. Turning ENABLE back on for a denied event would trap again inside the #VC handler. On a PerfCntrGlobalCtl write, first write each event-select ANDed with its returned value, which turns off the counters with denied events, then write PerfCntrGlobalCtl. Signed-off-by: Manali Shukla --- arch/x86/coco/core.c | 3 ++ arch/x86/coco/sev/core.c | 2 +- arch/x86/coco/sev/internal.h | 1 + arch/x86/coco/sev/vc-handle.c | 68 +++++++++++++++++++++++++++++++++++ include/linux/cc_platform.h | 8 +++++ 5 files changed, 81 insertions(+), 1 deletion(-) diff --git a/arch/x86/coco/core.c b/arch/x86/coco/core.c index 989ca9f72ba30..d8249a3acb605 100644 --- a/arch/x86/coco/core.c +++ b/arch/x86/coco/core.c @@ -107,6 +107,9 @@ static bool noinstr amd_cc_platform_has(enum cc_attr attr) case CC_ATTR_SNP_SECURE_AVIC: return sev_status & MSR_AMD64_SNP_SECURE_AVIC; + case CC_ATTR_GUEST_PMC_VIRT: + return sev_status & MSR_AMD64_SNP_PMC_VIRT; + default: return false; } diff --git a/arch/x86/coco/sev/core.c b/arch/x86/coco/sev/core.c index 60a0403898e4f..8b89725290af1 100644 --- a/arch/x86/coco/sev/core.c +++ b/arch/x86/coco/sev/core.c @@ -756,7 +756,7 @@ static void *snp_alloc_vmsa_page(int cpu) return page_address(p + 1); } -static unsigned int sev_pmc_nr_gp_counters __ro_after_init; +unsigned int sev_pmc_nr_gp_counters __ro_after_init; static int __init sev_pmc_init(void) { diff --git a/arch/x86/coco/sev/internal.h b/arch/x86/coco/sev/internal.h index b9632c0fc3916..78486007d22ef 100644 --- a/arch/x86/coco/sev/internal.h +++ b/arch/x86/coco/sev/internal.h @@ -6,6 +6,7 @@ extern u64 sev_hv_features; extern u64 sev_secrets_pa; +extern unsigned int sev_pmc_nr_gp_counters; /* #VC handler runtime per-CPU data */ struct sev_es_runtime_data { diff --git a/arch/x86/coco/sev/vc-handle.c b/arch/x86/coco/sev/vc-handle.c index 96b62b49b2b53..7486762969f63 100644 --- a/arch/x86/coco/sev/vc-handle.c +++ b/arch/x86/coco/sev/vc-handle.c @@ -33,6 +33,7 @@ #include #include #include +#include #include "internal.h" @@ -368,6 +369,63 @@ static enum es_result __vc_handle_msr_caa(struct pt_regs *regs, bool write) return ES_OK; } +static u32 sev_pmc_eventsel_msr(unsigned int i) +{ + if (boot_cpu_has(X86_FEATURE_PERFCTR_CORE)) + return MSR_F15H_PERF_CTL0 + 2 * i; + + return MSR_K7_EVNTSEL0 + i; +} + +static enum es_result __vc_handle_pmc_msr(struct ghcb *ghcb, struct es_em_ctxt *ctxt) +{ + struct perf_ctls *ctls = (struct perf_ctls *)ghcb->shared_buffer; + u64 eventsels[AMD64_NUM_COUNTERS_CORE]; + struct pt_regs *regs = ctxt->regs; + u64 val = lower_32_bits(regs->ax) | ((u64)lower_32_bits(regs->dx) << 32); + unsigned int i, n = sev_pmc_nr_gp_counters; + enum es_result ret; + + /* + * Disable the counter first, so it doesn't keep running an event the + * hypervisor removes from its allow list while handling this request. + */ + if (regs->cx != MSR_AMD64_PERF_CNTR_GLOBAL_CTL) + native_write_msr(regs->cx, val & ~ARCH_PERFMON_EVENTSEL_ENABLE); + + ctls->num_counters = n; + for (i = 0; i < n; i++) { + eventsels[i] = native_read_msr(sev_pmc_eventsel_msr(i)); + ctls->counters[i].perf_ctl_idx = sev_pmc_eventsel_msr(i); + ctls->counters[i].perf_ctl_val = eventsels[i]; + } + + ghcb_set_sw_scratch(ghcb, __pa(ctls)); + ghcb_set_rcx(ghcb, regs->cx); + ghcb_set_rax(ghcb, regs->ax); + ghcb_set_rdx(ghcb, regs->dx); + + ret = sev_es_ghcb_hv_call(ghcb, ctxt, SVM_VMGEXIT_PMC_FILTER, 0, 0); + if (ret != ES_OK) + return ret; + + if (regs->cx == MSR_AMD64_PERF_CNTR_GLOBAL_CTL) { + for (i = 0; i < n; i++) + native_write_msr(sev_pmc_eventsel_msr(i), + eventsels[i] & ctls->counters[i].perf_ctl_val); + } else { + if (!(ghcb_rax_is_valid(ghcb) && ghcb_rdx_is_valid(ghcb))) + return ES_VMM_ERROR; + + val &= lower_32_bits(ghcb->save.rax) | + ((u64)lower_32_bits(ghcb->save.rdx) << 32); + } + + native_write_msr(regs->cx, val); + + return ES_OK; +} + /* * TSC related accesses should not exit to the hypervisor when a guest is * executing with Secure TSC enabled, so special handling is required for @@ -426,6 +484,16 @@ enum es_result __vc_handle_msr(struct ghcb *ghcb, struct es_em_ctxt *ctxt, bool if (cc_platform_has(CC_ATTR_SNP_SECURE_AVIC)) return ES_VMM_ERROR; break; + case MSR_F15H_PERF_CTL0 ... MSR_F15H_PERF_CTR5: + /* Odd MSRs are the counters. */ + if (regs->cx & 1) + break; + fallthrough; + case MSR_K7_EVNTSEL0 ... MSR_K7_EVNTSEL3: + case MSR_AMD64_PERF_CNTR_GLOBAL_CTL: + if (write && cc_platform_has(CC_ATTR_GUEST_PMC_VIRT)) + return __vc_handle_pmc_msr(ghcb, ctxt); + break; default: break; } diff --git a/include/linux/cc_platform.h b/include/linux/cc_platform.h index 559353ad64acb..5b98a357f3e20 100644 --- a/include/linux/cc_platform.h +++ b/include/linux/cc_platform.h @@ -104,6 +104,14 @@ enum cc_attr { * to run SEV-SNP guests with full Secure AVIC capabilities. */ CC_ATTR_SNP_SECURE_AVIC, + + /** + * @CC_ATTR_GUEST_PMC_VIRT: SEV-ES/SNP PMC virtualization is active. + * + * The hypervisor filters PMC events in hardware, trapped event-select + * writes must be forwarded to it. + */ + CC_ATTR_GUEST_PMC_VIRT, }; #ifdef CONFIG_ARCH_HAS_CC_PLATFORM -- 2.43.0