Message ID | 20200214183615.25498-2-maz@kernel.org (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | KVM: arm64: Filtering PMU events | expand |
Hi Marc, On 2020-02-14 6:36 pm, Marc Zyngier wrote: [...] > @@ -585,6 +585,14 @@ static void kvm_pmu_create_perf_event(struct kvm_vcpu *vcpu, u64 select_idx) > pmc->idx != ARMV8_PMU_CYCLE_IDX) > return; > > + /* > + * If we have a filter in place and that the event isn't allowed, do > + * not install a perf event either. > + */ > + if (vcpu->kvm->arch.pmu_filter && > + !test_bit(eventsel, vcpu->kvm->arch.pmu_filter)) > + return; If I'm reading the derivation of eventsel right, this will end up treating cycle counter events (aliased to SW_INCR) differently from CPU_CYCLES, which doesn't seem desirable. Also, if the user did try to blacklist SW_INCR for ridiculous reasons, we'd need to special-case kvm_pmu_software_increment() to make it (not) work as expected, right? Robin. > + > memset(&attr, 0, sizeof(struct perf_event_attr)); > attr.type = PERF_TYPE_RAW; > attr.size = sizeof(attr);
On Fri, 14 Feb 2020 22:01:01 +0000, Robin Murphy <robin.murphy@arm.com> wrote: Hi Robin, > > Hi Marc, > > On 2020-02-14 6:36 pm, Marc Zyngier wrote: > [...] > > @@ -585,6 +585,14 @@ static void kvm_pmu_create_perf_event(struct kvm_vcpu *vcpu, u64 select_idx) > > pmc->idx != ARMV8_PMU_CYCLE_IDX) > > return; > > + /* > > + * If we have a filter in place and that the event isn't allowed, do > > + * not install a perf event either. > > + */ > > + if (vcpu->kvm->arch.pmu_filter && > > + !test_bit(eventsel, vcpu->kvm->arch.pmu_filter)) > > + return; > > If I'm reading the derivation of eventsel right, this will end up > treating cycle counter events (aliased to SW_INCR) differently from > CPU_CYCLES, which doesn't seem desirable. Indeed, this doesn't look quite right. Looking at the description of event 0x11, it doesn't seem to count exactly like the cycle counter (there are a number of PMCR controls affecting it). But none of these actually apply to our PMU emulation (no secure mode, and the idea of dealing with virtual EL2 in the context of the PMU is... not appealing). Now, given that we implement the cycle counter with event 0x11 anyway, I don't think there is any reason to deal with them separately. > Also, if the user did try to blacklist SW_INCR for ridiculous > reasons, we'd need to special-case kvm_pmu_software_increment() to > make it (not) work as expected, right? I thought of that one, and couldn't see a reason to blacklist it (after all, the guest could also increment a variable) and send itself an interrupt. I'm tempted to simply document that event 0 is never filtered. Thanks, M.
On 15/02/2020 10:28 am, Marc Zyngier wrote: > On Fri, 14 Feb 2020 22:01:01 +0000, > Robin Murphy <robin.murphy@arm.com> wrote: > > Hi Robin, > >> >> Hi Marc, >> >> On 2020-02-14 6:36 pm, Marc Zyngier wrote: >> [...] >>> @@ -585,6 +585,14 @@ static void kvm_pmu_create_perf_event(struct kvm_vcpu *vcpu, u64 select_idx) >>> pmc->idx != ARMV8_PMU_CYCLE_IDX) >>> return; >>> + /* >>> + * If we have a filter in place and that the event isn't allowed, do >>> + * not install a perf event either. >>> + */ >>> + if (vcpu->kvm->arch.pmu_filter && >>> + !test_bit(eventsel, vcpu->kvm->arch.pmu_filter)) >>> + return; >> >> If I'm reading the derivation of eventsel right, this will end up >> treating cycle counter events (aliased to SW_INCR) differently from >> CPU_CYCLES, which doesn't seem desirable. > > Indeed, this doesn't look quite right. > > Looking at the description of event 0x11, it doesn't seem to count > exactly like the cycle counter (there are a number of PMCR controls > affecting it). But none of these actually apply to our PMU emulation > (no secure mode, and the idea of dealing with virtual EL2 in the > context of the PMU is... not appealing). > > Now, given that we implement the cycle counter with event 0x11 anyway, > I don't think there is any reason to deal with them separately. Right, from the user's PoV they can only ask for event 0x11, and where it gets scheduled is more of a black-box implementation detail. Reading the Arm ARM doesn't leave me entirely convinced that cycles couldn't ever leak idle/not-idle information between closely-coupled PEs, so this might not be entirely academic. >> Also, if the user did try to blacklist SW_INCR for ridiculous >> reasons, we'd need to special-case kvm_pmu_software_increment() to >> make it (not) work as expected, right? > > I thought of that one, and couldn't see a reason to blacklist it > (after all, the guest could also increment a variable) and send itself > an interrupt. I'm tempted to simply document that event 0 is never > filtered. I'd say you're on even stronger ground simply because KVM's implementation of SW_INCR doesn't go near the PMU hardware at all, thus is well beyond the purpose of the blacklist anyway. I believe it's important that how the code behaves matches expectations, but there's no harm in changing the latter as appropriate ;) Cheers, Robin.
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h index d87aa609d2b6..b594c604215f 100644 --- a/arch/arm64/include/asm/kvm_host.h +++ b/arch/arm64/include/asm/kvm_host.h @@ -91,6 +91,12 @@ struct kvm_arch { * supported. */ bool return_nisv_io_abort_to_user; + + /* + * VM-wide PMU filter, implemented as a bitmap and big enough + * for up to 65536 events + */ + unsigned long *pmu_filter; }; #define KVM_NR_MEM_OBJS 40 diff --git a/arch/arm64/include/uapi/asm/kvm.h b/arch/arm64/include/uapi/asm/kvm.h index ba85bb23f060..7b1511d6ce44 100644 --- a/arch/arm64/include/uapi/asm/kvm.h +++ b/arch/arm64/include/uapi/asm/kvm.h @@ -159,6 +159,21 @@ struct kvm_sync_regs { struct kvm_arch_memory_slot { }; +/* + * 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]; +}; + /* for KVM_GET/SET_VCPU_EVENTS */ struct kvm_vcpu_events { struct { @@ -329,6 +344,7 @@ struct kvm_vcpu_events { #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_TIMER_CTRL 1 #define KVM_ARM_VCPU_TIMER_IRQ_VTIMER 0 #define KVM_ARM_VCPU_TIMER_IRQ_PTIMER 1 diff --git a/virt/kvm/arm/arm.c b/virt/kvm/arm/arm.c index d65a0faa46d8..9132f7508975 100644 --- a/virt/kvm/arm/arm.c +++ b/virt/kvm/arm/arm.c @@ -164,6 +164,8 @@ void kvm_arch_destroy_vm(struct kvm *kvm) free_percpu(kvm->arch.last_vcpu_ran); kvm->arch.last_vcpu_ran = NULL; + bitmap_free(kvm->arch.pmu_filter); + for (i = 0; i < KVM_MAX_VCPUS; ++i) { if (kvm->vcpus[i]) { kvm_vcpu_destroy(kvm->vcpus[i]); diff --git a/virt/kvm/arm/pmu.c b/virt/kvm/arm/pmu.c index f0d0312c0a55..817f341ed9ad 100644 --- a/virt/kvm/arm/pmu.c +++ b/virt/kvm/arm/pmu.c @@ -585,6 +585,14 @@ static void kvm_pmu_create_perf_event(struct kvm_vcpu *vcpu, u64 select_idx) pmc->idx != ARMV8_PMU_CYCLE_IDX) return; + /* + * If we have a filter in place and that the event isn't allowed, do + * not install a perf event either. + */ + if (vcpu->kvm->arch.pmu_filter && + !test_bit(eventsel, vcpu->kvm->arch.pmu_filter)) + return; + memset(&attr, 0, sizeof(struct perf_event_attr)); attr.type = PERF_TYPE_RAW; attr.size = sizeof(attr); @@ -735,15 +743,6 @@ int kvm_arm_pmu_v3_enable(struct kvm_vcpu *vcpu) static int kvm_arm_pmu_v3_init(struct kvm_vcpu *vcpu) { - if (!kvm_arm_support_pmu_v3()) - return -ENODEV; - - if (!test_bit(KVM_ARM_VCPU_PMU_V3, vcpu->arch.features)) - return -ENXIO; - - if (vcpu->arch.pmu.created) - return -EBUSY; - if (irqchip_in_kernel(vcpu->kvm)) { int ret; @@ -794,8 +793,19 @@ static bool pmu_irq_is_valid(struct kvm *kvm, int irq) return true; } +#define NR_EVENTS (ARMV8_PMU_EVTYPE_EVENT + 1) + int kvm_arm_pmu_v3_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr) { + if (!kvm_arm_support_pmu_v3()) + return -ENODEV; + + if (!test_bit(KVM_ARM_VCPU_PMU_V3, vcpu->arch.features)) + return -ENODEV; + + if (vcpu->arch.pmu.created) + return -EBUSY; + switch (attr->attr) { case KVM_ARM_VCPU_PMU_V3_IRQ: { int __user *uaddr = (int __user *)(long)attr->addr; @@ -804,9 +814,6 @@ int kvm_arm_pmu_v3_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr) if (!irqchip_in_kernel(vcpu->kvm)) return -EINVAL; - if (!test_bit(KVM_ARM_VCPU_PMU_V3, vcpu->arch.features)) - return -ENODEV; - if (get_user(irq, uaddr)) return -EFAULT; @@ -824,6 +831,50 @@ int kvm_arm_pmu_v3_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr) vcpu->arch.pmu.irq_num = irq; return 0; } + case KVM_ARM_VCPU_PMU_V3_FILTER: { + struct kvm_pmu_event_filter __user *uaddr; + struct kvm_pmu_event_filter filter; + + uaddr = (struct kvm_pmu_event_filter __user *)(long)attr->addr; + + if (copy_from_user(&filter, uaddr, sizeof(filter))) + return -EFAULT; + + if (((u32)filter.base_event + filter.nevents) > NR_EVENTS || + (filter.action != KVM_PMU_EVENT_ALLOW && + filter.action != KVM_PMU_EVENT_DENY)) + return -EINVAL; + + mutex_lock(&vcpu->kvm->lock); + + if (!vcpu->kvm->arch.pmu_filter) { + vcpu->kvm->arch.pmu_filter = bitmap_alloc(NR_EVENTS, GFP_KERNEL); + if (!vcpu->kvm->arch.pmu_filter) { + mutex_unlock(&vcpu->kvm->lock); + return -ENOMEM; + } + + /* + * The default depends on the first applied filter. + * If it allows events, the default is to deny. + * Conversely, if the first filter denies a set of + * events, the default is to allow. + */ + if (filter.action == KVM_PMU_EVENT_ALLOW) + bitmap_zero(vcpu->kvm->arch.pmu_filter, NR_EVENTS); + else + bitmap_fill(vcpu->kvm->arch.pmu_filter, NR_EVENTS); + } + + if (filter.action == KVM_PMU_EVENT_ALLOW) + bitmap_set(vcpu->kvm->arch.pmu_filter, filter.base_event, filter.nevents); + else + bitmap_clear(vcpu->kvm->arch.pmu_filter, filter.base_event, filter.nevents); + + mutex_unlock(&vcpu->kvm->lock); + + return 0; + } case KVM_ARM_VCPU_PMU_V3_INIT: return kvm_arm_pmu_v3_init(vcpu); } @@ -860,6 +911,7 @@ int kvm_arm_pmu_v3_has_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr) switch (attr->attr) { case KVM_ARM_VCPU_PMU_V3_IRQ: case KVM_ARM_VCPU_PMU_V3_INIT: + case KVM_ARM_VCPU_PMU_V3_FILTER: if (kvm_arm_support_pmu_v3() && test_bit(KVM_ARM_VCPU_PMU_V3, vcpu->arch.features)) return 0;
It can be desirable to expose a PMU to a guest, and yet not want the guest to be able to count some of the implemented events (because this would give information on shared resources, for example. For this, let's extend the PMUv3 device API, and offer a way to setup a bitmap of the allowed events (the default being no bitmap, and thus no filtering). Userspace can thus allow/deny ranges of event. The default policy depends on the "polarity" of the first filter setup (default deny if the filter allows events, and default allow if the filter denies events). This allows to setup exactly what is allowed for a given guest. Note that although the ioctl is per-vcpu, the map of allowed events is global to the VM (it can be setup from any vcpu until the vcpu PMU is initialized). Signed-off-by: Marc Zyngier <maz@kernel.org> --- arch/arm64/include/asm/kvm_host.h | 6 +++ arch/arm64/include/uapi/asm/kvm.h | 16 +++++++ virt/kvm/arm/arm.c | 2 + virt/kvm/arm/pmu.c | 76 ++++++++++++++++++++++++++----- 4 files changed, 88 insertions(+), 12 deletions(-)