arm_boot.c 12.4 KB
Newer Older
1
/*
2 3
 * ARM kernel loader.
 *
P
pbrook 已提交
4
 * Copyright (c) 2006-2007 CodeSourcery.
5 6
 * Written by Paul Brook
 *
M
Matthew Fernandez 已提交
7
 * This code is licensed under the GPL.
8 9
 */

G
Grant Likely 已提交
10
#include "config.h"
P
pbrook 已提交
11 12 13
#include "hw.h"
#include "arm-misc.h"
#include "sysemu.h"
G
Grant Likely 已提交
14
#include "boards.h"
B
Blue Swirl 已提交
15 16
#include "loader.h"
#include "elf.h"
G
Grant Likely 已提交
17
#include "device_tree.h"
18 19 20

#define KERNEL_ARGS_ADDR 0x100
#define KERNEL_LOAD_ADDR 0x00010000
21
#define INITRD_LOAD_ADDR 0x00d00000
22 23 24 25

/* The worlds second smallest bootloader.  Set r0-r2, then jump to kernel.  */
static uint32_t bootloader[] = {
  0xe3a00000, /* mov     r0, #0 */
26 27 28 29
  0xe59f1004, /* ldr     r1, [pc, #4] */
  0xe59f2004, /* ldr     r2, [pc, #4] */
  0xe59ff004, /* ldr     pc, [pc, #4] */
  0, /* Board ID */
30 31 32 33
  0, /* Address of kernel args.  Set by integratorcp_init.  */
  0  /* Kernel entry point.  Set by integratorcp_init.  */
};

34 35 36 37 38 39 40 41 42 43 44 45 46 47
/* 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.
 */
P
pbrook 已提交
48
static uint32_t smpboot[] = {
49
  0xe59f201c, /* ldr r2, gic_cpu_if */
50 51
  0xe59f001c, /* ldr r0, startaddr */
  0xe3a01001, /* mov r1, #1 */
52
  0xe5821000, /* str r1, [r2] */
P
pbrook 已提交
53 54
  0xe320f003, /* wfi */
  0xe5901000, /* ldr     r1, [r0] */
P
Paul Brook 已提交
55 56
  0xe1110001, /* tst     r1, r1 */
  0x0afffffb, /* beq     <wfi> */
P
Paul Brook 已提交
57
  0xe12fff11, /* bx      r1 */
58
  0,          /* gic_cpu_if: base address of GIC CPU interface */
59
  0           /* bootreg: Boot register address is held here */
P
pbrook 已提交
60 61
};

62 63 64 65 66
static void default_write_secondary(CPUState *env,
                                    const struct arm_boot_info *info)
{
    int n;
    smpboot[ARRAY_SIZE(smpboot) - 1] = info->smp_bootreg_addr;
67
    smpboot[ARRAY_SIZE(smpboot) - 2] = info->gic_cpu_if_addr;
68 69 70 71 72 73 74 75 76 77 78 79 80 81
    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);
}

static void default_reset_secondary(CPUState *env,
                                    const struct arm_boot_info *info)
{
    stl_phys_notdirty(info->smp_bootreg_addr, 0);
    env->regs[15] = info->smp_loader_start;
}

82 83 84 85 86
#define WRITE_WORD(p, value) do { \
    stl_phys_notdirty(p, value);  \
    p += 4;                       \
} while (0)

87
static void set_kernel_args(const struct arm_boot_info *info)
88
{
89 90
    int initrd_size = info->initrd_size;
    target_phys_addr_t base = info->loader_start;
A
Anthony Liguori 已提交
91
    target_phys_addr_t p;
92

93
    p = base + KERNEL_ARGS_ADDR;
94
    /* ATAG_CORE */
95 96 97 98 99
    WRITE_WORD(p, 5);
    WRITE_WORD(p, 0x54410001);
    WRITE_WORD(p, 1);
    WRITE_WORD(p, 0x1000);
    WRITE_WORD(p, 0);
100
    /* ATAG_MEM */
101
    /* TODO: handle multiple chips on one ATAG list */
102 103 104 105
    WRITE_WORD(p, 4);
    WRITE_WORD(p, 0x54410002);
    WRITE_WORD(p, info->ram_size);
    WRITE_WORD(p, info->loader_start);
106 107
    if (initrd_size) {
        /* ATAG_INITRD2 */
108 109 110 111
        WRITE_WORD(p, 4);
        WRITE_WORD(p, 0x54420005);
        WRITE_WORD(p, info->loader_start + INITRD_LOAD_ADDR);
        WRITE_WORD(p, initrd_size);
112
    }
113
    if (info->kernel_cmdline && *info->kernel_cmdline) {
114 115 116
        /* ATAG_CMDLINE */
        int cmdline_size;

117
        cmdline_size = strlen(info->kernel_cmdline);
118 119
        cpu_physical_memory_write(p + 8, (void *)info->kernel_cmdline,
                                  cmdline_size + 1);
120
        cmdline_size = (cmdline_size >> 2) + 1;
121 122 123
        WRITE_WORD(p, cmdline_size + 2);
        WRITE_WORD(p, 0x54410009);
        p += cmdline_size * 4;
124
    }
125 126 127
    if (info->atag_board) {
        /* ATAG_BOARD */
        int atag_board_len;
128
        uint8_t atag_board_buf[0x1000];
129

130 131 132 133
        atag_board_len = (info->atag_board(info, atag_board_buf) + 3) & ~3;
        WRITE_WORD(p, (atag_board_len + 8) >> 2);
        WRITE_WORD(p, 0x414f4d50);
        cpu_physical_memory_write(p, atag_board_buf, atag_board_len);
134 135
        p += atag_board_len;
    }
136
    /* ATAG_END */
137 138
    WRITE_WORD(p, 0);
    WRITE_WORD(p, 0);
139 140
}

