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,