| #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 |