arm64: Add PMU event filtering option

In order to be able to let kvmtool specify which PMU events to
allow or deny, allow a --pmu-filter option to be passed on the
command line. The expected syntax is:

  --pmu-filter "{A,D}:start-end[,...]"

where A means "allow" and D means "deny", start is the first event
of the range and end the last one. For example:

  --pmu-filter "A:0x11-0x11,A:0x23-0x3a,D:0x30-0x30"

allows event 0x11 (the cycle counter), events 0x23 to 0x3a except
for event 0x30, and all the other events are disallowed.

Signed-off-by: Marc Zyngier <maz@kernel.org>
diff --git a/arm/aarch64/include/kvm/kvm-config-arch.h b/arm/aarch64/include/kvm/kvm-config-arch.h
index 04be43d..79ecb21 100644
--- a/arm/aarch64/include/kvm/kvm-config-arch.h
+++ b/arm/aarch64/include/kvm/kvm-config-arch.h
@@ -8,7 +8,9 @@
 			"Create PMUv3 device"),				\
 	OPT_U64('\0', "kaslr-seed", &(cfg)->kaslr_seed,			\
 			"Specify random seed for Kernel Address Space "	\
-			"Layout Randomization (KASLR)"),
+			"Layout Randomization (KASLR)"),		\
+	OPT_STRING('\0', "pmu-filter", &(cfg)->pmu_filter,		\
+		   "PMU filter desc", "PMU filter ({A,D}:start-end[,...])"),
 
 #include "arm-common/kvm-config-arch.h"
 
diff --git a/arm/include/arm-common/kvm-config-arch.h b/arm/include/arm-common/kvm-config-arch.h
index 5734c46..5f2cef3 100644
--- a/arm/include/arm-common/kvm-config-arch.h
+++ b/arm/include/arm-common/kvm-config-arch.h
@@ -9,6 +9,7 @@
 	bool		virtio_trans_pci;
 	bool		aarch32_guest;
 	bool		has_pmuv3;
+	const char	*pmu_filter;
 	u64		kaslr_seed;
 	enum irqchip_type irqchip;
 	u64		fw_addr;
diff --git a/arm/pmu.c b/arm/pmu.c
index ffd152e..5073b38 100644
--- a/arm/pmu.c
+++ b/arm/pmu.c
@@ -26,6 +26,75 @@
 	return ret;
 }
 
+#ifndef KVM_ARM_VCPU_PMU_V3_FILTER
+/*
+ * PMU filter structure. Describe a range of events with a particular
+ * action. To be used with KVM_ARM_VCPU_PMU_V3_FILTER.
+ */
+struct kvm_pmu_event_filter {
+	__u16	base_event;
+	__u16	nevents;
+
+#define KVM_PMU_EVENT_ALLOW	0
+#define KVM_PMU_EVENT_DENY	1
+
+	__u8	action;
+	__u8	pad[3];
+};
+
+#define KVM_ARM_VCPU_PMU_V3_FILTER	2
+#endif
+
+static void set_pmu_filter(struct kvm *kvm, int vcpu_idx)
+{
+	char *tmp, *ptr;
+
+	if (!kvm->cfg.arch.pmu_filter)
+		return;
+
+	if (vcpu_idx != 0)
+		return;
+
+	tmp = ptr = strdup(kvm->cfg.arch.pmu_filter);
+
+	while (1) {
+		struct kvm_pmu_event_filter filter;
+		struct kvm_device_attr pmu_attr;
+		unsigned short start = 0, end = 0;
+		char *str, act;
+		int ret;
+
+		str = strtok(tmp, ",");
+		tmp = NULL;
+
+		if (!str)
+			break;
+
+		sscanf(str, "%c:%hx-%hx", &act, &start, &end);
+		if ((act != 'A' && act != 'D') || (!start && !end)) {
+			pr_info("skipping invalid filter %s\n", str);
+			continue;
+		}
+
+		filter = (struct kvm_pmu_event_filter) {
+			.base_event	= start,
+			.nevents	= end - start + 1,
+			.action		= act == 'D',
+		};
+		pmu_attr = (struct kvm_device_attr) {
+			.group		= KVM_ARM_VCPU_PMU_V3_CTRL,
+			.addr		= (u64)(unsigned long)&filter,
+			.attr		= KVM_ARM_VCPU_PMU_V3_FILTER,
+		};
+
+		ret = set_pmu_attr(kvm, vcpu_idx, &pmu_attr);
+		if (ret)
+			pr_info("failed to add filter %s\n", str);
+	}
+
+	free(ptr);
+}
+
 void pmu__generate_fdt_nodes(void *fdt, struct kvm *kvm)
 {
 	const char compatible[] = "arm,armv8-pmuv3";
@@ -61,6 +130,8 @@
 		if (ret)
 			return;
 
+		set_pmu_filter(kvm, i);
+
 		pmu_attr = (struct kvm_device_attr){
 			.group	= KVM_ARM_VCPU_PMU_V3_CTRL,
 			.attr	= KVM_ARM_VCPU_PMU_V3_INIT,