**coltonlewis.name: Partitioned PMU Part 6 [Org] All L1 (Kernel Hacker Mode) ---

Partitioned PMU Part 6

KVM ioctls

The hard part is finished with the IRQs handled. The work remaining is making the controllable by userspace. Since the partitioned PMU is something that works on a per-VCPU basis, all that is required is to introduce a VCPU ioctl that enables a new boolean. Call the boolean something like partition_enable and tie all the partitioning logic to only trigger if it is true.

The ioctl is fairly simple to add. You add some stuff include/uapi/linux/kvm.h:

#define KVM_CAP_ARM_PARTITION_PMU 244
...
#define KVM_ARM_PARTITION_PMU	_IOWR(KVMIO, 0xce, bool)

Then you add a case in the big switch statement in arch/arm64/kvm/arm.c in function kvm_arch_vcpu_ioctl

case KVM_ARM_PARTITION_PMU: {
	bool enable;
	if (unlikely(!kvm_vcpu_initialized(vcpu)))
		return -ENOEXEC;

	if (!kvm_pmu_is_partitioned(vcpu->kvm->arch.arm_pmu))
		return -EPERM;

	if (copy_from_user(&enable, argp, sizeof(enable)))
		return -EFAULT;

	vcpu->kvm->arch.partitioned_pmu_enable = enable;
	return 0;
}

And you add a capability definition to check that KVM supports this new feature in kvm_vm_ioctl_check_extension.

#define KVM_CAP_ARM_PARTITION_PMU 244
...
case KVM_CAP_ARM_PARTITION_PMU:
	r = kvm_pmu_partition_supported();
	break;

Lastly, update the documentation at Documentation/virt/kvm/api.rst

Selftests

Finally, the new feature needs some tests. There is an existing ARM64 PMU selftest called vpmu_counter_access. It's a simple test that creates a minimal VM and accesses all the PMU registers, making sure KVM behaves like the underlying ARM architecture says it should.

So what I did was create an enum for the two PMU implementations which is passed into all the test functions. If we are testing the partitioned PMU, call the right ioctl to enable it. Otherwise disable it. This means running the existing tests twice, and it works fine.

The core of the test was basically

pmcr_n = get_pmcr_n_limit();
for (i = 0; i <= pmcr_n; i++) {
	run_access_test(i);
	run_pmregs_validity_test(i);
}

for (i = pmcr_n + 1; i < ARMV8_PMU_MAX_COUNTERS; i++)
	run_error_test(i);

And I generalized it with a parameter.