Commit 7c0e8b0c authored by Will Deacon's avatar Will Deacon
Browse files

kvm tools: add support for ARMv7 processors



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.

Tested-by: default avatarMarc Zyngier <marc.zyngier@arm.com>
Signed-off-by: default avatarWill Deacon <will.deacon@arm.com>
Signed-off-by: default avatarPekka Enberg <penberg@kernel.org>
parent 0cb41990
......@@ -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,25 @@ 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
OBJS += arm/aarch32/smp-pen.o
ARCH_INCLUDE := $(HDRS_ARM_COMMON)
ARCH_INCLUDE += -Iarm/aarch32/include
ASFLAGS += -D__ASSEMBLY__
ASFLAGS += -I$(ARCH_INCLUDE)
CFLAGS += -march=armv7-a
CFLAGS += -I../../scripts/dtc/libfdt
OTHEROBJS += $(LIBFDT_OBJS)
endif
###
ifeq (,$(ARCH_INCLUDE))
......
#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);
#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 /* KVM__KVM_BARRIER_H */
#ifndef KVM__KVM_ARCH_H
#define KVM__KVM_ARCH_H
#include <linux/const.h>
#define ARM_LOMAP_MMIO_AREA _AC(0x00000000, UL)
#define ARM_LOMAP_AXI_AREA _AC(0x40000000, UL)
#define ARM_LOMAP_MEMORY_AREA _AC(0x80000000, UL)
#define ARM_LOMAP_MAX_MEMORY _AC(0x7fffffff, UL)
#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 /* KVM__KVM_ARCH_H */
#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, &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, &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, &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, &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, &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, &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, &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, &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, &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, &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, &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, &reg) < 0)
die("KVM_GET_ONE_REG failed (LR_svc)");
dprintf(debug_fd, " LR_svc: 0x%x\n", data);
}
#include "kvm/kvm-arch.h"
#include "arm-common/gic.h"
#define AARCH32_SMP_BAD_MAGIC 0xdeadc0de
.arm
.globl smp_pen_start
.globl smp_jump_addr
.globl smp_pen_end
.align
smp_pen_start:
@ Ensure that the CPU interface is enabled for the wfi wakeup
ldr r0, =ARM_GIC_CPUI_BASE
mov r1, #GIC_CPUI_CTLR_EN
str r1, [r0]
@ Now wait for the primary to poke us
adr r0, smp_jump_addr
ldr r1, =AARCH32_SMP_BAD_MAGIC
dsb
1: wfi
ldr r2, [r0]
cmp r1, r2
beq 1b
mov pc, r2
.ltorg
.align
smp_jump_addr:
.long AARCH32_SMP_BAD_MAGIC
smp_pen_end:
#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)
{
struct device_header *dev_hdr;
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 */
dev_hdr = device__first_dev(DEVICE_BUS_MMIO);
while (dev_hdr) {
generate_virtio_mmio_node(fdt, dev_hdr->data);
dev_hdr = device__next_dev(dev_hdr);
}
/* 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;
}
late_init(setup_fdt);
static int read_image(int fd, void **pos, void *limit)
{
int count;
while (((count = xread(fd, *pos, SZ_64K)) > 0) && *pos <= limit)
*pos += count;
if (pos < 0)
die_perror("xread");
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%llx (%llu 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%llx - 0x%llx",
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%llx - 0x%llx",
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%llx (%llu 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;
}
#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 > GIC_MAX_IRQ)
die("GIC IRQ limit %d reached!", GIC_MAX_IRQ);
return irq;
}
int gic__init_irqchip(struct kvm *kvm)
{
int err;
struct kvm_device_address gic_addr[] = {
[0] = {