blob: 5073b384cd6aea67f38e09f8f133df94864f5431 [file] [log] [blame]
#include "kvm/fdt.h"
#include "kvm/kvm.h"
#include "kvm/kvm-cpu.h"
#include "kvm/util.h"
#include "arm-common/gic.h"
#include "arm-common/pmu.h"
#ifdef CONFIG_ARM64
static int set_pmu_attr(struct kvm *kvm, int vcpu_idx,
struct kvm_device_attr *attr)
{
int ret, fd;
fd = kvm->cpus[vcpu_idx]->vcpu_fd;
ret = ioctl(fd, KVM_HAS_DEVICE_ATTR, attr);
if (!ret) {
ret = ioctl(fd, KVM_SET_DEVICE_ATTR, attr);
if (ret)
perror("PMU KVM_SET_DEVICE_ATTR failed");
} else {
pr_err("Unsupported PMU on vcpu%d\n", vcpu_idx);
}
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";
int irq = KVM_ARM_PMUv3_PPI;
int i, ret;
u32 cpu_mask = (((1 << kvm->nrcpus) - 1) << GIC_FDT_IRQ_PPI_CPU_SHIFT) \
& GIC_FDT_IRQ_PPI_CPU_MASK;
u32 irq_prop[] = {
cpu_to_fdt32(GIC_FDT_IRQ_TYPE_PPI),
cpu_to_fdt32(irq - 16),
cpu_to_fdt32(cpu_mask | IRQ_TYPE_LEVEL_HIGH),
};
if (!kvm->cfg.arch.has_pmuv3)
return;
if (!kvm__supports_extension(kvm, KVM_CAP_ARM_PMU_V3)) {
pr_info("PMU unsupported\n");
return;
}
for (i = 0; i < kvm->nrcpus; i++) {
struct kvm_device_attr pmu_attr;
pmu_attr = (struct kvm_device_attr){
.group = KVM_ARM_VCPU_PMU_V3_CTRL,
.addr = (u64)(unsigned long)&irq,
.attr = KVM_ARM_VCPU_PMU_V3_IRQ,
};
ret = set_pmu_attr(kvm, i, &pmu_attr);
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,
};
ret = set_pmu_attr(kvm, i, &pmu_attr);
if (ret)
return;
}
_FDT(fdt_begin_node(fdt, "pmu"));
_FDT(fdt_property(fdt, "compatible", compatible, sizeof(compatible)));
_FDT(fdt_property(fdt, "interrupts", irq_prop, sizeof(irq_prop)));
_FDT(fdt_end_node(fdt));
}
#else
void pmu__generate_fdt_nodes(void *fdt, struct kvm *kvm) { }
#endif