X-Git-Url: https://repo.jachan.dev/qemu.git/blobdiff_plain/ca20cf32ab3d945155141ef737f5d08ebb373e1d..5d309320e3e419187b48b4537d403daf95835bc6:/hw/arm_boot.c diff --git a/hw/arm_boot.c b/hw/arm_boot.c index a8a38c5a36..8eabfb204f 100644 --- a/hw/arm_boot.c +++ b/hw/arm_boot.c @@ -4,55 +4,81 @@ * Copyright (c) 2006-2007 CodeSourcery. * Written by Paul Brook * - * This code is licenced under the GPL. + * This code is licensed under the GPL. */ +#include "config.h" #include "hw.h" #include "arm-misc.h" #include "sysemu.h" +#include "boards.h" #include "loader.h" #include "elf.h" +#include "device_tree.h" #define KERNEL_ARGS_ADDR 0x100 #define KERNEL_LOAD_ADDR 0x00010000 -#define INITRD_LOAD_ADDR 0x00800000 +#define INITRD_LOAD_ADDR 0x00d00000 /* The worlds second smallest bootloader. Set r0-r2, then jump to kernel. */ static uint32_t bootloader[] = { 0xe3a00000, /* mov r0, #0 */ - 0xe3a01000, /* mov r1, #0x?? */ - 0xe3811c00, /* orr r1, r1, #0x??00 */ - 0xe59f2000, /* ldr r2, [pc, #0] */ - 0xe59ff000, /* ldr pc, [pc, #0] */ + 0xe59f1004, /* ldr r1, [pc, #4] */ + 0xe59f2004, /* ldr r2, [pc, #4] */ + 0xe59ff004, /* ldr pc, [pc, #4] */ + 0, /* Board ID */ 0, /* Address of kernel args. Set by integratorcp_init. */ 0 /* Kernel entry point. Set by integratorcp_init. */ }; -/* Entry point for secondary CPUs. Enable interrupt controller and - Issue WFI until start address is written to system controller. */ +/* Handling for secondary CPU boot in a multicore system. + * Unlike the uniprocessor/primary CPU boot, this is platform + * dependent. The default code here is based on the secondary + * CPU boot protocol used on realview/vexpress boards, with + * some parameterisation to increase its flexibility. + * QEMU platform models for which this code is not appropriate + * should override write_secondary_boot and secondary_cpu_reset_hook + * instead. + * + * This code enables the interrupt controllers for the secondary + * CPUs and then puts all the secondary CPUs into a loop waiting + * for an interprocessor interrupt and polling a configurable + * location for the kernel secondary CPU entry point. + */ static uint32_t smpboot[] = { - 0xe3a00201, /* mov r0, #0x10000000 */ - 0xe3800601, /* orr r0, r0, #0x001000000 */ - 0xe3a01001, /* mov r1, #1 */ - 0xe5801100, /* str r1, [r0, #0x100] */ - 0xe3a00201, /* mov r0, #0x10000000 */ - 0xe3800030, /* orr r0, #0x30 */ + 0xe59f201c, /* ldr r2, gic_cpu_if */ + 0xe59f001c, /* ldr r0, startaddr */ + 0xe3a01001, /* mov r1, #1 */ + 0xe5821000, /* str r1, [r2] */ 0xe320f003, /* wfi */ 0xe5901000, /* ldr r1, [r0] */ - 0xe3110003, /* tst r1, #3 */ - 0x1afffffb, /* bne */ - 0xe12fff11 /* bx r1 */ + 0xe1110001, /* tst r1, r1 */ + 0x0afffffb, /* beq */ + 0xe12fff11, /* bx r1 */ + 0, /* gic_cpu_if: base address of GIC CPU interface */ + 0 /* bootreg: Boot register address is held here */ }; -static void main_cpu_reset(void *opaque) +static void default_write_secondary(ARMCPU *cpu, + const struct arm_boot_info *info) { - CPUState *env = opaque; + int n; + smpboot[ARRAY_SIZE(smpboot) - 1] = info->smp_bootreg_addr; + smpboot[ARRAY_SIZE(smpboot) - 2] = info->gic_cpu_if_addr; + for (n = 0; n < ARRAY_SIZE(smpboot); n++) { + smpboot[n] = tswap32(smpboot[n]); + } + rom_add_blob_fixed("smpboot", smpboot, sizeof(smpboot), + info->smp_loader_start); +} - cpu_reset(env); - if (env->boot_info) - arm_load_kernel(env, env->boot_info); +static void default_reset_secondary(ARMCPU *cpu, + const struct arm_boot_info *info) +{ + CPUARMState *env = &cpu->env; - /* TODO: Reset secondary CPUs. */ + stl_phys_notdirty(info->smp_bootreg_addr, 0); + env->regs[15] = info->smp_loader_start; } #define WRITE_WORD(p, value) do { \ @@ -60,9 +86,10 @@ static void main_cpu_reset(void *opaque) p += 4; \ } while (0) -static void set_kernel_args(struct arm_boot_info *info, - int initrd_size, target_phys_addr_t base) +static void set_kernel_args(const struct arm_boot_info *info) { + int initrd_size = info->initrd_size; + target_phys_addr_t base = info->loader_start; target_phys_addr_t p; p = base + KERNEL_ARGS_ADDR; @@ -113,12 +140,12 @@ static void set_kernel_args(struct arm_boot_info *info, WRITE_WORD(p, 0); } -static void set_kernel_args_old(struct arm_boot_info *info, - int initrd_size, target_phys_addr_t base) +static void set_kernel_args_old(const struct arm_boot_info *info) { target_phys_addr_t p; const char *s; - + int initrd_size = info->initrd_size; + target_phys_addr_t base = info->loader_start; /* see linux/include/asm-arm/setup.h */ p = base + KERNEL_ARGS_ADDR; @@ -186,8 +213,99 @@ static void set_kernel_args_old(struct arm_boot_info *info, } } -void arm_load_kernel(CPUState *env, struct arm_boot_info *info) +static int load_dtb(target_phys_addr_t addr, const struct arm_boot_info *binfo) { +#ifdef CONFIG_FDT + uint32_t mem_reg_property[] = { cpu_to_be32(binfo->loader_start), + cpu_to_be32(binfo->ram_size) }; + void *fdt = NULL; + char *filename; + int size, rc; + + filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, binfo->dtb_filename); + if (!filename) { + fprintf(stderr, "Couldn't open dtb file %s\n", binfo->dtb_filename); + return -1; + } + + fdt = load_device_tree(filename, &size); + if (!fdt) { + fprintf(stderr, "Couldn't open dtb file %s\n", filename); + g_free(filename); + return -1; + } + g_free(filename); + + rc = qemu_devtree_setprop(fdt, "/memory", "reg", mem_reg_property, + sizeof(mem_reg_property)); + if (rc < 0) { + fprintf(stderr, "couldn't set /memory/reg\n"); + } + + rc = qemu_devtree_setprop_string(fdt, "/chosen", "bootargs", + binfo->kernel_cmdline); + if (rc < 0) { + fprintf(stderr, "couldn't set /chosen/bootargs\n"); + } + + if (binfo->initrd_size) { + rc = qemu_devtree_setprop_cell(fdt, "/chosen", "linux,initrd-start", + binfo->loader_start + INITRD_LOAD_ADDR); + if (rc < 0) { + fprintf(stderr, "couldn't set /chosen/linux,initrd-start\n"); + } + + rc = qemu_devtree_setprop_cell(fdt, "/chosen", "linux,initrd-end", + binfo->loader_start + INITRD_LOAD_ADDR + + binfo->initrd_size); + if (rc < 0) { + fprintf(stderr, "couldn't set /chosen/linux,initrd-end\n"); + } + } + + cpu_physical_memory_write(addr, fdt, size); + + return 0; + +#else + fprintf(stderr, "Device tree requested, " + "but qemu was compiled without fdt support\n"); + return -1; +#endif +} + +static void do_cpu_reset(void *opaque) +{ + ARMCPU *cpu = opaque; + CPUARMState *env = &cpu->env; + const struct arm_boot_info *info = env->boot_info; + + cpu_reset(CPU(cpu)); + if (info) { + if (!info->is_linux) { + /* Jump to the entry point. */ + env->regs[15] = info->entry & 0xfffffffe; + env->thumb = info->entry & 1; + } else { + if (env == first_cpu) { + env->regs[15] = info->loader_start; + if (!info->dtb_filename) { + if (old_param) { + set_kernel_args_old(info); + } else { + set_kernel_args(info); + } + } + } else { + info->secondary_cpu_reset_hook(cpu, info); + } + } + } +} + +void arm_load_kernel(CPUARMState *env, struct arm_boot_info *info) +{ + ARMCPU *cpu = arm_env_get_cpu(env); int kernel_size; int initrd_size; int n; @@ -195,6 +313,7 @@ void arm_load_kernel(CPUState *env, struct arm_boot_info *info) uint64_t elf_entry; target_phys_addr_t entry; int big_endian; + QemuOpts *machine_opts; /* Load the kernel. */ if (!info->kernel_filename) { @@ -202,12 +321,22 @@ void arm_load_kernel(CPUState *env, struct arm_boot_info *info) exit(1); } - if (!env->boot_info) { - if (info->nb_cpus == 0) - info->nb_cpus = 1; - env->boot_info = info; - qemu_register_reset(main_cpu_reset, env); + machine_opts = qemu_opts_find(qemu_find_opts("machine"), 0); + if (machine_opts) { + info->dtb_filename = qemu_opt_get(machine_opts, "dtb"); + } else { + info->dtb_filename = NULL; + } + + if (!info->secondary_cpu_reset_hook) { + info->secondary_cpu_reset_hook = default_reset_secondary; } + if (!info->write_secondary_boot) { + info->write_secondary_boot = default_write_secondary; + } + + if (info->nb_cpus == 0) + info->nb_cpus = 1; #ifdef TARGET_WORDS_BIGENDIAN big_endian = 1; @@ -216,8 +345,8 @@ void arm_load_kernel(CPUState *env, struct arm_boot_info *info) #endif /* Assume that raw images are linux kernels, and ELF images are not. */ - kernel_size = load_elf(info->kernel_filename, 0, &elf_entry, NULL, NULL, - big_endian, ELF_MACHINE, 1); + kernel_size = load_elf(info->kernel_filename, NULL, NULL, &elf_entry, + NULL, NULL, big_endian, ELF_MACHINE, 1); entry = elf_entry; if (kernel_size < 0) { kernel_size = load_uimage(info->kernel_filename, &entry, NULL, @@ -234,11 +363,8 @@ void arm_load_kernel(CPUState *env, struct arm_boot_info *info) info->kernel_filename); exit(1); } - if (!is_linux) { - /* Jump to the entry point. */ - env->regs[15] = entry & 0xfffffffe; - env->thumb = entry & 1; - } else { + info->entry = entry; + if (is_linux) { if (info->initrd_filename) { initrd_size = load_image_targphys(info->initrd_filename, info->loader_start @@ -252,21 +378,40 @@ void arm_load_kernel(CPUState *env, struct arm_boot_info *info) } else { initrd_size = 0; } - bootloader[1] |= info->board_id & 0xff; - bootloader[2] |= (info->board_id >> 8) & 0xff; - bootloader[5] = info->loader_start + KERNEL_ARGS_ADDR; + info->initrd_size = initrd_size; + + bootloader[4] = info->board_id; + + /* for device tree boot, we pass the DTB directly in r2. Otherwise + * we point to the kernel args. + */ + if (info->dtb_filename) { + /* Place the DTB after the initrd in memory */ + target_phys_addr_t dtb_start = TARGET_PAGE_ALIGN(info->loader_start + + INITRD_LOAD_ADDR + + initrd_size); + if (load_dtb(dtb_start, info)) { + exit(1); + } + bootloader[5] = dtb_start; + } else { + bootloader[5] = info->loader_start + KERNEL_ARGS_ADDR; + } bootloader[6] = entry; for (n = 0; n < sizeof(bootloader) / 4; n++) { - stl_phys_notdirty(info->loader_start + (n * 4), bootloader[n]); + bootloader[n] = tswap32(bootloader[n]); } + rom_add_blob_fixed("bootloader", bootloader, sizeof(bootloader), + info->loader_start); if (info->nb_cpus > 1) { - for (n = 0; n < sizeof(smpboot) / 4; n++) { - stl_phys_notdirty(info->smp_loader_start + (n * 4), smpboot[n]); - } + info->write_secondary_boot(cpu, info); } - if (old_param) - set_kernel_args_old(info, initrd_size, info->loader_start); - else - set_kernel_args(info, initrd_size, info->loader_start); + } + info->is_linux = is_linux; + + for (; env; env = env->next_cpu) { + cpu = arm_env_get_cpu(env); + env->boot_info = info; + qemu_register_reset(do_cpu_reset, cpu); } }