@@ -102,7 +102,8 @@ OBJS += builtin-sandbox.o
OBJS += virtio/mmio.o
# Translate uname -m into ARCH string
-ARCH ?= $(shell uname -m | sed -e s/i.86/i386/ -e s/ppc.*/powerpc/)
+ARCH ?= $(shell uname -m | sed -e s/i.86/i386/ -e s/ppc.*/powerpc/ \
+ -e s/armv7.*/arm/)
ifeq ($(ARCH),i386)
ARCH := x86
@@ -157,6 +158,23 @@ ifeq ($(ARCH), powerpc)
CFLAGS += -m64
endif
+# ARM
+OBJS_ARM_COMMON := arm/fdt.o arm/gic.o arm/ioport.o arm/irq.o \
+ arm/kvm.o arm/kvm-cpu.o arm/smp.o
+HDRS_ARM_COMMON := arm/include
+ifeq ($(ARCH), arm)
+ DEFINES += -DCONFIG_ARM
+ OBJS += $(OBJS_ARM_COMMON)
+ OBJS += arm/aarch32/cortex-a15.o
+ OBJS += arm/aarch32/kvm-cpu.o
+ OTHEROBJS += arm/aarch32/smp-pen.o
+ ARCH_INCLUDE := $(HDRS_ARM_COMMON)
+ ARCH_INCLUDE += -Iarm/aarch32/include
+ CFLAGS += -march=armv7-a
+ CFLAGS += -I../../scripts/dtc/libfdt
+ OTHEROBJS += $(LIBFDT_OBJS)
+endif
+
###
ifeq (,$(ARCH_INCLUDE))
new file mode 100644
@@ -0,0 +1,98 @@
+#include "kvm/fdt.h"
+#include "kvm/kvm.h"
+#include "kvm/kvm-cpu.h"
+#include "kvm/util.h"
+
+#include "arm-common/gic.h"
+
+#include <linux/byteorder.h>
+#include <linux/types.h>
+
+#define CPU_NAME_MAX_LEN 8
+static void generate_cpu_nodes(void *fdt, struct kvm *kvm)
+{
+ int cpu;
+
+ _FDT(fdt_begin_node(fdt, "cpus"));
+ _FDT(fdt_property_cell(fdt, "#address-cells", 0x1));
+ _FDT(fdt_property_cell(fdt, "#size-cells", 0x0));
+
+ for (cpu = 0; cpu < kvm->nrcpus; ++cpu) {
+ char cpu_name[CPU_NAME_MAX_LEN];
+
+ if (kvm->cpus[cpu]->cpu_type != KVM_ARM_TARGET_CORTEX_A15) {
+ pr_warning("Ignoring unknown type for CPU %d\n", cpu);
+ continue;
+ }
+
+ snprintf(cpu_name, CPU_NAME_MAX_LEN, "cpu@%d", cpu);
+
+ _FDT(fdt_begin_node(fdt, cpu_name));
+ _FDT(fdt_property_string(fdt, "device_type", "cpu"));
+ _FDT(fdt_property_string(fdt, "compatible", "arm,cortex-a15"));
+
+ if (kvm->nrcpus > 1) {
+ _FDT(fdt_property_string(fdt, "enable-method",
+ "spin-table"));
+ _FDT(fdt_property_cell(fdt, "cpu-release-addr",
+ kvm->arch.smp_jump_guest_start));
+ }
+
+ _FDT(fdt_property_cell(fdt, "reg", cpu));
+ _FDT(fdt_end_node(fdt));
+ }
+
+ _FDT(fdt_end_node(fdt));
+}
+
+static void generate_timer_nodes(void *fdt, struct kvm *kvm)
+{
+ u32 cpu_mask = (((1 << kvm->nrcpus) - 1) << GIC_FDT_IRQ_PPI_CPU_SHIFT) \
+ & GIC_FDT_IRQ_PPI_CPU_MASK;
+ u32 irq_prop[] = {
+ cpu_to_fdt32(GIC_FDT_IRQ_TYPE_PPI),
+ cpu_to_fdt32(13),
+ cpu_to_fdt32(cpu_mask | GIC_FDT_IRQ_FLAGS_EDGE_LO_HI),
+
+ cpu_to_fdt32(GIC_FDT_IRQ_TYPE_PPI),
+ cpu_to_fdt32(14),
+ cpu_to_fdt32(cpu_mask | GIC_FDT_IRQ_FLAGS_EDGE_LO_HI),
+
+ cpu_to_fdt32(GIC_FDT_IRQ_TYPE_PPI),
+ cpu_to_fdt32(11),
+ cpu_to_fdt32(cpu_mask | GIC_FDT_IRQ_FLAGS_EDGE_LO_HI),
+
+ cpu_to_fdt32(GIC_FDT_IRQ_TYPE_PPI),
+ cpu_to_fdt32(10),
+ cpu_to_fdt32(cpu_mask | GIC_FDT_IRQ_FLAGS_EDGE_LO_HI),
+ };
+
+ _FDT(fdt_begin_node(fdt, "timer"));
+ _FDT(fdt_property_string(fdt, "compatible", "arm,armv7-timer"));
+ _FDT(fdt_property(fdt, "interrupts", irq_prop, sizeof(irq_prop)));
+ _FDT(fdt_end_node(fdt));
+}
+
+static void generate_fdt_nodes(void *fdt, struct kvm *kvm, u32 gic_phandle)
+{
+ generate_cpu_nodes(fdt, kvm);
+ gic__generate_fdt_nodes(fdt, gic_phandle);
+ generate_timer_nodes(fdt, kvm);
+}
+
+static int cortex_a15__vcpu_init(struct kvm_cpu *vcpu)
+{
+ vcpu->generate_fdt_nodes = generate_fdt_nodes;
+ return 0;
+}
+
+static struct kvm_arm_target target_cortex_a15 = {
+ .id = KVM_ARM_TARGET_CORTEX_A15,
+ .init = cortex_a15__vcpu_init,
+};
+
+static int cortex_a15__core_init(struct kvm *kvm)
+{
+ return kvm_cpu__register_kvm_arm_target(&target_cortex_a15);
+}
+core_init(cortex_a15__core_init);
new file mode 100644
@@ -0,0 +1,10 @@
+#ifndef KVM__KVM_BARRIER_H
+#define KVM__KVM_BARRIER_H
+
+#define dmb() asm volatile ("dmb" : : : "memory")
+
+#define mb() dmb()
+#define rmb() dmb()
+#define wmb() dmb()
+
+#endif
new file mode 100644
@@ -0,0 +1,28 @@
+#ifndef KVM__KVM_ARCH_H
+#define KVM__KVM_ARCH_H
+
+#define ARM_LOMAP_MMIO_AREA 0x00000000UL
+#define ARM_LOMAP_AXI_AREA 0x40000000UL
+#define ARM_LOMAP_MEMORY_AREA 0x80000000UL
+#define ARM_LOMAP_MAX_MEMORY 0x7ffff000UL
+
+#define ARM_GIC_DIST_SIZE 0x1000
+#define ARM_GIC_DIST_BASE (ARM_LOMAP_AXI_AREA - ARM_GIC_DIST_SIZE)
+#define ARM_GIC_CPUI_SIZE 0x2000
+#define ARM_GIC_CPUI_BASE (ARM_GIC_DIST_BASE - ARM_GIC_CPUI_SIZE)
+
+#define ARM_KERN_OFFSET 0x8000
+
+#define ARM_SMP_PEN_SIZE PAGE_SIZE
+#define ARM_VIRTIO_MMIO_SIZE (ARM_GIC_DIST_BASE - ARM_LOMAP_MMIO_AREA)
+#define ARM_PCI_MMIO_SIZE (ARM_LOMAP_MEMORY_AREA - ARM_LOMAP_AXI_AREA)
+
+#define ARM_MEMORY_AREA ARM_LOMAP_MEMORY_AREA
+#define ARM_MAX_MEMORY ARM_LOMAP_MAX_MEMORY
+
+#define KVM_PCI_MMIO_AREA ARM_LOMAP_AXI_AREA
+#define KVM_VIRTIO_MMIO_AREA ARM_LOMAP_MMIO_AREA
+
+#include "arm-common/kvm-arch.h"
+
+#endif
new file mode 100644
@@ -0,0 +1,111 @@
+#include "kvm/kvm-cpu.h"
+#include "kvm/kvm.h"
+
+#include <asm/ptrace.h>
+
+#define ARM_CORE_REG(x) (KVM_REG_ARM | KVM_REG_SIZE_U32 | KVM_REG_ARM_CORE | \
+ KVM_REG_ARM_CORE_REG(x))
+
+void kvm_cpu__reset_vcpu(struct kvm_cpu *vcpu)
+{
+ struct kvm *kvm = vcpu->kvm;
+ struct kvm_one_reg reg;
+ u32 data;
+
+ /* Who said future-proofing was a good idea? */
+ reg.addr = (u64)(unsigned long)&data;
+
+ /* cpsr = IRQs/FIQs masked */
+ data = PSR_I_BIT | PSR_F_BIT | SVC_MODE;
+ reg.id = ARM_CORE_REG(usr_regs.ARM_cpsr);
+ if (ioctl(vcpu->vcpu_fd, KVM_SET_ONE_REG, ®) < 0)
+ die_perror("KVM_SET_ONE_REG failed (cpsr)");
+
+ if (vcpu->cpu_id == 0) {
+ /* r0 = 0 */
+ data = 0;
+ reg.id = ARM_CORE_REG(usr_regs.ARM_r0);
+ if (ioctl(vcpu->vcpu_fd, KVM_SET_ONE_REG, ®) < 0)
+ die_perror("KVM_SET_ONE_REG failed (r0)");
+
+ /* r1 = machine type (-1) */
+ data = -1;
+ reg.id = ARM_CORE_REG(usr_regs.ARM_r1);
+ if (ioctl(vcpu->vcpu_fd, KVM_SET_ONE_REG, ®) < 0)
+ die_perror("KVM_SET_ONE_REG failed (r1)");
+
+ /* r2 = physical address of the device tree blob */
+ data = kvm->arch.dtb_guest_start;
+ reg.id = ARM_CORE_REG(usr_regs.ARM_r2);
+ if (ioctl(vcpu->vcpu_fd, KVM_SET_ONE_REG, ®) < 0)
+ die_perror("KVM_SET_ONE_REG failed (r2)");
+
+ /* pc = start of kernel image */
+ data = kvm->arch.kern_guest_start;
+ reg.id = ARM_CORE_REG(usr_regs.ARM_pc);
+ if (ioctl(vcpu->vcpu_fd, KVM_SET_ONE_REG, ®) < 0)
+ die_perror("KVM_SET_ONE_REG failed (pc)");
+
+ } else {
+ /* Simply enter the pen */
+ data = kvm->arch.smp_pen_guest_start;
+ reg.id = ARM_CORE_REG(usr_regs.ARM_pc);
+ if (ioctl(vcpu->vcpu_fd, KVM_SET_ONE_REG, ®) < 0)
+ die_perror("KVM_SET_ONE_REG failed (SMP pc)");
+ }
+}
+
+void kvm_cpu__show_code(struct kvm_cpu *vcpu)
+{
+ struct kvm_one_reg reg;
+ u32 data;
+
+ reg.addr = (u64)(unsigned long)&data;
+
+ printf("*pc:\n");
+ reg.id = ARM_CORE_REG(usr_regs.ARM_pc);
+ if (ioctl(vcpu->vcpu_fd, KVM_GET_ONE_REG, ®) < 0)
+ die("KVM_GET_ONE_REG failed (show_code @ PC)");
+
+ kvm__dump_mem(vcpu->kvm, data, 32);
+ printf("\n");
+
+ printf("*lr (svc):\n");
+ reg.id = ARM_CORE_REG(svc_regs[1]);
+ if (ioctl(vcpu->vcpu_fd, KVM_GET_ONE_REG, ®) < 0)
+ die("KVM_GET_ONE_REG failed (show_code @ LR_svc)");
+ data &= ~0x1;
+
+ kvm__dump_mem(vcpu->kvm, data, 32);
+ printf("\n");
+}
+
+void kvm_cpu__show_registers(struct kvm_cpu *vcpu)
+{
+ struct kvm_one_reg reg;
+ u32 data;
+ int debug_fd = kvm_cpu__get_debug_fd();
+
+ reg.addr = (u64)(unsigned long)&data;
+ dprintf(debug_fd, "\n Registers:\n");
+
+ reg.id = ARM_CORE_REG(usr_regs.ARM_pc);
+ if (ioctl(vcpu->vcpu_fd, KVM_GET_ONE_REG, ®) < 0)
+ die("KVM_GET_ONE_REG failed (pc)");
+ dprintf(debug_fd, " PC: 0x%x\n", data);
+
+ reg.id = ARM_CORE_REG(usr_regs.ARM_cpsr);
+ if (ioctl(vcpu->vcpu_fd, KVM_GET_ONE_REG, ®) < 0)
+ die("KVM_GET_ONE_REG failed (cpsr)");
+ dprintf(debug_fd, " CPSR: 0x%x\n", data);
+
+ reg.id = ARM_CORE_REG(svc_regs[0]);
+ if (ioctl(vcpu->vcpu_fd, KVM_GET_ONE_REG, ®) < 0)
+ die("KVM_GET_ONE_REG failed (SP_svc)");
+ dprintf(debug_fd, " SP_svc: 0x%x\n", data);
+
+ reg.id = ARM_CORE_REG(svc_regs[1]);
+ if (ioctl(vcpu->vcpu_fd, KVM_GET_ONE_REG, ®) < 0)
+ die("KVM_GET_ONE_REG failed (LR_svc)");
+ dprintf(debug_fd, " LR_svc: 0x%x\n", data);
+}
new file mode 100644
@@ -0,0 +1,14 @@
+ .arm
+ .globl smp_pen_start
+ .globl smp_pen_end
+ .globl smp_jump_addr
+ .align
+smp_pen_start:
+ adr r0, smp_jump_addr
+ wfi
+ ldr r1, [r0]
+ mov pc, r1
+ .align
+smp_jump_addr:
+ .long 0xdeadc0de
+smp_pen_end:
new file mode 100644
@@ -0,0 +1,269 @@
+#include "kvm/devices.h"
+#include "kvm/fdt.h"
+#include "kvm/kvm.h"
+#include "kvm/kvm-cpu.h"
+#include "kvm/virtio-mmio.h"
+
+#include "arm-common/gic.h"
+
+#include <stdbool.h>
+
+#include <asm/setup.h>
+#include <linux/byteorder.h>
+#include <linux/kernel.h>
+#include <linux/sizes.h>
+
+#define DEBUG 0
+#define DEBUG_FDT_DUMP_FILE "/tmp/kvmtool.dtb"
+
+static char kern_cmdline[COMMAND_LINE_SIZE];
+
+bool kvm__load_firmware(struct kvm *kvm, const char *firmware_filename)
+{
+ return false;
+}
+
+int kvm__arch_setup_firmware(struct kvm *kvm)
+{
+ return 0;
+}
+
+#if DEBUG
+static void dump_fdt(void *fdt)
+{
+ int count, fd;
+
+ fd = open(DEBUG_FDT_DUMP_FILE, O_CREAT | O_TRUNC | O_RDWR, 0666);
+ if (fd < 0)
+ die("Failed to write dtb to %s", DEBUG_FDT_DUMP_FILE);
+
+ count = write(fd, fdt, FDT_MAX_SIZE);
+ if (count < 0)
+ die_perror("Failed to dump dtb");
+
+ pr_info("Wrote %d bytes to dtb %s\n", count, DEBUG_FDT_DUMP_FILE);
+ close(fd);
+}
+#else
+static void dump_fdt(void *fdt) { }
+#endif
+
+#define DEVICE_NAME_MAX_LEN 32
+static void generate_virtio_mmio_node(void *fdt, struct virtio_mmio *vmmio)
+{
+ char dev_name[DEVICE_NAME_MAX_LEN];
+ u64 addr = vmmio->addr;
+ u64 reg_prop[] = {
+ cpu_to_fdt64(addr),
+ cpu_to_fdt64(VIRTIO_MMIO_IO_SIZE)
+ };
+ u32 irq_prop[] = {
+ cpu_to_fdt32(GIC_FDT_IRQ_TYPE_SPI),
+ cpu_to_fdt32(vmmio->irq - GIC_SPI_IRQ_BASE),
+ cpu_to_fdt32(GIC_FDT_IRQ_FLAGS_EDGE_LO_HI),
+ };
+
+ snprintf(dev_name, DEVICE_NAME_MAX_LEN, "virtio@%llx", addr);
+
+ _FDT(fdt_begin_node(fdt, dev_name));
+ _FDT(fdt_property_string(fdt, "compatible", "virtio,mmio"));
+ _FDT(fdt_property(fdt, "reg", reg_prop, sizeof(reg_prop)));
+ _FDT(fdt_property(fdt, "interrupts", irq_prop, sizeof(irq_prop)));
+ _FDT(fdt_end_node(fdt));
+}
+
+static int setup_fdt(struct kvm *kvm)
+{
+ int devid;
+ u8 staging_fdt[FDT_MAX_SIZE];
+ u32 gic_phandle = fdt__alloc_phandle();
+ u64 mem_reg_prop[] = {
+ cpu_to_fdt64(kvm->arch.memory_guest_start),
+ cpu_to_fdt64(kvm->ram_size),
+ };
+ void *fdt = staging_fdt;
+ void *fdt_dest = guest_flat_to_host(kvm,
+ kvm->arch.dtb_guest_start);
+ void (*generate_cpu_nodes)(void *, struct kvm *, u32)
+ = kvm->cpus[0]->generate_fdt_nodes;
+
+ /* Create new tree without a reserve map */
+ _FDT(fdt_create(fdt, FDT_MAX_SIZE));
+ if (kvm->nrcpus > 1)
+ _FDT(fdt_add_reservemap_entry(fdt,
+ kvm->arch.smp_pen_guest_start,
+ ARM_SMP_PEN_SIZE));
+ _FDT(fdt_finish_reservemap(fdt));
+
+ /* Header */
+ _FDT(fdt_begin_node(fdt, ""));
+ _FDT(fdt_property_cell(fdt, "interrupt-parent", gic_phandle));
+ _FDT(fdt_property_string(fdt, "compatible", "linux,dummy-virt"));
+ _FDT(fdt_property_cell(fdt, "#address-cells", 0x2));
+ _FDT(fdt_property_cell(fdt, "#size-cells", 0x2));
+
+ /* /chosen */
+ _FDT(fdt_begin_node(fdt, "chosen"));
+ _FDT(fdt_property_string(fdt, "bootargs", kern_cmdline));
+
+ /* Initrd */
+ if (kvm->arch.initrd_size != 0) {
+ u32 ird_st_prop = cpu_to_fdt64(kvm->arch.initrd_guest_start);
+ u32 ird_end_prop = cpu_to_fdt64(kvm->arch.initrd_guest_start +
+ kvm->arch.initrd_size);
+
+ _FDT(fdt_property(fdt, "linux,initrd-start",
+ &ird_st_prop, sizeof(ird_st_prop)));
+ _FDT(fdt_property(fdt, "linux,initrd-end",
+ &ird_end_prop, sizeof(ird_end_prop)));
+ }
+ _FDT(fdt_end_node(fdt));
+
+ /* Memory */
+ _FDT(fdt_begin_node(fdt, "memory"));
+ _FDT(fdt_property_string(fdt, "device_type", "memory"));
+ _FDT(fdt_property(fdt, "reg", mem_reg_prop, sizeof(mem_reg_prop)));
+ _FDT(fdt_end_node(fdt));
+
+ /* CPU and peripherals (interrupt controller, timers, etc) */
+ if (generate_cpu_nodes)
+ generate_cpu_nodes(fdt, kvm, gic_phandle);
+
+ /* Virtio MMIO devices */
+ for (devid = 0; devid < KVM_MAX_DEVICES; ++devid) {
+ struct device_header *dev_hdr = device__find_dev(devid);
+
+ if (!dev_hdr || dev_hdr->bus_type != DEVICE_BUS_MMIO)
+ continue;
+
+ generate_virtio_mmio_node(fdt, dev_hdr->data);
+ }
+
+ /* Finalise. */
+ _FDT(fdt_end_node(fdt));
+ _FDT(fdt_finish(fdt));
+
+ _FDT(fdt_open_into(fdt, fdt_dest, FDT_MAX_SIZE));
+ _FDT(fdt_pack(fdt_dest));
+
+ dump_fdt(fdt_dest);
+ return 0;
+}
+firmware_init(setup_fdt);
+
+static int read_image(int fd, void **pos, void *limit)
+{
+ int count;
+
+ while (((count = read(fd, *pos, SZ_64K)) > 0) && *pos <= limit)
+ *pos += count;
+
+ if (pos < 0)
+ die_perror("read");
+
+ return *pos < limit ? 0 : -ENOMEM;
+}
+
+#define FDT_ALIGN SZ_2M
+#define INITRD_ALIGN 4
+#define SMP_PEN_ALIGN PAGE_SIZE
+int load_flat_binary(struct kvm *kvm, int fd_kernel, int fd_initrd,
+ const char *kernel_cmdline)
+{
+ void *pos, *kernel_end, *limit;
+ unsigned long guest_addr;
+
+ if (lseek(fd_kernel, 0, SEEK_SET) < 0)
+ die_perror("lseek");
+
+ /*
+ * Linux requires the initrd, pen and dtb to be mapped inside
+ * lowmem, so we can't just place them at the top of memory.
+ */
+ limit = kvm->ram_start + min(kvm->ram_size, (u64)SZ_256M) - 1;
+
+ pos = kvm->ram_start + ARM_KERN_OFFSET;
+ kvm->arch.kern_guest_start = host_to_guest_flat(kvm, pos);
+ if (read_image(fd_kernel, &pos, limit) == -ENOMEM)
+ die("kernel image too big to contain in guest memory.");
+
+ kernel_end = pos;
+ pr_info("Loaded kernel to 0x%lx (%ld bytes)",
+ kvm->arch.kern_guest_start,
+ host_to_guest_flat(kvm, pos) - kvm->arch.kern_guest_start);
+
+ /*
+ * Now load backwards from the end of memory so the kernel
+ * decompressor has plenty of space to work with. First up is
+ * the SMP pen if we have more than one virtual CPU...
+ */
+ pos = limit;
+ if (kvm->cfg.nrcpus > 1) {
+ pos -= (ARM_SMP_PEN_SIZE + SMP_PEN_ALIGN);
+ guest_addr = ALIGN(host_to_guest_flat(kvm, pos), SMP_PEN_ALIGN);
+ pos = guest_flat_to_host(kvm, guest_addr);
+ if (pos < kernel_end)
+ die("SMP pen overlaps with kernel image.");
+
+ kvm->arch.smp_pen_guest_start = guest_addr;
+ pr_info("Placing SMP pen at 0x%lx - 0x%lx",
+ kvm->arch.smp_pen_guest_start,
+ host_to_guest_flat(kvm, limit));
+ limit = pos;
+ }
+
+ /* ...now the device tree blob... */
+ pos -= (FDT_MAX_SIZE + FDT_ALIGN);
+ guest_addr = ALIGN(host_to_guest_flat(kvm, pos), FDT_ALIGN);
+ pos = guest_flat_to_host(kvm, guest_addr);
+ if (pos < kernel_end)
+ die("fdt overlaps with kernel image.");
+
+ kvm->arch.dtb_guest_start = guest_addr;
+ pr_info("Placing fdt at 0x%lx - 0x%lx",
+ kvm->arch.dtb_guest_start,
+ host_to_guest_flat(kvm, limit));
+ limit = pos;
+
+ /* ... and finally the initrd, if we have one. */
+ if (fd_initrd != -1) {
+ struct stat sb;
+ unsigned long initrd_start;
+
+ if (lseek(fd_initrd, 0, SEEK_SET) < 0)
+ die_perror("lseek");
+
+ if (fstat(fd_initrd, &sb))
+ die_perror("fstat");
+
+ pos -= (sb.st_size + INITRD_ALIGN);
+ guest_addr = ALIGN(host_to_guest_flat(kvm, pos), INITRD_ALIGN);
+ pos = guest_flat_to_host(kvm, guest_addr);
+ if (pos < kernel_end)
+ die("initrd overlaps with kernel image.");
+
+ initrd_start = guest_addr;
+ if (read_image(fd_initrd, &pos, limit) == -ENOMEM)
+ die("initrd too big to contain in guest memory.");
+
+ kvm->arch.initrd_guest_start = initrd_start;
+ kvm->arch.initrd_size = host_to_guest_flat(kvm, pos) - initrd_start;
+ pr_info("Loaded initrd to 0x%lx (%ld bytes)",
+ kvm->arch.initrd_guest_start,
+ kvm->arch.initrd_size);
+ } else {
+ kvm->arch.initrd_size = 0;
+ }
+
+ strncpy(kern_cmdline, kernel_cmdline, COMMAND_LINE_SIZE);
+ kern_cmdline[COMMAND_LINE_SIZE - 1] = '\0';
+
+ return true;
+}
+
+bool load_bzimage(struct kvm *kvm, int fd_kernel,
+ int fd_initrd, const char *kernel_cmdline, u16 vidmode)
+{
+ /* To b or not to b? That is the zImage. */
+ return false;
+}
new file mode 100644
@@ -0,0 +1,86 @@
+#include "kvm/fdt.h"
+#include "kvm/kvm.h"
+#include "kvm/virtio.h"
+
+#include "arm-common/gic.h"
+
+#include <linux/byteorder.h>
+#include <linux/kvm.h>
+
+static int irq_ids;
+
+int gic__alloc_irqnum(void)
+{
+ int irq = GIC_SPI_IRQ_BASE + irq_ids++;
+
+ if (irq > KVM_MAX_IRQ)
+ die("IRQ limit %d reached!", KVM_MAX_IRQ);
+
+ return irq;
+}
+
+int gic__init_irqchip(struct kvm *kvm)
+{
+ int err;
+ struct kvm_device_address gic_addr[] = {
+ [0] = {
+ .id = (KVM_ARM_DEVICE_VGIC_V2 << KVM_DEVICE_ID_SHIFT) |\
+ KVM_VGIC_V2_ADDR_TYPE_DIST,
+ .addr = ARM_GIC_DIST_BASE,
+ },
+ [1] = {
+ .id = (KVM_ARM_DEVICE_VGIC_V2 << KVM_DEVICE_ID_SHIFT) |\
+ KVM_VGIC_V2_ADDR_TYPE_CPU,
+ .addr = ARM_GIC_CPUI_BASE,
+ }
+ };
+
+ err = ioctl(kvm->vm_fd, KVM_CREATE_IRQCHIP);
+ if (err)
+ return err;
+
+ err = ioctl(kvm->vm_fd, KVM_SET_DEVICE_ADDRESS, &gic_addr[0]);
+ if (err)
+ return err;
+
+ err = ioctl(kvm->vm_fd, KVM_SET_DEVICE_ADDRESS, &gic_addr[1]);
+ return err;
+}
+
+void gic__generate_fdt_nodes(void *fdt, u32 phandle)
+{
+ u64 reg_prop[] = {
+ cpu_to_fdt64(ARM_GIC_DIST_BASE), cpu_to_fdt64(ARM_GIC_DIST_SIZE),
+ cpu_to_fdt64(ARM_GIC_CPUI_BASE), cpu_to_fdt64(ARM_GIC_CPUI_SIZE),
+ };
+
+ _FDT(fdt_begin_node(fdt, "intc"));
+ _FDT(fdt_property_string(fdt, "compatible", "arm,cortex-a15-gic"));
+ _FDT(fdt_property_cell(fdt, "#interrupt-cells", GIC_FDT_IRQ_NUM_CELLS));
+ _FDT(fdt_property(fdt, "interrupt-controller", NULL, 0));
+ _FDT(fdt_property(fdt, "reg", reg_prop, sizeof(reg_prop)));
+ _FDT(fdt_property_cell(fdt, "phandle", phandle));
+ _FDT(fdt_end_node(fdt));
+}
+
+#define KVM_IRQCHIP_IRQ(x) (KVM_ARM_IRQ_TYPE_SPI << KVM_ARM_IRQ_TYPE_SHIFT) |\
+ ((x) & KVM_ARM_IRQ_NUM_MASK)
+
+void kvm__irq_line(struct kvm *kvm, int irq, int level)
+{
+ struct kvm_irq_level irq_level = {
+ .irq = KVM_IRQCHIP_IRQ(irq),
+ .level = !!level,
+ };
+
+ if (irq < GIC_SPI_IRQ_BASE || irq > KVM_MAX_IRQ)
+ pr_warning("Ignoring invalid GIC IRQ %d", irq);
+ else if (ioctl(kvm->vm_fd, KVM_IRQ_LINE, &irq_level) < 0)
+ pr_warning("Could not KVM_IRQ_LINE for irq %d", irq);
+}
+
+void kvm__irq_trigger(struct kvm *kvm, int irq)
+{
+ kvm__irq_line(kvm, irq, VIRTIO_IRQ_HIGH);
+ kvm__irq_line(kvm, irq, VIRTIO_IRQ_LOW);
+}
new file mode 100644
@@ -0,0 +1,29 @@
+#ifndef ARM_COMMON__GIC_H
+#define ARM_COMMON__GIC_H
+
+#define GIC_SGI_IRQ_BASE 0
+#define GIC_PPI_IRQ_BASE 16
+#define GIC_SPI_IRQ_BASE 32
+
+#define GIC_FDT_IRQ_NUM_CELLS 3
+
+#define GIC_FDT_IRQ_TYPE_SPI 0
+#define GIC_FDT_IRQ_TYPE_PPI 1
+
+#define GIC_FDT_IRQ_FLAGS_EDGE_LO_HI 1
+#define GIC_FDT_IRQ_FLAGS_EDGE_HI_LO 2
+#define GIC_FDT_IRQ_FLAGS_LEVEL_HI 4
+#define GIC_FDT_IRQ_FLAGS_LEVEL_LO 8
+
+#define GIC_FDT_IRQ_PPI_CPU_SHIFT 8
+#define GIC_FDT_IRQ_PPI_CPU_MASK (0xff << GIC_FDT_IRQ_PPI_CPU_SHIFT)
+
+#define KVM_MAX_IRQ 255
+
+struct kvm;
+
+int gic__alloc_irqnum(void);
+int gic__init_irqchip(struct kvm *kvm);
+void gic__generate_fdt_nodes(void *fdt, u32 phandle);
+
+#endif /* ARM_COMMON__GIC_H */
new file mode 100644
@@ -0,0 +1,31 @@
+#ifndef ARM_COMMON__KVM_ARCH_H
+#define ARM_COMMON__KVM_ARCH_H
+
+#include <stdbool.h>
+#include <linux/types.h>
+
+#define VIRTIO_DEFAULT_TRANS VIRTIO_MMIO
+
+static inline bool arm_addr_in_virtio_mmio_region(u64 phys_addr)
+{
+ u64 limit = KVM_VIRTIO_MMIO_AREA + ARM_VIRTIO_MMIO_SIZE;
+ return phys_addr >= KVM_VIRTIO_MMIO_AREA && phys_addr < limit;
+}
+
+static inline bool arm_addr_in_pci_mmio_region(u64 phys_addr)
+{
+ u64 limit = KVM_PCI_MMIO_AREA + ARM_PCI_MMIO_SIZE;
+ return phys_addr >= KVM_PCI_MMIO_AREA && phys_addr < limit;
+}
+
+struct kvm_arch {
+ unsigned long memory_guest_start;
+ unsigned long kern_guest_start;
+ unsigned long initrd_guest_start;
+ unsigned long initrd_size;
+ unsigned long dtb_guest_start;
+ unsigned long smp_pen_guest_start;
+ unsigned long smp_jump_guest_start;
+};
+
+#endif /* ARM_COMMON__KVM_ARCH_H */
new file mode 100644
@@ -0,0 +1,7 @@
+#ifndef ARM_COMMON__SMP_H
+#define ARM_COMMON__SMP_H
+
+extern u8 smp_pen_start, smp_pen_end, smp_jump_addr;
+void smp_pen_init(struct kvm *kvm);
+
+#endif /* ARM_COMMON__SMP_H */
new file mode 100644
@@ -0,0 +1,47 @@
+#ifndef ARM_COMMON__KVM_CPU_ARCH_H
+#define ARM_COMMON__KVM_CPU_ARCH_H
+
+#include <linux/kvm.h>
+#include <pthread.h>
+#include <stdbool.h>
+
+struct kvm;
+
+struct kvm_cpu {
+ pthread_t thread;
+
+ unsigned long cpu_id;
+ unsigned long cpu_type;
+
+ struct kvm *kvm;
+ int vcpu_fd;
+ struct kvm_run *kvm_run;
+
+ u8 is_running;
+ u8 paused;
+ u8 needs_nmi;
+
+ struct kvm_coalesced_mmio_ring *ring;
+
+ void (*generate_fdt_nodes)(void *fdt, struct kvm* kvm,
+ u32 gic_phandle);
+};
+
+struct kvm_arm_target {
+ u32 id;
+ int (*init)(struct kvm_cpu *vcpu);
+};
+
+int kvm_cpu__register_kvm_arm_target(struct kvm_arm_target *target);
+
+static inline bool kvm_cpu__emulate_io(struct kvm *kvm, u16 port, void *data,
+ int direction, int size, u32 count)
+{
+ return false;
+}
+
+bool kvm_cpu__emulate_mmio(struct kvm *kvm, u64 phys_addr, u8 *data, u32 len,
+ u8 is_write);
+
+#endif /* ARM_COMMON__KVM_CPU_ARCH_H */
+
new file mode 100644
@@ -0,0 +1,5 @@
+#include "kvm/ioport.h"
+
+void ioport__setup_arch(struct kvm *kvm)
+{
+}
new file mode 100644
@@ -0,0 +1,25 @@
+#include "kvm/devices.h"
+#include "kvm/irq.h"
+#include "kvm/kvm.h"
+#include "kvm/util.h"
+
+#include "arm-common/gic.h"
+
+static int device_ids;
+
+int irq__register_device(u32 dev, u8 *num, u8 *pin, u8 *line)
+{
+ if (device_ids >= KVM_MAX_DEVICES)
+ die("Device limit %d reached!", KVM_MAX_DEVICES);
+
+ *num = device_ids++;
+ *line = gic__alloc_irqnum();
+
+ return 0;
+}
+
+int irq__add_msix_route(struct kvm *kvm, struct msi_msg *msg)
+{
+ die(__FUNCTION__);
+ return 0;
+}
new file mode 100644
@@ -0,0 +1,112 @@
+#include "kvm/kvm.h"
+#include "kvm/kvm-cpu.h"
+
+#include "arm-common/smp.h"
+
+static int debug_fd;
+
+void kvm_cpu__set_debug_fd(int fd)
+{
+ debug_fd = fd;
+}
+
+int kvm_cpu__get_debug_fd(void)
+{
+ return debug_fd;
+}
+
+static struct kvm_arm_target *kvm_arm_targets[KVM_ARM_NUM_TARGETS];
+int kvm_cpu__register_kvm_arm_target(struct kvm_arm_target *target)
+{
+ unsigned int i = 0;
+
+ for (i = 0; i < ARRAY_SIZE(kvm_arm_targets); ++i) {
+ if (!kvm_arm_targets[i]) {
+ kvm_arm_targets[i] = target;
+ return 0;
+ }
+ }
+
+ return -ENOSPC;
+}
+
+struct kvm_cpu *kvm_cpu__arch_init(struct kvm *kvm, unsigned long cpu_id)
+{
+ struct kvm_cpu *vcpu;
+ int coalesced_offset, mmap_size, err = -1;
+ unsigned int i;
+ struct kvm_vcpu_init vcpu_init = { };
+
+ vcpu = calloc(1, sizeof(struct kvm_cpu));
+ if (!vcpu)
+ return NULL;
+
+ vcpu->vcpu_fd = ioctl(kvm->vm_fd, KVM_CREATE_VCPU, cpu_id);
+ if (vcpu->vcpu_fd < 0)
+ die_perror("KVM_CREATE_VCPU ioctl");
+
+ mmap_size = ioctl(kvm->sys_fd, KVM_GET_VCPU_MMAP_SIZE, 0);
+ if (mmap_size < 0)
+ die_perror("KVM_GET_VCPU_MMAP_SIZE ioctl");
+
+ vcpu->kvm_run = mmap(NULL, mmap_size, PROT_RW, MAP_SHARED,
+ vcpu->vcpu_fd, 0);
+ if (vcpu->kvm_run == MAP_FAILED)
+ die("unable to mmap vcpu fd");
+
+ /* Find an appropriate target CPU type. */
+ for (i = 0; i < ARRAY_SIZE(kvm_arm_targets); ++i) {
+ vcpu_init.target = kvm_arm_targets[i]->id;
+ err = ioctl(vcpu->vcpu_fd, KVM_ARM_VCPU_INIT, &vcpu_init);
+ if (!err)
+ break;
+ }
+
+ if (err || kvm_arm_targets[i]->init(vcpu))
+ die("Unable to initialise ARM vcpu");
+
+ coalesced_offset = ioctl(kvm->sys_fd, KVM_CHECK_EXTENSION,
+ KVM_CAP_COALESCED_MMIO);
+ if (coalesced_offset)
+ vcpu->ring = (void *)vcpu->kvm_run +
+ (coalesced_offset * PAGE_SIZE);
+
+ if (cpu_id != 0)
+ smp_pen_init(kvm);
+
+ /* Populate the vcpu structure. */
+ vcpu->kvm = kvm;
+ vcpu->cpu_id = cpu_id;
+ vcpu->cpu_type = vcpu_init.target;
+ vcpu->is_running = true;
+ return vcpu;
+}
+
+void kvm_cpu__arch_nmi(struct kvm_cpu *cpu)
+{
+}
+
+void kvm_cpu__delete(struct kvm_cpu *vcpu)
+{
+ free(vcpu);
+}
+
+bool kvm_cpu__handle_exit(struct kvm_cpu *vcpu)
+{
+ return false;
+}
+
+bool kvm_cpu__emulate_mmio(struct kvm *kvm, u64 phys_addr, u8 *data, u32 len,
+ u8 is_write)
+{
+ if (arm_addr_in_virtio_mmio_region(phys_addr))
+ return kvm__emulate_mmio(kvm, phys_addr, data, len, is_write);
+ else if (arm_addr_in_pci_mmio_region(phys_addr))
+ die("PCI emulation not supported on ARM!");
+
+ return false;
+}
+
+void kvm_cpu__show_page_tables(struct kvm_cpu *vcpu)
+{
+}
new file mode 100644
@@ -0,0 +1,69 @@
+#include "kvm/kvm.h"
+#include "kvm/term.h"
+#include "kvm/util.h"
+#include "kvm/virtio-console.h"
+
+#include "arm-common/gic.h"
+
+#include <linux/kernel.h>
+#include <linux/kvm.h>
+
+struct kvm_ext kvm_req_ext[] = {
+ { DEFINE_KVM_EXT(KVM_CAP_IRQCHIP) },
+ { DEFINE_KVM_EXT(KVM_CAP_ONE_REG) },
+ { 0, 0 },
+};
+
+bool kvm__arch_cpu_supports_vm(void)
+{
+ /* The KVM capability check is enough. */
+ return true;
+}
+
+void kvm__init_ram(struct kvm *kvm)
+{
+ int err;
+ u64 phys_start, phys_size;
+ void *host_mem;
+
+ phys_start = ARM_MEMORY_AREA;
+ phys_size = kvm->ram_size;
+ host_mem = kvm->ram_start;
+
+ err = kvm__register_mem(kvm, phys_start, phys_size, host_mem);
+ if (err)
+ die("Failed to register %lld bytes of memory at physical "
+ "address 0x%llx [err %d]", phys_size, phys_start, err);
+
+ kvm->arch.memory_guest_start = phys_start;
+}
+
+void kvm__arch_delete_ram(struct kvm *kvm)
+{
+ munmap(kvm->ram_start, kvm->ram_size);
+}
+
+void kvm__arch_periodic_poll(struct kvm *kvm)
+{
+ if (term_readable(0))
+ virtio_console__inject_interrupt(kvm);
+}
+
+void kvm__arch_set_cmdline(char *cmdline, bool video)
+{
+}
+
+void kvm__arch_init(struct kvm *kvm, const char *hugetlbfs_path, u64 ram_size)
+{
+ /* Allocate guest memory. */
+ kvm->ram_size = min(ram_size, (u64)ARM_MAX_MEMORY);
+ kvm->ram_start = mmap_anon_or_hugetlbfs(kvm, NULL, kvm->ram_size);
+ if (kvm->ram_start == MAP_FAILED)
+ die("Failed to map %lld bytes for guest memory (%d)",
+ kvm->ram_size, errno);
+ madvise(kvm->ram_start, kvm->ram_size, MADV_MERGEABLE);
+
+ /* Initialise the virtual GIC. */
+ if (gic__init_irqchip(kvm))
+ die("Failed to initialise virtual GIC");
+}
new file mode 100644
@@ -0,0 +1,13 @@
+#include "kvm/kvm.h"
+
+#include "arm-common/smp.h"
+
+void smp_pen_init(struct kvm *kvm)
+{
+ unsigned long pen_size = &smp_pen_end - &smp_pen_start;
+ unsigned long pen_start = kvm->arch.smp_pen_guest_start;
+ unsigned long jump_offset = &smp_jump_addr - &smp_pen_start;
+
+ kvm->arch.smp_jump_guest_start = pen_start + jump_offset;
+ memcpy(guest_flat_to_host(kvm, pen_start), &smp_pen_start, pen_size);
+}
This patch adds initial support for ARMv7 processors (more specifically, Cortex-A15) to kvmtool. Everything is driven by FDT, including dynamic generation of virtio nodes for MMIO devices (PCI is not used due to lack of a suitable host-bridge). The virtual timers and virtual interrupt controller (VGIC) are provided by the kernel and require very little in terms of userspace code. Signed-off-by: Will Deacon <will.deacon@arm.com> --- tools/kvm/Makefile | 20 ++- tools/kvm/arm/aarch32/cortex-a15.c | 98 ++++++++++ tools/kvm/arm/aarch32/include/kvm/barrier.h | 10 + tools/kvm/arm/aarch32/include/kvm/kvm-arch.h | 28 +++ tools/kvm/arm/aarch32/kvm-cpu.c | 111 +++++++++++ tools/kvm/arm/aarch32/smp-pen.S | 14 ++ tools/kvm/arm/fdt.c | 269 ++++++++++++++++++++++++++ tools/kvm/arm/gic.c | 86 ++++++++ tools/kvm/arm/include/arm-common/gic.h | 29 +++ tools/kvm/arm/include/arm-common/kvm-arch.h | 31 +++ tools/kvm/arm/include/arm-common/smp.h | 7 + tools/kvm/arm/include/kvm/kvm-cpu-arch.h | 47 +++++ tools/kvm/arm/ioport.c | 5 + tools/kvm/arm/irq.c | 25 +++ tools/kvm/arm/kvm-cpu.c | 112 +++++++++++ tools/kvm/arm/kvm.c | 69 +++++++ tools/kvm/arm/smp.c | 13 ++ 17 files changed, 973 insertions(+), 1 deletions(-) create mode 100644 tools/kvm/arm/aarch32/cortex-a15.c create mode 100644 tools/kvm/arm/aarch32/include/kvm/barrier.h create mode 100644 tools/kvm/arm/aarch32/include/kvm/kvm-arch.h create mode 100644 tools/kvm/arm/aarch32/kvm-cpu.c create mode 100644 tools/kvm/arm/aarch32/smp-pen.S create mode 100644 tools/kvm/arm/fdt.c create mode 100644 tools/kvm/arm/gic.c create mode 100644 tools/kvm/arm/include/arm-common/gic.h create mode 100644 tools/kvm/arm/include/arm-common/kvm-arch.h create mode 100644 tools/kvm/arm/include/arm-common/smp.h create mode 100644 tools/kvm/arm/include/kvm/kvm-cpu-arch.h create mode 100644 tools/kvm/arm/ioport.c create mode 100644 tools/kvm/arm/irq.c create mode 100644 tools/kvm/arm/kvm-cpu.c create mode 100644 tools/kvm/arm/kvm.c create mode 100644 tools/kvm/arm/smp.c