141
static void set_kernel_args_old(const struct arm_boot_info *info)
142
{
A
Anthony Liguori 已提交
143
    target_phys_addr_t p;
144
    const char *s;
145 146
    int initrd_size = info->initrd_size;
    target_phys_addr_t base = info->loader_start;
147 148

    /* see linux/include/asm-arm/setup.h */
149
    p = base + KERNEL_ARGS_ADDR;
150
    /* page_size */
151
    WRITE_WORD(p, 4096);
152
    /* nr_pages */
153
    WRITE_WORD(p, info->ram_size / 4096);
154
    /* ramdisk_size */
155
    WRITE_WORD(p, 0);
156 157 158 159
#define FLAG_READONLY	1
#define FLAG_RDLOAD	4
#define FLAG_RDPROMPT	8
    /* flags */
160
    WRITE_WORD(p, FLAG_READONLY | FLAG_RDLOAD | FLAG_RDPROMPT);
161
    /* rootdev */
162
    WRITE_WORD(p, (31 << 8) | 0);	/* /dev/mtdblock0 */
163
    /* video_num_cols */
164
    WRITE_WORD(p, 0);
165
    /* video_num_rows */
166
    WRITE_WORD(p, 0);
167
    /* video_x */
168
    WRITE_WORD(p, 0);
169
    /* video_y */
170
    WRITE_WORD(p, 0);
171
    /* memc_control_reg */
172
    WRITE_WORD(p, 0);
173 174 175 176
    /* unsigned char sounddefault */
    /* unsigned char adfsdrives */
    /* unsigned char bytes_per_char_h */
    /* unsigned char bytes_per_char_v */
177
    WRITE_WORD(p, 0);
178
    /* pages_in_bank[4] */
179 180 181 182
    WRITE_WORD(p, 0);
    WRITE_WORD(p, 0);
    WRITE_WORD(p, 0);
    WRITE_WORD(p, 0);
183
    /* pages_in_vram */
184
    WRITE_WORD(p, 0);
185 186
    /* initrd_start */
    if (initrd_size)
187
        WRITE_WORD(p, info->loader_start + INITRD_LOAD_ADDR);
188
    else
189
        WRITE_WORD(p, 0);
190
    /* initrd_size */
191
    WRITE_WORD(p, initrd_size);
192
    /* rd_start */
193
    WRITE_WORD(p, 0);
194
    /* system_rev */
195
    WRITE_WORD(p, 0);
196
    /* system_serial_low */
197
    WRITE_WORD(p, 0);
198
    /* system_serial_high */
199
    WRITE_WORD(p, 0);
200
    /* mem_fclk_21285 */
201
    WRITE_WORD(p, 0);
202
    /* zero unused fields */
203 204 205 206 207 208 209 210 211
    while (p < base + KERNEL_ARGS_ADDR + 256 + 1024) {
        WRITE_WORD(p, 0);
    }
    s = info->kernel_cmdline;
    if (s) {
        cpu_physical_memory_write(p, (void *)s, strlen(s) + 1);
    } else {
        WRITE_WORD(p, 0);
    }
212 213
}

G
Grant Likely 已提交
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274
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
}

