KVM uses the KVM_ARM_VCPU_SUPPORTED_CPUS to make sure that an SPE-enabled VCPU is not scheduled on a CPU without SPE. Get the cpulist of physical CPUs that support SPE by parsing the /sys/devices directories that the SPE driver creates, and passing that on as the argument for KVM_ARM_VCPU_SUPPORTED_CPUS. It is still up to the user to make sure that the VCPUs run on the correct physical CPUs (those specified via KVM_ARM_VCPU_SUPPORTED_CPUS), for example, by using taskset. Signed-off-by: Alexandru Elisei <alexandru.elisei@xxxxxxx> --- arm/aarch64/spe.c | 130 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/arm/aarch64/spe.c b/arm/aarch64/spe.c index 673c84e63cc2..6eccc6b72677 100644 --- a/arm/aarch64/spe.c +++ b/arm/aarch64/spe.c @@ -1,3 +1,4 @@ +#include <dirent.h> #include <stdio.h> #include <sys/resource.h> @@ -13,6 +14,8 @@ #include "arm-common/gic.h" +#define SYS_DEVICES "/sys/devices/" + void spe__generate_fdt_nodes(void *fdt, struct kvm *kvm) { const char compatible[] = "arm,statistical-profiling-extension-v1"; @@ -35,6 +38,118 @@ void spe__generate_fdt_nodes(void *fdt, struct kvm *kvm) _FDT(fdt_end_node(fdt)); } +static int spe_set_supported_cpumask(char *cpumask) +{ + struct dirent *dirent; + size_t cpumask_len; + char *path; + size_t path_len; + DIR *dir; + int fd; + ssize_t fd_sz; + int ret = 0; + + path = calloc(1, PAGE_SIZE); + if (!path) + return -ENOMEM; + + /* Make the compiler happy by copying the NULL terminating byte. */ + strncpy(path, SYS_DEVICES, strlen(SYS_DEVICES) + 1); + + dir = opendir(SYS_DEVICES); + if (!dir) { + ret = -errno; + goto out_free; + } + + cpumask_len = 0; + while ((dirent = readdir(dir))) { + if (dirent->d_type != DT_DIR) + continue; + if (strncmp(dirent->d_name, "arm_spe", 7) != 0) + continue; + + path_len = strlen(SYS_DEVICES) + strlen(dirent->d_name) + + strlen("/cpumask"); + /* No room for NULL. */ + if (path_len >= (long unsigned)PAGE_SIZE) { + ret = -ENOMEM; + goto out_free; + } + + strcat(path, dirent->d_name); + strcat(path, "/cpumask"); + + fd = open(path, O_RDONLY); + if (fd < 0) { + ret = -errno; + goto out_free; + } + + /* No room for comma + single digit CPU number. */ + if (cpumask_len >= (long unsigned)PAGE_SIZE - 2) { + ret = -ENOMEM; + goto out_free; + } + if (cpumask_len > 0) + cpumask[cpumask_len++] = ','; + + /* Newline will be converted to NULL, it's safe to fill cpumask. */ + fd_sz = read_file(fd, &cpumask[cpumask_len], + PAGE_SIZE - cpumask_len); + if (fd_sz < 0) { + ret = -errno; + goto out_free; + } + close(fd); + + cpumask_len = strlen(cpumask); + /* Strip newline. */ + cpumask[--cpumask_len] = '\0'; + + /* Reset path to point to /sys/devices/ */ + memset(&path[strlen(SYS_DEVICES)], '\0', + strlen(path) - strlen(SYS_DEVICES)); + } + + if (cpumask_len == 0) + ret = -ENODEV; + +out_free: + free(path); + return ret; +} + +static int spe_set_supported_cpus(struct kvm *kvm) +{ + char *cpumask; + int i, fd; + int ret; + + cpumask = calloc(1, PAGE_SIZE); + if (!cpumask) + return -ENOMEM; + + ret = spe_set_supported_cpumask(cpumask); + if (ret) + goto out_free; + + pr_info("SPE detected on CPUs %s", cpumask); + + for (i = 0; i < kvm->nrcpus; i++) { + fd = kvm->cpus[i]->vcpu_fd; + ret = ioctl(fd, KVM_ARM_VCPU_SUPPORTED_CPUS, cpumask); + if (ret == -1) { + ret = -errno; + goto out_free; + } + } + +out_free: + free(cpumask); + return ret; +} + static void spe_try_increase_mlock_limit(struct kvm *kvm) { u64 size = kvm->ram_size; @@ -88,6 +203,21 @@ static int spe__init(struct kvm *kvm) if (!kvm->cfg.arch.has_spe) return 0; + if (kvm__supports_extension(kvm, KVM_CAP_ARM_VCPU_SUPPORTED_CPUS)) { + ret = spe_set_supported_cpus(kvm); + if (ret) + return ret; + } else { + /* + * Assume that KVM knows what it's doing by not supporting the + * extension and has some other way to prevent SPE enabled VCPUs + * from running on physical CPUs without SPE, if there are + * present. Print a debug statement just in case something goes + * horribly wrong. + */ + pr_debug("KVM_ARM_VCPU_SUPPORTED_CPUS not present"); + } + if (!kvm__supports_extension(kvm, KVM_CAP_ARM_LOCK_USER_MEMORY_REGION)) die("KVM_CAP_ARM_LOCK_USER_MEMORY_REGION not supported"); -- 2.33.0