blob: 5ed4979a8cac8b0975cd2354bc920660785e0a6e [file] [log] [blame]
#include <dirent.h>
#include <sched.h>
#include "linux/cpumask.h"
#include "linux/err.h"
#include "kvm/fdt.h"
#include "kvm/kvm.h"
#include "kvm/kvm-cpu.h"
#include "kvm/util.h"
#include "arm-common/gic.h"
#include "asm/pmu.h"
static bool pmu_has_attr(struct kvm_cpu *vcpu, u64 attr)
{
struct kvm_device_attr pmu_attr = {
.group = KVM_ARM_VCPU_PMU_V3_CTRL,
.attr = attr,
};
int ret = ioctl(vcpu->vcpu_fd, KVM_HAS_DEVICE_ATTR, &pmu_attr);
return ret == 0;
}
static void set_pmu_attr(struct kvm_cpu *vcpu, void *addr, u64 attr)
{
struct kvm_device_attr pmu_attr = {
.group = KVM_ARM_VCPU_PMU_V3_CTRL,
.addr = (u64)addr,
.attr = attr,
};
int ret;
if (pmu_has_attr(vcpu, attr)) {
ret = ioctl(vcpu->vcpu_fd, KVM_SET_DEVICE_ATTR, &pmu_attr);
if (ret)
die_perror("PMU KVM_SET_DEVICE_ATTR");
} else {
die_perror("PMU KVM_HAS_DEVICE_ATTR");
}
}
#define SYS_EVENT_SOURCE "/sys/bus/event_source/devices/"
/*
* int is 32 bits and INT_MAX translates in decimal to 2 * 10^9.
* Make room for newline and NUL.
*/
#define PMU_ID_MAXLEN 12
static int find_pmu_cpumask(struct kvm *kvm, cpumask_t *cpumask)
{
cpumask_t pmu_cpumask, tmp;
char buf[PMU_ID_MAXLEN];
struct dirent *dirent;
char *cpulist, *path;
int pmu_id = -ENXIO;
unsigned long val;
ssize_t fd_sz;
int fd, ret;
DIR *dir;
memset(buf, 0, sizeof(buf));
cpulist = calloc(1, PAGE_SIZE);
if (!cpulist)
die_perror("calloc");
path = calloc(1, PAGE_SIZE);
if (!path)
die_perror("calloc");
dir = opendir(SYS_EVENT_SOURCE);
if (!dir) {
pmu_id = -errno;
goto out_free;
}
/* Make the compiler happy by copying the NUL terminating byte. */
strncpy(path, SYS_EVENT_SOURCE, strlen(SYS_EVENT_SOURCE) + 1);
while ((dirent = readdir(dir))) {
if (dirent->d_type != DT_LNK)
continue;
strcat(path, dirent->d_name);
strcat(path, "/cpus");
fd = open(path, O_RDONLY);
if (fd < 0)
goto next_dir;
fd_sz = read_file(fd, cpulist, PAGE_SIZE);
if (fd_sz < 0) {
pmu_id = -errno;
goto out_free;
}
close(fd);
ret = cpulist_parse(cpulist, &pmu_cpumask);
if (ret) {
pmu_id = ret;
goto out_free;
}
if (!cpumask_and(&tmp, cpumask, &pmu_cpumask))
goto next_dir;
/*
* One CPU cannot more than one PMU, hence the set of CPUs which
* share PMU A and the set of CPUs which share PMU B are
* disjoint. If the target CPUs and the current PMU have at
* least one CPU in common, but the target CPUs is not a subset
* of the current PMU, then a PMU which is associated with all
* the target CPUs does not exist. Stop searching for a PMU when
* this happens.
*/
if (!cpumask_subset(cpumask, &pmu_cpumask))
goto out_free;
strcpy(&path[strlen(path) - 4], "type");
fd = open(path, O_RDONLY);
if (fd < 0)
goto next_dir;
fd_sz = read_file(fd, buf, PMU_ID_MAXLEN - 1);
if (fd_sz < 0) {
pmu_id = -errno;
goto out_free;
}
close(fd);
val = strtoul(buf, NULL, 10);
if (val > INT_MAX) {
pmu_id = -EOVERFLOW;
goto out_free;
}
pmu_id = (int)val;
pr_debug("Using PMU %s (id %d)", dirent->d_name, pmu_id);
break;
next_dir:
/* Reset path. */
memset(&path[strlen(SYS_EVENT_SOURCE)], '\0',
strlen(path) - strlen(SYS_EVENT_SOURCE));
}
out_free:
free(path);
free(cpulist);
return pmu_id;
}
/*
* In the case of homogeneous systems, there only one hardware PMU, and all
* VCPUs will use the same PMU, regardless of the physical CPUs on which the
* VCPU threads will be executing.
*
* For heterogeneous systems, there are 2 ways for the user to ensure that the
* VM runs on CPUs that have the same PMU:
*
* 1. By pinning the entire VM to the desired CPUs, in which case kvmtool will
* choose the PMU associated with the CPU on which the main thread is executing
* (the thread that calls find_pmu()).
*
* 2. By setting the affinity mask for the VCPUs with the --vcpu-affinity
* command line argument. All CPUs in the affinity mask must have the same PMU,
* otherwise kvmtool will not be able to set a PMU.
*/
static int find_pmu(struct kvm *kvm)
{
cpumask_t *cpumask;
int i, this_cpu;
cpumask = calloc(1, cpumask_size());
if (!cpumask)
die_perror("calloc");
if (!kvm->arch.vcpu_affinity_cpuset) {
this_cpu = sched_getcpu();
if (this_cpu < 0)
return -errno;
cpumask_set_cpu(this_cpu, cpumask);
} else {
for (i = 0; i < CPU_SETSIZE; i ++) {
if (CPU_ISSET(i, kvm->arch.vcpu_affinity_cpuset))
cpumask_set_cpu(i, cpumask);
}
}
return find_pmu_cpumask(kvm, cpumask);
}
void pmu__generate_fdt_nodes(void *fdt, struct kvm *kvm)
{
const char compatible[] = "arm,armv8-pmuv3";
int irq = KVM_ARM_PMUv3_PPI;
struct kvm_cpu *vcpu;
int pmu_id = -ENXIO;
int i;
u32 cpu_mask = gic__get_fdt_irq_cpumask(kvm);
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 (pmu_has_attr(kvm->cpus[0], KVM_ARM_VCPU_PMU_V3_SET_PMU)) {
pmu_id = find_pmu(kvm);
if (pmu_id < 0) {
pr_debug("Failed to find a PMU (errno: %d), "
"PMU events might not work", -pmu_id);
}
}
for (i = 0; i < kvm->nrcpus; i++) {
vcpu = kvm->cpus[i];
set_pmu_attr(vcpu, &irq, KVM_ARM_VCPU_PMU_V3_IRQ);
/*
* PMU IDs 0-5 are reserved; a positive value means a PMU was
* found.
*/
if (pmu_id > 0)
set_pmu_attr(vcpu, &pmu_id, KVM_ARM_VCPU_PMU_V3_SET_PMU);
set_pmu_attr(vcpu, NULL, KVM_ARM_VCPU_PMU_V3_INIT);
}
_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));
}