KVM: arm64: Allow userspace to limit the number of PMU counters for EL2 VMs
authorMarc Zyngier <maz@kernel.org>
Wed, 9 Apr 2025 10:53:15 +0000 (11:53 +0100)
committerMarc Zyngier <maz@kernel.org>
Fri, 11 Apr 2025 12:08:23 +0000 (13:08 +0100)
As long as we had purely EL1 VMs, we could easily update the number
of guest-visible counters by letting userspace write to PMCR_EL0.N.

With VMs started at EL2, PMCR_EL1.N only reflects MDCR_EL2.HPMN,
and we don't have a good way to limit it.

For this purpose, introduce a new PMUv3 attribute that allows
limiting the maximum number of counters. This requires the explicit
selection of a PMU.

Suggested-by: Oliver Upton <oliver.upton@linux.dev>
Reviewed-by: Oliver Upton <oliver.upton@linux.dev>
Signed-off-by: Marc Zyngier <maz@kernel.org>
Documentation/virt/kvm/devices/vcpu.rst
arch/arm64/include/uapi/asm/kvm.h
arch/arm64/kvm/pmu-emul.c

index 31a9576c07afae4ba8e594313303732bfa797f11..60bf205cb3730c9d1c27f5cb72c920d1d886c9d9 100644 (file)
@@ -137,6 +137,30 @@ exit_reason = KVM_EXIT_FAIL_ENTRY and populate the fail_entry struct by setting
 hardare_entry_failure_reason field to KVM_EXIT_FAIL_ENTRY_CPU_UNSUPPORTED and
 the cpu field to the processor id.
 
+1.5 ATTRIBUTE: KVM_ARM_VCPU_PMU_V3_SET_NR_COUNTERS
+--------------------------------------------------
+
+:Parameters: in kvm_device_attr.addr the address to an unsigned int
+            representing the maximum value taken by PMCR_EL0.N
+
+:Returns:
+
+        =======  ====================================================
+        -EBUSY   PMUv3 already initialized, a VCPU has already run or
+                  an event filter has already been set
+        -EFAULT  Error accessing the value pointed to by addr
+        -ENODEV  PMUv3 not supported or GIC not initialized
+        -EINVAL  No PMUv3 explicitly selected, or value of N out of
+                 range
+        =======  ====================================================
+
+Set the number of implemented event counters in the virtual PMU. This
+mandates that a PMU has explicitly been selected via
+KVM_ARM_VCPU_PMU_V3_SET_PMU, and will fail when no PMU has been
+explicitly selected, or the number of counters is out of range for the
+selected PMU. Selecting a new PMU cancels the effect of setting this
+attribute.
+
 2. GROUP: KVM_ARM_VCPU_TIMER_CTRL
 =================================
 
index af9d9acaf9975a88438286e0e06555d4d540348e..ed5f3892674c7df421265514ebdb1b23e12f3e14 100644 (file)
@@ -431,10 +431,11 @@ enum {
 
 /* Device Control API on vcpu fd */
 #define KVM_ARM_VCPU_PMU_V3_CTRL       0
-#define   KVM_ARM_VCPU_PMU_V3_IRQ      0
-#define   KVM_ARM_VCPU_PMU_V3_INIT     1
-#define   KVM_ARM_VCPU_PMU_V3_FILTER   2
-#define   KVM_ARM_VCPU_PMU_V3_SET_PMU  3
+#define   KVM_ARM_VCPU_PMU_V3_IRQ              0
+#define   KVM_ARM_VCPU_PMU_V3_INIT             1
+#define   KVM_ARM_VCPU_PMU_V3_FILTER           2
+#define   KVM_ARM_VCPU_PMU_V3_SET_PMU          3
+#define   KVM_ARM_VCPU_PMU_V3_SET_NR_COUNTERS  4
 #define KVM_ARM_VCPU_TIMER_CTRL                1
 #define   KVM_ARM_VCPU_TIMER_IRQ_VTIMER                0
 #define   KVM_ARM_VCPU_TIMER_IRQ_PTIMER                1
index 2336d9c8bd5e7d1c240fb962154ed39d6005f446..efc59f343acc84bfee6b03565016b3ffb0aa75d6 100644 (file)
@@ -1104,6 +1104,20 @@ static int kvm_arm_pmu_v3_set_pmu(struct kvm_vcpu *vcpu, int pmu_id)
        return ret;
 }
 
+static int kvm_arm_pmu_v3_set_nr_counters(struct kvm_vcpu *vcpu, unsigned int n)
+{
+       struct kvm *kvm = vcpu->kvm;
+
+       if (!kvm->arch.arm_pmu)
+               return -EINVAL;
+
+       if (n > kvm_arm_pmu_get_max_counters(kvm))
+               return -EINVAL;
+
+       kvm_arm_set_nr_counters(kvm, n);
+       return 0;
+}
+
 int kvm_arm_pmu_v3_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
 {
        struct kvm *kvm = vcpu->kvm;
@@ -1200,6 +1214,15 @@ int kvm_arm_pmu_v3_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
 
                return kvm_arm_pmu_v3_set_pmu(vcpu, pmu_id);
        }
+       case KVM_ARM_VCPU_PMU_V3_SET_NR_COUNTERS: {
+               unsigned int __user *uaddr = (unsigned int __user *)(long)attr->addr;
+               unsigned int n;
+
+               if (get_user(n, uaddr))
+                       return -EFAULT;
+
+               return kvm_arm_pmu_v3_set_nr_counters(vcpu, n);
+       }
        case KVM_ARM_VCPU_PMU_V3_INIT:
                return kvm_arm_pmu_v3_init(vcpu);
        }
@@ -1238,6 +1261,7 @@ int kvm_arm_pmu_v3_has_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
        case KVM_ARM_VCPU_PMU_V3_INIT:
        case KVM_ARM_VCPU_PMU_V3_FILTER:
        case KVM_ARM_VCPU_PMU_V3_SET_PMU:
+       case KVM_ARM_VCPU_PMU_V3_SET_NR_COUNTERS:
                if (kvm_vcpu_has_pmu(vcpu))
                        return 0;
        }