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 <[email protected]>
---
 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

_______________________________________________
kvmarm mailing list
[email protected]
https://lists.cs.columbia.edu/mailman/listinfo/kvmarm

Reply via email to