@@ -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");
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@arm.com> --- arm/aarch64/spe.c | 130 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+)