[RFC PATCH v3 5/8] KVM: arm64: Introduce module param to partition the PMU
Colton Lewis
coltonlewis at google.com
Thu Feb 13 10:26:39 PST 2025
Colton Lewis <coltonlewis at google.com> writes:
> For PMUv3, the register MDCR_EL2.HPMN partitiones the PMU counters
> into two ranges where counters 0..HPMN-1 are accessible by EL1 and, if
> allowed, EL0 while counters HPMN..N are only accessible by EL2.
> Introduce a module parameter in KVM to set this register. The name
> reserved_host_counters reflects the intent to reserve some counters
> for the host so the guest may eventually be allowed direct access to a
> subset of PMU functionality for increased performance.
> Track HPMN and whether the pmu is partitioned in struct arm_pmu
> because both KVM and the PMUv3 driver will need to know that to handle
> guests correctly.
> Due to the difficulty this feature would create for the driver running
> at EL1 on the host, partitioning is only allowed in VHE mode. Working
> on nVHE mode would require a hypercall for every register access
> because the counters reserved for the host by HPMN are now only
> accessible to EL2.
> The parameter is only configurable at boot time. Making the parameter
> configurable on a running system is dangerous due to the difficulty of
> knowing for sure no counters are in use anywhere so it is safe to
> reporgram HPMN.
> Signed-off-by: Colton Lewis <coltonlewis at google.com>
> ---
> arch/arm64/include/asm/kvm_pmu.h | 4 +++
> arch/arm64/kvm/Makefile | 2 +-
> arch/arm64/kvm/debug.c | 9 ++++--
> arch/arm64/kvm/pmu-part.c | 47 ++++++++++++++++++++++++++++++++
> arch/arm64/kvm/pmu.c | 2 ++
> include/linux/perf/arm_pmu.h | 2 ++
> 6 files changed, 62 insertions(+), 4 deletions(-)
> create mode 100644 arch/arm64/kvm/pmu-part.c
> diff --git a/arch/arm64/include/asm/kvm_pmu.h
> b/arch/arm64/include/asm/kvm_pmu.h
> index 613cddbdbdd8..174b7f376d95 100644
> --- a/arch/arm64/include/asm/kvm_pmu.h
> +++ b/arch/arm64/include/asm/kvm_pmu.h
> @@ -22,6 +22,10 @@ bool kvm_set_pmuserenr(u64 val);
> void kvm_vcpu_pmu_resync_el0(void);
> void kvm_host_pmu_init(struct arm_pmu *pmu);
> +u8 kvm_pmu_get_reserved_counters(void);
> +u8 kvm_pmu_hpmn(u8 nr_counters);
> +void kvm_pmu_partition(struct arm_pmu *pmu);
> +
> #else
> static inline void kvm_set_pmu_events(u64 set, struct perf_event_attr
> *attr) {}
> diff --git a/arch/arm64/kvm/Makefile b/arch/arm64/kvm/Makefile
> index 3cf7adb2b503..065a6b804c84 100644
> --- a/arch/arm64/kvm/Makefile
> +++ b/arch/arm64/kvm/Makefile
> @@ -25,7 +25,7 @@ kvm-y += arm.o mmu.o mmio.o psci.o hypercalls.o
> pvtime.o \
> vgic/vgic-mmio-v3.o vgic/vgic-kvm-device.o \
> vgic/vgic-its.o vgic/vgic-debug.o
> -kvm-$(CONFIG_HW_PERF_EVENTS) += pmu-emul.o pmu.o
> +kvm-$(CONFIG_HW_PERF_EVENTS) += pmu-emul.o pmu-part.o pmu.o
> kvm-$(CONFIG_ARM64_PTR_AUTH) += pauth.o
> kvm-$(CONFIG_PTDUMP_STAGE2_DEBUGFS) += ptdump.o
> diff --git a/arch/arm64/kvm/debug.c b/arch/arm64/kvm/debug.c
> index 7fb1d9e7180f..b5ac5a213877 100644
> --- a/arch/arm64/kvm/debug.c
> +++ b/arch/arm64/kvm/debug.c
> @@ -31,15 +31,18 @@
> */
> static void kvm_arm_setup_mdcr_el2(struct kvm_vcpu *vcpu)
> {
> + u8 counters = *host_data_ptr(nr_event_counters);
> + u8 hpmn = kvm_pmu_hpmn(counters);
> +
> preempt_disable();
> /*
> * This also clears MDCR_EL2_E2PB_MASK and MDCR_EL2_E2TB_MASK
> * to disable guest access to the profiling and trace buffers
> */
> - vcpu->arch.mdcr_el2 = FIELD_PREP(MDCR_EL2_HPMN,
> - *host_data_ptr(nr_event_counters));
> - vcpu->arch.mdcr_el2 |= (MDCR_EL2_TPM |
> + vcpu->arch.mdcr_el2 = FIELD_PREP(MDCR_EL2_HPMN, hpmn);
> + vcpu->arch.mdcr_el2 |= (MDCR_EL2_HPMD |
> + MDCR_EL2_TPM |
> MDCR_EL2_TPMS |
> MDCR_EL2_TTRF |
> MDCR_EL2_TPMCR |
> diff --git a/arch/arm64/kvm/pmu-part.c b/arch/arm64/kvm/pmu-part.c
> new file mode 100644
> index 000000000000..e74fecc67e37
> --- /dev/null
> +++ b/arch/arm64/kvm/pmu-part.c
> @@ -0,0 +1,47 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2025 Google LLC
> + * Author: Colton Lewis <coltonlewis at google.com>
> + */
> +
> +#include <linux/kvm_host.h>
> +#include <linux/perf/arm_pmu.h>
> +
> +#include <asm/kvm_pmu.h>
> +
> +static u8 reserved_host_counters __read_mostly;
> +
> +module_param(reserved_host_counters, byte, 0);
> +MODULE_PARM_DESC(reserved_host_counters,
> + "Partition the PMU into host and guest counters");
> +
> +u8 kvm_pmu_get_reserved_counters(void)
> +{
> + return reserved_host_counters;
> +}
> +
> +u8 kvm_pmu_hpmn(u8 nr_counters)
> +{
> + if (reserved_host_counters >= nr_counters) {
> + if (this_cpu_has_cap(ARM64_HAS_HPMN0))
> + return 0;
> +
> + return 1;
> + }
> +
> + return nr_counters - reserved_host_counters;
> +}
> +
> +void kvm_pmu_partition(struct arm_pmu *pmu)
> +{
> + u8 nr_counters = *host_data_ptr(nr_event_counters);
> + u8 hpmn = kvm_pmu_hpmn(nr_counters);
> +
> + if (hpmn < nr_counters) {
> + pmu->hpmn = hpmn;
> + pmu->partitioned = true;
> + } else {
> + pmu->hpmn = nr_counters;
> + pmu->partitioned = false;
> + }
> +}
There should be a VHE check in here. I thought I wouldn't need it with
moving MDCR_EL2 writes out of the driver but I just remembered there are
two spots in patch 7 I still need to write that register.
> diff --git a/arch/arm64/kvm/pmu.c b/arch/arm64/kvm/pmu.c
> index 85b5cb432c4f..7169c1a24dd6 100644
> --- a/arch/arm64/kvm/pmu.c
> +++ b/arch/arm64/kvm/pmu.c
> @@ -243,6 +243,8 @@ void kvm_host_pmu_init(struct arm_pmu *pmu)
> entry->arm_pmu = pmu;
> list_add_tail(&entry->entry, &arm_pmus);
> + kvm_pmu_partition(pmu);
> +
> if (list_is_singular(&arm_pmus))
> static_branch_enable(&kvm_arm_pmu_available);
> diff --git a/include/linux/perf/arm_pmu.h b/include/linux/perf/arm_pmu.h
> index 35c3a85bee43..ee4fc2e26bff 100644
> --- a/include/linux/perf/arm_pmu.h
> +++ b/include/linux/perf/arm_pmu.h
> @@ -125,6 +125,8 @@ struct arm_pmu {
> /* Only to be used by ACPI probing code */
> unsigned long acpi_cpuid;
> + u8 hpmn; /* MDCR_EL2.HPMN: counter partition pivot */
> + bool partitioned;
> };
> #define to_arm_pmu(p) (container_of(p, struct arm_pmu, pmu))
> --
> 2.48.1.601.g30ceb7b040-goog
More information about the linux-arm-kernel
mailing list