aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Rigby <john.rigby@linaro.org>2013-04-30 09:15:18 -0600
committerJohn Rigby <john.rigby@linaro.org>2013-05-10 13:57:29 -0600
commit5748690ddd7f4f3b8a4615641332574d3a76d3ab (patch)
treec48f3191a9d45a9437cdf0b01acc5933a2d06d64
parentbdfb37cc74c046dc55d3b022bb593c01d1119ba3 (diff)
ARM: Add mach-virt platformmachvirt-wip-v4
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 <john.rigby@linaro.org>
-rw-r--r--hw/arm/Makefile.objs2
-rw-r--r--hw/arm/virt.c437
2 files changed, 438 insertions, 1 deletions
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 <http://www.gnu.org/licenses/>.
+ *
+ * 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);