275
static void do_cpu_reset(void *opaque)
P
Paul Brook 已提交
276 277
{
    CPUState *env = opaque;
278
    const struct arm_boot_info *info = env->boot_info;
P
Paul Brook 已提交
279 280 281 282 283 284 285 286

    cpu_reset(env);
    if (info) {
        if (!info->is_linux) {
            /* Jump to the entry point.  */
            env->regs[15] = info->entry & 0xfffffffe;
            env->thumb = info->entry & 1;
        } else {
287 288
            if (env == first_cpu) {
                env->regs[15] = info->loader_start;
G
Grant Likely 已提交
289 290 291 292 293 294
                if (!info->dtb_filename) {
                    if (old_param) {
                        set_kernel_args_old(info);
                    } else {
                        set_kernel_args(info);
                    }
295
                }
P
Paul Brook 已提交
296
            } else {
297
                info->secondary_cpu_reset_hook(env, info);
P
Paul Brook 已提交
298 299 300 301 302
            }
        }
    }
}

303
void arm_load_kernel(CPUState *env, struct arm_boot_info *info)
304 305 306 307
{
    int kernel_size;
    int initrd_size;
    int n;
P
pbrook 已提交
308 309
    int is_linux = 0;
    uint64_t elf_entry;
A
Anthony Liguori 已提交
310
    target_phys_addr_t entry;
B
Blue Swirl 已提交
311
    int big_endian;
G
Grant Likely 已提交
312
    QemuOpts *machine_opts;
313 314

    /* Load the kernel.  */
315
    if (!info->kernel_filename) {
316 317 318
        fprintf(stderr, "Kernel image must be specified\n");
        exit(1);
    }
P
pbrook 已提交
319

G
Grant Likely 已提交
320 321 322 323 324 325 326
    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;
    }

327 328 329 330 331 332 333
    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;
    }

P
Paul Brook 已提交
334 335
    if (info->nb_cpus == 0)
        info->nb_cpus = 1;
336

B
Blue Swirl 已提交
337 338 339 340 341 342
#ifdef TARGET_WORDS_BIGENDIAN
    big_endian = 1;
#else
    big_endian = 0;
#endif

P
pbrook 已提交
343
    /* Assume that raw images are linux kernels, and ELF images are not.  */
344 345
    kernel_size = load_elf(info->kernel_filename, NULL, NULL, &elf_entry,
                           NULL, NULL, big_endian, ELF_MACHINE, 1);
P
pbrook 已提交
346 347
    entry = elf_entry;
    if (kernel_size < 0) {
348 349
        kernel_size = load_uimage(info->kernel_filename, &entry, NULL,
                                  &is_linux);
P
pbrook 已提交
350 351
    }
    if (kernel_size < 0) {
352
        entry = info->loader_start + KERNEL_LOAD_ADDR;
P
pbrook 已提交
353 354
        kernel_size = load_image_targphys(info->kernel_filename, entry,
                                          ram_size - KERNEL_LOAD_ADDR);
P
pbrook 已提交
355 356 357
        is_linux = 1;
    }
    if (kernel_size < 0) {
358 359
        fprintf(stderr, "qemu: could not load kernel '%s'\n",
                info->kernel_filename);
P
pbrook 已提交
360 361
        exit(1);
    }
P
Paul Brook 已提交
362 363
    info->entry = entry;
    if (is_linux) {
364
        if (info->initrd_filename) {
P
pbrook 已提交
365 366 367 368
            initrd_size = load_image_targphys(info->initrd_filename,
                                              info->loader_start
                                              + INITRD_LOAD_ADDR,
                                              ram_size - INITRD_LOAD_ADDR);
P
pbrook 已提交
369 370
            if (initrd_size < 0) {
                fprintf(stderr, "qemu: could not load initrd '%s'\n",
371
                        info->initrd_filename);
P
pbrook 已提交
372 373 374 375 376
                exit(1);
            }
        } else {
            initrd_size = 0;
        }
G
Grant Likely 已提交
377 378
        info->initrd_size = initrd_size;

379
        bootloader[4] = info->board_id;
G
Grant Likely 已提交
380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395

        /* 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;
        }
P
pbrook 已提交
396
        bootloader[6] = entry;
397
        for (n = 0; n < sizeof(bootloader) / 4; n++) {
P
Paul Brook 已提交
398
            bootloader[n] = tswap32(bootloader[n]);
399
        }
P
Paul Brook 已提交
400 401
        rom_add_blob_fixed("bootloader", bootloader, sizeof(bootloader),
                           info->loader_start);
402
        if (info->nb_cpus > 1) {
403
            info->write_secondary_boot(env, info);
404
        }
405
    }
P
Paul Brook 已提交
406
    info->is_linux = is_linux;
407 408 409 410 411

    for (; env; env = env->next_cpu) {
        env->boot_info = info;
        qemu_register_reset(do_cpu_reset, env);
    }
412
}