| /* |
| * 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); |
| } |
| } |
| } |