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;
+}