From 5748690ddd7f4f3b8a4615641332574d3a76d3ab Mon Sep 17 00:00:00 2001 From: John Rigby Date: Tue, 30 Apr 2013 09:15:18 -0600 Subject: ARM: Add mach-virt platform Add mach-virt platform support corresponding to /arch/arm/mach-virt in kernel tree. For now it is not virtual but instantiates a pl011 uart and an sp804 timer. The uart is need for a console the timer is needed when running without kvm and there is no arch timer. Signed-off-by: John Rigby --- hw/arm/Makefile.objs | 2 +- hw/arm/virt.c | 437 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 438 insertions(+), 1 deletion(-) create mode 100644 hw/arm/virt.c diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs index 9e3a06fc1..744484fc0 100644 --- a/hw/arm/Makefile.objs +++ b/hw/arm/Makefile.objs @@ -1,7 +1,7 @@ obj-y += boot.o collie.o exynos4_boards.o gumstix.o highbank.o obj-y += integratorcp.o kzm.o mainstone.o musicpal.o nseries.o obj-y += omap_sx1.o palm.o pic_cpu.o realview.o spitz.o stellaris.o -obj-y += tosa.o versatilepb.o vexpress.o xilinx_zynq.o z2.o +obj-y += tosa.o versatilepb.o vexpress.o virt.o xilinx_zynq.o z2.o obj-y += armv7m.o exynos4210.o pxa2xx.o pxa2xx_gpio.o pxa2xx_pic.o obj-y += omap1.o omap2.o strongarm.o diff --git a/hw/arm/virt.c b/hw/arm/virt.c new file mode 100644 index 000000000..6a0472fa3 --- /dev/null +++ b/hw/arm/virt.c @@ -0,0 +1,437 @@ +/* + * ARM mach-virt emulation + * + * Copyright (c) 2013 Linaro + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2 or later, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * Emulate a virtual board compatible with arch/arm/mach-virt/virt.c in the + * linux kernel source. + */ + +#include "hw/sysbus.h" +#include "hw/arm/arm.h" +#include "hw/arm/primecell.h" +#include "hw/devices.h" +#include "net/net.h" +#include "sysemu/device_tree.h" +#include "sysemu/sysemu.h" +#include "sysemu/kvm.h" +#include "hw/boards.h" +#include "exec/address-spaces.h" +#include "libfdt_env.h" + +#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 CPU_NAME_MAX_LEN 16 + +#define MEM_BASE 0 +#define MAX_MEM 0xff800000 +#define IO_BASE 0xfff00000 +#define IO_LEN 0x000f0000 + +struct machine_info { + const char cpu_model[CPU_NAME_MAX_LEN]; + const char *cpu_compat; + const char *qdevname; + uint64_t mem_base; + uint64_t max_mem; + uint64_t io_base; + uint64_t io_len; + /* offsets and sizes of gic regions */ + struct { + uint32_t dist_base; + uint32_t dist_size; + uint32_t cpui_base; + uint32_t cpui_size; + uint32_t total_size; + const char *compatible; + } gic_info; + char *gic; +}; + +static struct machine_info machines[] = { + { + .cpu_model = "cortex-a15", + .cpu_compat = "arm,cortex-a15", + .qdevname = "a15mpcore_priv", + .mem_base = MEM_BASE, + .max_mem = MAX_MEM, + .io_base = IO_BASE, + .io_len = IO_LEN, + .gic_info = {0x01000, 0x1000, 0x02000, 0x1000, 0x8000, + "arm,cortex-a15-gic"}, + }, + { + .cpu_model = "cortex-a9", + .cpu_compat = "arm,cortex-a9", + .qdevname = "a9mpcore_priv", + .mem_base = MEM_BASE, + .max_mem = MAX_MEM, + .io_base = IO_BASE, + .io_len = IO_LEN, + .gic_info = {0x01000, 0x1000, 0x0100, 0x0100, 0x2000, + "arm,cortex-a9-gic"}, + }, + { + .cpu_model = "", + }, +}; + +static struct machine_info *find_machine_info(const char *cpu) +{ + struct machine_info *mi = machines; + + while (mi->cpu_model) { + if (strncmp(cpu, mi->cpu_model, sizeof(mi->cpu_model)) == 0) { + return mi; + } + mi++; + } + return NULL; +} + +static void *virt_fdt; +static int virt_fdt_size; + +static void *initial_fdt(struct machine_info *mi) +{ + void *fdt = create_device_tree(&virt_fdt_size); + char compatible_sb[] = "simple-bus\0arm,amba-bus"; + + if (fdt == NULL) { + return NULL; + } + + /* Header */ + qemu_devtree_setprop_string(fdt, "/", "compatible", "linux,dummy-virt"); + qemu_devtree_setprop_cell(fdt, "/", "#address-cells", 0x2); + qemu_devtree_setprop_cell(fdt, "/", "#size-cells", 0x2); + + /* + * /chosen and /memory nodes must exist for load_dtb + * to fill in neccessary properties later + */ + qemu_devtree_add_subnode(fdt, "/chosen"); + qemu_devtree_add_subnode(fdt, "/memory"); + qemu_devtree_setprop_string(fdt, "/memory", "device_type", "memory"); + + /* + * Fixed soc properties + */ + qemu_devtree_add_subnode(fdt, "/soc"); + qemu_devtree_setprop(fdt, "/soc", "compatible", compatible_sb, + sizeof(compatible_sb)); + qemu_devtree_setprop_cell(fdt, "/soc", "#address-cells", 0x1); + qemu_devtree_setprop_cell(fdt, "/soc", "#size-cells", 0x1); + qemu_devtree_setprop_cells(fdt, "/soc", "ranges", mi->io_base, 0x0, + mi->io_base, mi->io_len); + qemu_devtree_setprop_cell(fdt, "/soc", "#interrupt-cells", 0x1); + + /* No PSCI for TCG yet */ +#ifdef CONFIG_KVM + if (kvm_enabled()) { + qemu_devtree_add_subnode(fdt, "/psci"); + qemu_devtree_setprop_string(fdt, "/psci", "compatible", "arm,psci"); + qemu_devtree_setprop_string(fdt, "/psci", "method", "hvc"); + qemu_devtree_setprop_cell(fdt, "/psci", "cpu_suspend", + KVM_PSCI_FN_CPU_SUSPEND); + qemu_devtree_setprop_cell(fdt, "/psci", "cpu_off", KVM_PSCI_FN_CPU_OFF); + qemu_devtree_setprop_cell(fdt, "/psci", "cpu_on", KVM_PSCI_FN_CPU_ON); + qemu_devtree_setprop_cell(fdt, "/psci", "migrate", KVM_PSCI_FN_MIGRATE); + } +#endif + return fdt; +} + +static void fdt_add_timer_nodes(void *fdt, int smp_cpus) +{ + uint32_t cpu_mask = + (((1 << smp_cpus) - 1) << GIC_FDT_IRQ_PPI_CPU_SHIFT) + & GIC_FDT_IRQ_PPI_CPU_MASK; + cpu_mask = 0xf00; + uint32_t 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), + }; + + qemu_devtree_add_subnode(fdt, "/timer"); + qemu_devtree_setprop_string(fdt, "/timer", "compatible", "arm,armv7-timer"); + qemu_devtree_setprop(fdt, "/timer", "interrupts", irq_prop, + sizeof(irq_prop)); +} + +static void fdt_add_cpu_nodes(void *fdt, int smp_cpus) +{ + int cpu; + + qemu_devtree_add_subnode(fdt, "/cpus"); + qemu_devtree_setprop_cell(fdt, "/cpus", "#address-cells", 0x1); + qemu_devtree_setprop_cell(fdt, "/cpus", "#size-cells", 0x0); + + for (cpu = 0; cpu < smp_cpus; ++cpu) { + char cpu_name[CPU_NAME_MAX_LEN]; + + snprintf(cpu_name, CPU_NAME_MAX_LEN, "/cpus/cpu@%d", cpu); + + qemu_devtree_add_subnode(fdt, cpu_name); + qemu_devtree_setprop_string(fdt, cpu_name, "device_type", "cpu"); + qemu_devtree_setprop_string(fdt, cpu_name, "compatible", + "arm,cortex-a15"); + + if (smp_cpus > 1) { + qemu_devtree_setprop_string(fdt, cpu_name, "enable-method", "psci"); + } + + qemu_devtree_setprop_cell(fdt, cpu_name, "reg", cpu); + } +} + +static void add_interrupt_map(void *fdt, const char *soc, uint32_t gic, + int nirqs) +{ + int i, irq; + int len; + uint32_t *map; + + qemu_devtree_setprop_cell(fdt, soc, "#interrupt-cells", 0x1); + qemu_devtree_setprop_cells(fdt, soc, "interrupt-map-mask", 0, 63); + + len = nirqs * 6 * sizeof(uint32_t); + map = g_malloc(len); + + for (i = 0, irq = 0; irq < nirqs; irq++) { + map[i++] = cpu_to_be32(0x0); + map[i++] = cpu_to_be32(irq); + map[i++] = cpu_to_be32(gic); + map[i++] = cpu_to_be32(0x0); + map[i++] = cpu_to_be32(irq); + map[i++] = cpu_to_be32(4); + } + qemu_devtree_setprop(fdt, soc, "interrupt-map", map, len); + g_free(map); +} + +static void fdt_add_gic_node(void *fdt, struct machine_info *mi) +{ + uint32_t gic_phandle; + uint64_t reg_prop[4]; + + reg_prop[0] = cpu_to_fdt64(mi->gic_info.dist_base + mi->io_base); + reg_prop[1] = cpu_to_fdt64(mi->gic_info.dist_size); + reg_prop[2] = cpu_to_fdt64(mi->gic_info.cpui_base + mi->io_base); + reg_prop[3] = cpu_to_fdt64(mi->gic_info.cpui_size); + + mi->io_base += mi->gic_info.total_size; + + gic_phandle = qemu_devtree_alloc_phandle(fdt); + qemu_devtree_setprop_cell(fdt, "/", "interrupt-parent", gic_phandle); + + qemu_devtree_add_subnode(fdt, "/intc"); + qemu_devtree_setprop_string(fdt, "/intc", "compatible", + mi->gic_info.compatible); + qemu_devtree_setprop_cell(fdt, "/intc", "#interrupt-cells", + GIC_FDT_IRQ_NUM_CELLS); + qemu_devtree_setprop(fdt, "/intc", "interrupt-controller", NULL, 0); + qemu_devtree_setprop(fdt, "/intc", "reg", reg_prop, sizeof(reg_prop)); + qemu_devtree_setprop_cell(fdt, "/intc", "phandle", gic_phandle); + add_interrupt_map(fdt, "/soc", gic_phandle, 43); +} + +static void fdt_add_soc_nodes(void *fdt, struct machine_info *mi, qemu_irq *pic) +{ + uint32_t clock_phandle; + char compatible_uart[] = "arm,pl011\0arm,primecell"; + char compatible_timer[] = "arm,sp804\0arm,primecell"; + char clock_names_uart[] = "uartclk\0apb_pclk"; + char clock_names_timer[] = "timerclk\0apb_pclk"; + uint32_t base; + uint32_t len; + int irq; + + clock_phandle = qemu_devtree_alloc_phandle(fdt); + qemu_devtree_add_subnode(fdt, "/soc/clock"); + qemu_devtree_setprop_string(fdt, "/soc/clock", "compatible", "fixed-clock"); + qemu_devtree_setprop_cell(fdt, "/soc/clock", "#clock-cells", 0x0); + qemu_devtree_setprop_cell(fdt, "/soc/clock", "clock-frequency", 24000000); + qemu_devtree_setprop_string(fdt, "/soc/clock", "clock-output-names", + "clk24mhz"); + qemu_devtree_setprop_cell(fdt, "/soc/clock", "phandle", clock_phandle); + + len = 0x1000; + base = mi->io_base; + mi->io_base += len; + irq = 5; + sysbus_create_simple("pl011", base, pic[irq]); + qemu_devtree_add_subnode(fdt, "/soc/uart"); + qemu_devtree_setprop(fdt, "/soc/uart", "compatible", compatible_uart, + sizeof(compatible_uart)); + qemu_devtree_setprop_cells(fdt, "/soc/uart", "reg", base, len); + qemu_devtree_setprop_cell(fdt, "/soc/uart", "interrupts", irq); + qemu_devtree_setprop_cells(fdt, "/soc/uart", "clocks", clock_phandle, + clock_phandle); + qemu_devtree_setprop(fdt, "/soc/uart", "clock-names", clock_names_uart, + sizeof(clock_names_uart)); + + len = 0x1000; + base = mi->io_base; + mi->io_base += len; + irq = 2; + sysbus_create_simple("sp804", base, pic[irq]); + qemu_devtree_add_subnode(fdt, "/soc/timer"); + qemu_devtree_setprop(fdt, "/soc/timer", "compatible", compatible_timer, + sizeof(compatible_timer)); + qemu_devtree_setprop_cells(fdt, "/soc/timer", "reg", base, len); + qemu_devtree_setprop_cell(fdt, "/soc/timer", "interrupts", irq); + qemu_devtree_setprop_cells(fdt, "/soc/timer", "clocks", clock_phandle, + clock_phandle); + qemu_devtree_setprop(fdt, "/soc/timer", "clock-names", clock_names_timer, + sizeof(clock_names_timer)); +} + +static void *machvirt_dtb(hwaddr addr, const struct arm_boot_info *binfo, + int *fdt_size) +{ + *fdt_size = virt_fdt_size; + return virt_fdt; +} + +static struct arm_boot_info machvirt_binfo; + +static void machvirt_init(QEMUMachineInitArgs *args) +{ + qemu_irq pic[64]; + MemoryRegion *sysmem = get_system_memory(); + int n; + MemoryRegion *ram = g_new(MemoryRegion, 1); + qemu_irq cpu_irq[4]; + DeviceState *dev; + SysBusDevice *busdev; + const char *cpu_model = args->cpu_model; + struct machine_info *mi; + + if (!cpu_model) { + cpu_model = "cortex-a15"; + } + + mi = find_machine_info(cpu_model); + + if (!mi) { + hw_error("No machine info for cpu%s\n", cpu_model); + exit(1); + } + + /* + * Only supported method of starting secondary CPUs is PSCI and + * PSCI is not yet supported with TCG so limit smp_cpus to 1 + * if not kvm. + */ + if (!kvm_enabled() && smp_cpus > 1) { + hw_error("Multiple cpus only supported with kvm\n"); + exit(1); + } + + if (ram_size > mi->max_mem) { + fprintf(stderr, "mach-virt: cannot model more than 30GB RAM\n"); + exit(1); + } + + virt_fdt = initial_fdt(mi); + fdt_add_timer_nodes(virt_fdt, smp_cpus); + + for (n = 0; n < smp_cpus; n++) { + ARMCPU *cpu; + qemu_irq *irqp; + + cpu = cpu_arm_init(cpu_model); + if (!cpu) { + fprintf(stderr, "Unable to find CPU definition %s\n", cpu_model); + exit(1); + } + irqp = arm_pic_init_cpu(cpu); + cpu_irq[n] = irqp[ARM_PIC_CPU_IRQ]; + } + fdt_add_cpu_nodes(virt_fdt, smp_cpus); + + memory_region_init_ram(ram, "mach-virt.ram", ram_size); + vmstate_register_ram_global(ram); + memory_region_add_subregion(sysmem, mi->mem_base, ram); + + dev = qdev_create(NULL, mi->qdevname); + qdev_prop_set_uint32(dev, "num-cpu", smp_cpus); + qdev_init_nofail(dev); + busdev = SYS_BUS_DEVICE(dev); + sysbus_mmio_map(busdev, 0, mi->io_base); + fdt_add_gic_node(virt_fdt, mi); + for (n = 0; n < smp_cpus; n++) { + sysbus_connect_irq(busdev, n, cpu_irq[n]); + } + + for (n = 0; n < 64; n++) { + pic[n] = qdev_get_gpio_in(dev, n); + } + + /* + * Not completely virtual yet, add some soc io nodes + */ + fdt_add_soc_nodes(virt_fdt, mi, pic); + + machvirt_binfo.ram_size = args->ram_size; + machvirt_binfo.kernel_filename = args->kernel_filename; + machvirt_binfo.kernel_cmdline = args->kernel_cmdline; + machvirt_binfo.initrd_filename = args->initrd_filename; + machvirt_binfo.nb_cpus = smp_cpus; + machvirt_binfo.board_id = -1; + machvirt_binfo.loader_start = mi->mem_base; + machvirt_binfo.get_dtb = machvirt_dtb; + arm_load_kernel(arm_env_get_cpu(first_cpu), &machvirt_binfo); +} + +static QEMUMachine machvirt_a15_machine = { + .name = "machvirt", + .desc = "ARM Virtual Machine", + .init = machvirt_init, + .max_cpus = 4, + DEFAULT_MACHINE_OPTIONS, +}; + +static void machvirt_machine_init(void) +{ + qemu_register_machine(&machvirt_a15_machine); +} + +machine_init(machvirt_machine_init); -- cgit v1.2.3