kvmtest-spci: Add example using the SPCI apis
Signed-off-by: Andrew Scull <ascull@google.com>
diff --git a/kvmtest/kvmtest-spci.README b/kvmtest/kvmtest-spci.README
new file mode 100644
index 0000000..eb58ebb
--- /dev/null
+++ b/kvmtest/kvmtest-spci.README
@@ -0,0 +1,39 @@
+Running with qemu
+-----------------
+
+Set up the FDT with something like:
+
+ reserved-memory {
+ spci_reserved0: spci_mem@e0000000 {
+ compatible = "arm,spci-0.9-partition-memory-region";
+ reg = <0x00000000 0xe0000000 0 0x04000000>;
+ ipa-range = <0x0 0x0 0 0x04000000>;
+ read-only;
+ };
+ }
+
+ spci {
+ compatible = "arm,spci-0.9-hypervisor";
+ memory-region = <&spci_hyp_reserved>;
+
+ spci_partition0: spci-partition {
+ compatible = "arm,spci-0.9-partition";
+ uuid = "58758040-4f9c-42b7-b6ac-e30c62c9ee96";
+ nr-exec-ctxs = <2>;
+ exec-state = "AArch64";
+ entry-point = <0x0 0x80000>;
+ memory-region = <&spci_reserved0>;
+ };
+ };
+
+Build something to go in the partition. There a basic example in `vms/`. Have
+qemu load that into memory at the address used in the FDT.
+
+ (cd vms; make)
+ qemu <other args> -device loader,file=vms/brk.bin,addr=0xe0080000,force-raw=on
+
+On the target system
+--------------------
+
+ gcc kvmtest-spci.c -o kvmtest-spci
+ ./kvmtest-spci.sh
diff --git a/kvmtest/kvmtest-spci.c b/kvmtest/kvmtest-spci.c
new file mode 100644
index 0000000..47b6726
--- /dev/null
+++ b/kvmtest/kvmtest-spci.c
@@ -0,0 +1,216 @@
+/*
+ * This is an Arm64 SPCI port of the x86 code accompanying "Using the KVM API"
+ * (https://lwn.net/Articles/658511/).
+ *
+ * Original x86 code in the file kvmtest.c and https://lwn.net/Articles/658512/.
+ *
+ * Copyright (C) 2020 Google LLC
+ * Author: Andrew Scull <ascull@google.com>
+ */
+
+/* Sample code for /dev/kvm API
+ *
+ * Copyright (c) 2015 Intel Corporation
+ * Author: Josh Triplett <josh@joshtriplett.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include <err.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <linux/kvm.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#define KVM_CAP_ARM_SPCI 182
+#define KVM_VM_TYPE_ARM_SPCI_ATTACH 0x8000UL
+#define KVM_VM_TYPE_ARM_SPCI_ATTACH_ID_SHIFT 8
+#define KVM_VM_TYPE_ARM_SPCI_ATTACH_ID_MASK \
+ (0x7fUL << KVM_VM_TYPE_ARM_SPCI_ATTACH_ID_SHIFT)
+#define KVM_VM_TYPE_ARM_SPCI_ATTACH_ID(x) \
+ (((x) << KVM_VM_TYPE_ARM_SPCI_ATTACH_ID_SHIFT) \
+ & KVM_VM_TYPE_ARM_SPCI_ATTACH_ID_MASK)
+
+static struct {
+ int partition_id;
+ bool aarch32;
+} opts = {
+ .partition_id = -1,
+ .aarch32 = false,
+};
+
+enum options {
+ OPT_HELP,
+ OPT_PARTITION_ID,
+ OPT_AARCH32,
+};
+
+const char short_opts[] = "";
+const struct option long_opts[] = {
+ {"help", no_argument, NULL, OPT_HELP},
+ {"partition_id", required_argument, NULL, OPT_PARTITION_ID},
+ {"aarch32", no_argument, NULL, OPT_AARCH32,
+ {NULL, 0, NULL, 0},
+};
+
+void usage(void)
+{
+ puts("Arguments:\n"
+ "\t--help\t\t\tDisplay this information\n"
+ "\t--partition_id id\tID of partition to attach to\n");
+}
+
+int handle_opts(int argc, char **argv)
+{
+ int opt;
+ while ((opt = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) {
+ switch (opt) {
+ case OPT_PARTITION_ID:
+ opts.partition_id = strtol(optarg, NULL, 10);
+ break;
+ case OPT_AARCH32:
+ opts.aarch32 = true;
+ break;
+ case OPT_HELP:
+ default:
+ goto err;
+ }
+ }
+
+ if (opts.partition_id < 0)
+ goto err;
+
+ if (opts.aarch32)
+ errx(1, "AArch32 not yet supported");
+
+ return 0;
+
+err:
+ usage();
+ return -1;
+}
+
+int main(int argc, char **argv)
+{
+ int kvm, vmfd, vcpufd, ret;
+
+ size_t mmap_size;
+ struct kvm_run *run = NULL;
+ unsigned long vm_type;
+ struct kvm_vcpu_init vcpu_init;
+
+ ret = handle_opts(argc, argv);
+ if (ret < 0)
+ err(1, "Bad arguments");
+
+ kvm = open("/dev/kvm", O_RDWR | O_CLOEXEC);
+ if (kvm < 0)
+ err(1, "/dev/kvm");
+
+ /* Ensure this is the stable version of the KVM API (defined as 12) */
+ ret = ioctl(kvm, KVM_GET_API_VERSION, NULL);
+ if (ret < 0)
+ err(1, "KVM_GET_API_VERSION");
+ if (ret != 12)
+ errx(1, "KVM_GET_API_VERSION %d, expected 12", ret);
+
+ ret = ioctl(kvm, KVM_CHECK_EXTENSION, KVM_CAP_ARM_SPCI);
+ if (ret == -1)
+ err(1, "KVM_CHECK_EXTENSION KVM_CAP_ARM_SPCI");
+ if (!ret)
+ errx(1, "KVM_CAP_ARM_SPCI unavailable");
+
+ vm_type = KVM_VM_TYPE_ARM_SPCI_ATTACH
+ | KVM_VM_TYPE_ARM_SPCI_ATTACH_ID(opts.partition_id);
+ vmfd = ioctl(kvm, KVM_CREATE_VM, vm_type);
+ if (vmfd < 0)
+ err(1, "KVM_CREATE_VM");
+
+ /* Create one CPU to run in the VM. */
+ vcpufd = ioctl(vmfd, KVM_CREATE_VCPU, (unsigned long)0);
+ if (vcpufd < 0)
+ err(1, "KVM_CREATE_VCPU");
+
+ /* Map the shared kvm_run structure and following data. */
+ ret = ioctl(kvm, KVM_GET_VCPU_MMAP_SIZE, NULL);
+ if (ret < 0)
+ err(1, "KVM_GET_VCPU_MMAP_SIZE");
+ mmap_size = ret;
+ if (mmap_size < sizeof(*run))
+ errx(1, "KVM_GET_VCPU_MMAP_SIZE unexpectedly small");
+ run = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, vcpufd, 0);
+ if (!run)
+ err(1, "mmap vcpu");
+
+ /* Query KVM for preferred CPU target type that can be emulated. */
+ ret = ioctl(vmfd, KVM_ARM_PREFERRED_TARGET, &vcpu_init);
+ if (ret < 0)
+ err(1, "KVM_PREFERRED_TARGET");
+
+ /* Initialize VCPU with the preferred type obtained above. */
+ ret = ioctl(vcpufd, KVM_ARM_VCPU_INIT, &vcpu_init);
+ if (ret < 0)
+ err(1, "KVM_ARM_VCPU_INIT");
+
+ /*
+ * Enable debug so that brk instruction would exit KVM_RUN with
+ * KVM_EXIT_DEBUG.
+ */
+ struct kvm_guest_debug debug = {
+ .control = KVM_GUESTDBG_ENABLE,
+ };
+ ret = ioctl(vcpufd, KVM_SET_GUEST_DEBUG, &debug);
+ if (ret < 0)
+ err(1, "KVM_SET_GUEST_DEBUG");
+
+ /* Repeatedly run code and handle VM exits. */
+ for (;;) {
+ ret = ioctl(vcpufd, KVM_RUN, NULL);
+ if (ret < 0)
+ err(1, "KVM_RUN");
+ switch (run->exit_reason) {
+ case KVM_EXIT_DEBUG:
+ puts("KVM_EXIT_DEBUG");
+ return 0;
+ case KVM_EXIT_MMIO:
+ uint64_t payload = *(uint64_t*)(run->mmio.data); /* sorry */
+ puts("KVM_EXIT_MMIO... that shouldn't happen :/");
+ printf(" addr = 0x%llx, len = %u, is_write = %u, data = 0x%08llx\n",
+ run->mmio.phys_addr, run->mmio.len,
+ run->mmio.is_write, payload);
+ errx(1, "Bailing out");
+ break;
+ }
+ case KVM_EXIT_FAIL_ENTRY:
+ errx(1, "KVM_EXIT_FAIL_ENTRY: hardware_entry_failure_reason = 0x%llx",
+ (unsigned long long)run->fail_entry.hardware_entry_failure_reason);
+ case KVM_EXIT_INTERNAL_ERROR:
+ errx(1, "KVM_EXIT_INTERNAL_ERROR: suberror = 0x%x",
+ run->internal.suberror);
+ default:
+ errx(1, "exit_reason = 0x%x", run->exit_reason);
+ }
+ }
+}
diff --git a/kvmtest/kvmtest-spci.sh b/kvmtest/kvmtest-spci.sh
new file mode 100755
index 0000000..d84a571
--- /dev/null
+++ b/kvmtest/kvmtest-spci.sh
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: Apache-2.0
+# Copyright (c) 2020 Google LLC
+# Author: Andrew Scull <ascull@google.com>
+
+set -euo pipefail
+
+echo "Available partitions:"
+
+partition_ids=()
+for f in /sys/hypervisor/spci/*
+do
+ if [[ $f =~ ^/sys/hypervisor/spci/partition[0-9]+$ ]]
+ then
+ id=${f:30}
+ uuid=$(<$f/uuid)
+ exec_state=$(<$f/exec_state)
+ vcpus=$(<$f/vcpus)
+ echo " $id: $uuid $exec_state with $vcpus vCPUs"
+ partition_ids+=($id)
+ fi
+done
+
+selected_id=${partition_ids[0]}
+echo "Selecting partition $selected_id"
+
+cmd="./kvmtest-spci --partition_id $selected_id"
+echo $cmd
+$cmd
diff --git a/kvmtest/vms/.gitignore b/kvmtest/vms/.gitignore
new file mode 100644
index 0000000..ab0c339
--- /dev/null
+++ b/kvmtest/vms/.gitignore
@@ -0,0 +1,2 @@
+brk.o
+brk.bin
diff --git a/kvmtest/vms/Makefile b/kvmtest/vms/Makefile
new file mode 100644
index 0000000..e988d88
--- /dev/null
+++ b/kvmtest/vms/Makefile
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright (c) 2020 Google LLC
+# Author: Andrew Scull <ascull@google.com>
+
+brk.bin: brk.c
+ clang --target=aarch64-none-eabi -c -nostdlib -fno-builtin -ffreestanding $< -o brk.o
+ aarch64-linux-gnu-objcopy -O binary brk.o $@
+
+disas: brk.bin
+ aarch64-linux-gnu-objdump -D -b binary -m aarch64 $<
diff --git a/kvmtest/vms/brk.c b/kvmtest/vms/brk.c
new file mode 100644
index 0000000..1cfe19f
--- /dev/null
+++ b/kvmtest/vms/brk.c
@@ -0,0 +1,11 @@
+// SPDX-License-Identifier: Apache-2.0
+/*
+ * Copyright (c) 2020 Google LLC
+ * Author: Andrew Scull <ascull@google.com>
+ */
+
+void _start(void)
+{
+ __asm__ volatile("brk 0");
+ *((volatile int*)0) = 12;
+}