| 1 | /* |
| 2 | * (C) Copyright 2008 Openmoko, Inc. |
| 3 | * Author: Andy Green <andy@openmoko.org> |
| 4 | * |
| 5 | * Parse the U-Boot header and Boot Linux |
| 6 | * based on various code from U-Boot |
| 7 | * |
| 8 | * This program is free software; you can redistribute it and/or |
| 9 | * modify it under the terms of the GNU General Public License as |
| 10 | * published by the Free Software Foundation; either version 2 of |
| 11 | * the License, or (at your option) any later version. |
| 12 | * |
| 13 | * This program is distributed in the hope that it will be useful, |
| 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 16 | * GNU General Public License for more details. |
| 17 | * |
| 18 | * You should have received a copy of the GNU General Public License |
| 19 | * along with this program; if not, write to the Free Software |
| 20 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, |
| 21 | * MA 02111-1307 USA |
| 22 | */ |
| 23 | |
| 24 | #include <qi.h> |
| 25 | #include <neo_gta02.h> |
| 26 | #include "blink_led.h" |
| 27 | #include <string.h> |
| 28 | #define __ARM__ |
| 29 | #include <image.h> |
| 30 | #include <setup.h> |
| 31 | #include <ext2.h> |
| 32 | |
| 33 | |
| 34 | typedef void (*the_kernel_fn)(int zero, int arch, uint params); |
| 35 | |
| 36 | unsigned long partition_offset_blocks = 0; |
| 37 | unsigned long partition_length_blocks = 0; |
| 38 | |
| 39 | struct kernel_source const * this_kernel = 0; |
| 40 | |
| 41 | static const int INITRD_OFFSET = (8 * 1024 * 1024); |
| 42 | |
| 43 | |
| 44 | int raise(int n) |
| 45 | { |
| 46 | return 0; |
| 47 | } |
| 48 | |
| 49 | static void indicate(enum ui_indication ui_indication) |
| 50 | { |
| 51 | if (this_board->set_ui_indication) |
| 52 | (this_board->set_ui_indication)(ui_indication); |
| 53 | } |
| 54 | |
| 55 | static int read_file(const char * filepath, u8 * destination, int size) |
| 56 | { |
| 57 | int len = size; |
| 58 | int ret; |
| 59 | |
| 60 | switch (this_kernel->filesystem) { |
| 61 | case FS_EXT2: |
| 62 | if (!ext2fs_mount()) { |
| 63 | puts("Unable to mount ext2 filesystem\n"); |
| 64 | indicate(UI_IND_MOUNT_FAIL); |
| 65 | return -2; /* death */ |
| 66 | } |
| 67 | puts(" EXT2 open: "); |
| 68 | puts(filepath); |
| 69 | len = ext2fs_open(filepath); |
| 70 | if (len < 0) { |
| 71 | puts(" Open failed\n"); |
| 72 | return -1; |
| 73 | } |
| 74 | puts(" OK\n"); |
| 75 | ret = ext2fs_read((char *)destination, size); |
| 76 | if (ret < 0) { |
| 77 | puts(" Read failed\n"); |
| 78 | return -1; |
| 79 | } |
| 80 | break; |
| 81 | |
| 82 | case FS_FAT: |
| 83 | /* FIXME */ |
| 84 | case FS_RAW: |
| 85 | /* any filename-related request in raw filesystem will fail */ |
| 86 | if (filepath) |
| 87 | return -1; |
| 88 | puts(" RAW open: +"); |
| 89 | printdec(partition_offset_blocks); |
| 90 | puts(" 512-byte blocks\n"); |
| 91 | if (this_kernel->block_read(destination, |
| 92 | partition_offset_blocks, size >> 9) < 0) { |
| 93 | puts("Bad kernel header\n"); |
| 94 | return -1; |
| 95 | } |
| 96 | break; |
| 97 | } |
| 98 | |
| 99 | return len; |
| 100 | } |
| 101 | |
| 102 | static int do_block_init(void) |
| 103 | { |
| 104 | static void * last_block_init = NULL; |
| 105 | static int last_block_init_result = 0; |
| 106 | int fresh = 0; |
| 107 | |
| 108 | /* if this device needs initializing, try to init it */ |
| 109 | if (!this_kernel->block_init) |
| 110 | return 1; /* happy */ |
| 111 | |
| 112 | /* |
| 113 | * cache result to limit attempts for same |
| 114 | * block device to one time |
| 115 | */ |
| 116 | if (this_kernel->block_init != last_block_init) { |
| 117 | last_block_init = this_kernel->block_init; |
| 118 | last_block_init_result = (this_kernel->block_init)(); |
| 119 | fresh = 1; |
| 120 | } |
| 121 | |
| 122 | if (last_block_init_result) { |
| 123 | puts("block device init failed\n"); |
| 124 | if (fresh) |
| 125 | indicate(UI_IND_MOUNT_FAIL); |
| 126 | |
| 127 | return 0; /* failed */ |
| 128 | } |
| 129 | last_block_init = this_kernel->block_init; |
| 130 | |
| 131 | return 1; /* happy */ |
| 132 | } |
| 133 | |
| 134 | static int do_partitions(void *kernel_dram) |
| 135 | { |
| 136 | unsigned char *p = kernel_dram; |
| 137 | |
| 138 | /* if there's a partition table implied, parse it, otherwise |
| 139 | * just use a fixed offset |
| 140 | */ |
| 141 | if (!this_kernel->partition_index) { |
| 142 | partition_offset_blocks = |
| 143 | this_kernel->offset_blocks512_if_no_partition; |
| 144 | return 1; |
| 145 | } |
| 146 | |
| 147 | if ((int)this_kernel->block_read(kernel_dram, 0, 4) < 0) { |
| 148 | puts("Bad partition read\n"); |
| 149 | indicate(UI_IND_MOUNT_FAIL); |
| 150 | return 0; |
| 151 | } |
| 152 | |
| 153 | if ((p[0x1fe] != 0x55) || (p[0x1ff] != 0xaa)) { |
| 154 | puts("partition signature missing\n"); |
| 155 | indicate(UI_IND_MOUNT_FAIL); |
| 156 | return 0; |
| 157 | } |
| 158 | |
| 159 | p += 0x1be + 8 + (0x10 * (this_kernel->partition_index - 1)); |
| 160 | |
| 161 | partition_offset_blocks = (((u32)p[3]) << 24) | |
| 162 | (((u32)p[2]) << 16) | |
| 163 | (((u32)p[1]) << 8) | |
| 164 | p[0]; |
| 165 | partition_length_blocks = (((u32)p[7]) << 24) | |
| 166 | (((u32)p[6]) << 16) | |
| 167 | (((u32)p[5]) << 8) | |
| 168 | p[4]; |
| 169 | |
| 170 | puts(" Partition: "); |
| 171 | printdec(this_kernel->partition_index); |
| 172 | puts(" start +"); |
| 173 | printdec(partition_offset_blocks); |
| 174 | puts(" 512-byte blocks, size "); |
| 175 | printdec(partition_length_blocks / 2048); |
| 176 | puts(" MiB\n"); |
| 177 | |
| 178 | return 1; |
| 179 | } |
| 180 | |
| 181 | static void do_params(unsigned initramfs_len, |
| 182 | const char *commandline_rootfs_append) |
| 183 | { |
| 184 | const struct board_variant * board_variant = |
| 185 | (this_board->get_board_variant)(); |
| 186 | const char *p; |
| 187 | char * cmdline; |
| 188 | struct tag *params = (struct tag *)this_board->linux_tag_placement; |
| 189 | |
| 190 | /* eat leading white space */ |
| 191 | for (p = this_board->commandline_board; *p == ' '; p++); |
| 192 | |
| 193 | /* first tag */ |
| 194 | params->hdr.tag = ATAG_CORE; |
| 195 | params->hdr.size = tag_size(tag_core); |
| 196 | params->u.core.flags = 0; |
| 197 | params->u.core.pagesize = 0; |
| 198 | params->u.core.rootdev = 0; |
| 199 | params = tag_next(params); |
| 200 | |
| 201 | /* revision tag */ |
| 202 | params->hdr.tag = ATAG_REVISION; |
| 203 | params->hdr.size = tag_size(tag_revision); |
| 204 | params->u.revision.rev = board_variant->machine_revision; |
| 205 | params = tag_next(params); |
| 206 | |
| 207 | /* memory tags */ |
| 208 | params->hdr.tag = ATAG_MEM; |
| 209 | params->hdr.size = tag_size(tag_mem32); |
| 210 | params->u.mem.start = this_board->linux_mem_start; |
| 211 | params->u.mem.size = this_board->linux_mem_size; |
| 212 | params = tag_next(params); |
| 213 | |
| 214 | if (this_kernel->initramfs_filepath) { |
| 215 | /* INITRD2 tag */ |
| 216 | params->hdr.tag = ATAG_INITRD2; |
| 217 | params->hdr.size = tag_size(tag_initrd); |
| 218 | params->u.initrd.start = this_board->linux_mem_start + |
| 219 | INITRD_OFFSET; |
| 220 | params->u.initrd.size = initramfs_len; |
| 221 | params = tag_next(params); |
| 222 | } |
| 223 | |
| 224 | /* kernel commandline */ |
| 225 | |
| 226 | cmdline = params->u.cmdline.cmdline; |
| 227 | |
| 228 | /* start with the fixed device part of the commandline */ |
| 229 | |
| 230 | cmdline += strlen(strcpy(cmdline, p)); |
| 231 | |
| 232 | /* if the board itself needs a computed commandline, add it now */ |
| 233 | |
| 234 | if (this_board->append_device_specific_cmdline) |
| 235 | cmdline = (this_board->append_device_specific_cmdline)(cmdline); |
| 236 | |
| 237 | /* If he is giving an append commandline for this rootfs, apply that */ |
| 238 | |
| 239 | if (this_kernel->commandline_append) |
| 240 | cmdline += strlen(strcpy(cmdline, |
| 241 | this_kernel->commandline_append)); |
| 242 | if (commandline_rootfs_append[0]) |
| 243 | cmdline += strlen(strcpy(cmdline, |
| 244 | commandline_rootfs_append)); |
| 245 | |
| 246 | /* deal with any trailing newlines that hitched a ride */ |
| 247 | |
| 248 | while (*(cmdline - 1) == '\n') |
| 249 | cmdline--; |
| 250 | |
| 251 | *cmdline = '\0'; |
| 252 | |
| 253 | /* |
| 254 | * if he's still holding down the UI_ACTION_SKIPKERNEL key |
| 255 | * now we finished loading the kernel, take it to mean he wants |
| 256 | * to have the debugging options added to the commandline |
| 257 | */ |
| 258 | |
| 259 | if (this_board->commandline_board_debug && this_board->get_ui_debug) |
| 260 | if ((this_board->get_ui_debug)()) |
| 261 | cmdline += strlen(strcpy(cmdline, this_board-> |
| 262 | commandline_board_debug)); |
| 263 | |
| 264 | params->hdr.tag = ATAG_CMDLINE; |
| 265 | params->hdr.size = (sizeof(struct tag_header) + |
| 266 | strlen(params->u.cmdline.cmdline) + 1 + 4) >> 2; |
| 267 | |
| 268 | puts(" Cmdline: "); |
| 269 | puts(params->u.cmdline.cmdline); |
| 270 | puts("\n"); |
| 271 | |
| 272 | params = tag_next(params); |
| 273 | |
| 274 | /* needs to always be the last tag */ |
| 275 | params->hdr.tag = ATAG_NONE; |
| 276 | params->hdr.size = 0; |
| 277 | } |
| 278 | |
| 279 | static int do_crc(const image_header_t *hdr, const void *kernel_dram) |
| 280 | { |
| 281 | unsigned long crc; |
| 282 | |
| 283 | /* |
| 284 | * It's good for now to know that our kernel is intact from |
| 285 | * the storage before we jump into it and maybe crash silently |
| 286 | * even though it costs us some time |
| 287 | */ |
| 288 | crc = crc32(0, kernel_dram + sizeof(image_header_t), |
| 289 | __be32_to_cpu(hdr->ih_size)); |
| 290 | if (crc == __be32_to_cpu(hdr->ih_dcrc)) |
| 291 | return 1; |
| 292 | |
| 293 | puts("\nKernel CRC ERROR: read 0x"); |
| 294 | print32(crc); |
| 295 | puts(" vs hdr CRC 0x"); |
| 296 | print32(__be32_to_cpu(hdr->ih_dcrc)); |
| 297 | puts("\n"); |
| 298 | |
| 299 | return 0; |
| 300 | } |
| 301 | |
| 302 | static the_kernel_fn load_uimage(void *kernel_dram) |
| 303 | { |
| 304 | image_header_t *hdr; |
| 305 | u32 kernel_size; |
| 306 | |
| 307 | hdr = (image_header_t *)kernel_dram; |
| 308 | |
| 309 | if (__be32_to_cpu(hdr->ih_magic) != IH_MAGIC) { |
| 310 | puts("bad magic "); |
| 311 | print32(hdr->ih_magic); |
| 312 | puts("\n"); |
| 313 | return NULL; |
| 314 | } |
| 315 | |
| 316 | puts(" Found: \""); |
| 317 | puts((const char *)hdr->ih_name); |
| 318 | puts("\"\n Size: "); |
| 319 | printdec(__be32_to_cpu(hdr->ih_size) >> 10); |
| 320 | puts(" KiB\n"); |
| 321 | |
| 322 | kernel_size = ((__be32_to_cpu(hdr->ih_size) + |
| 323 | sizeof(image_header_t) + 2048) & ~(2048 - 1)); |
| 324 | |
| 325 | if (read_file(this_kernel->filepath, kernel_dram, kernel_size) < 0) { |
| 326 | indicate(UI_IND_KERNEL_PULL_FAIL); |
| 327 | return NULL; |
| 328 | } |
| 329 | |
| 330 | indicate(UI_IND_KERNEL_PULL_OK); |
| 331 | |
| 332 | if (!do_crc(hdr, kernel_dram)) |
| 333 | return NULL; |
| 334 | |
| 335 | return (the_kernel_fn) (((char *)hdr) + sizeof(image_header_t)); |
| 336 | } |
| 337 | |
| 338 | static the_kernel_fn load_zimage(void *kernel_dram) |
| 339 | { |
| 340 | u32 magic = *(u32 *) (kernel_dram + 0x24); |
| 341 | u32 size = *(u32 *) (kernel_dram + 0x2c); |
| 342 | int got; |
| 343 | |
| 344 | if (magic != 0x016f2818) { |
| 345 | puts("bad magic "); |
| 346 | print32(magic); |
| 347 | puts("\n"); |
| 348 | return NULL; |
| 349 | } |
| 350 | |
| 351 | puts(" Size: "); |
| 352 | printdec(size >> 10); |
| 353 | puts(" KiB\n"); |
| 354 | |
| 355 | got = read_file(this_kernel->filepath, kernel_dram, size); |
| 356 | if (got < 0) { |
| 357 | indicate(UI_IND_KERNEL_PULL_FAIL); |
| 358 | return NULL; |
| 359 | } |
| 360 | |
| 361 | if (got != size) { |
| 362 | puts("short kernel\n"); |
| 363 | return NULL; |
| 364 | } |
| 365 | |
| 366 | indicate(UI_IND_KERNEL_PULL_OK); |
| 367 | |
| 368 | return (the_kernel_fn) kernel_dram; |
| 369 | } |
| 370 | |
| 371 | static void try_this_kernel(void) |
| 372 | { |
| 373 | the_kernel_fn the_kernel; |
| 374 | unsigned int initramfs_len = 0; |
| 375 | static char commandline_rootfs_append[512] = ""; |
| 376 | int ret; |
| 377 | void * kernel_dram = (void *)this_board->linux_mem_start + 0x8000; |
| 378 | |
| 379 | partition_offset_blocks = 0; |
| 380 | partition_length_blocks = 0; |
| 381 | |
| 382 | puts("\nTrying kernel: "); |
| 383 | puts(this_kernel->name); |
| 384 | puts("\n"); |
| 385 | |
| 386 | indicate(UI_IND_MOUNT_PART); |
| 387 | |
| 388 | if (!do_block_init()) |
| 389 | return; |
| 390 | |
| 391 | if (!do_partitions(kernel_dram)) |
| 392 | return; |
| 393 | |
| 394 | /* does he want us to skip this? */ |
| 395 | |
| 396 | ret = read_file(this_board->noboot, kernel_dram, 512); |
| 397 | if (ret != -1) { |
| 398 | /* -2 (mount fail) should make us give up too */ |
| 399 | if (ret >= 0) { |
| 400 | puts(" (Skipping on finding "); |
| 401 | puts(this_board->noboot); |
| 402 | puts(")\n"); |
| 403 | indicate(UI_IND_SKIPPING); |
| 404 | } |
| 405 | return; |
| 406 | } |
| 407 | |
| 408 | /* is there a commandline append file? */ |
| 409 | |
| 410 | commandline_rootfs_append[0] = '\0'; |
| 411 | read_file(this_board->append, (u8 *)commandline_rootfs_append, 512); |
| 412 | |
| 413 | indicate(UI_IND_KERNEL_PULL); |
| 414 | |
| 415 | /* pull the kernel image */ |
| 416 | |
| 417 | if (read_file(this_kernel->filepath, kernel_dram, 4096) < 0) |
| 418 | return; |
| 419 | |
| 420 | the_kernel = load_uimage(kernel_dram); |
| 421 | if (!the_kernel) |
| 422 | the_kernel = load_zimage(kernel_dram); |
| 423 | if (!the_kernel) |
| 424 | return; |
| 425 | |
| 426 | /* initramfs if needed */ |
| 427 | |
| 428 | if (this_kernel->initramfs_filepath) { |
| 429 | indicate(UI_IND_INITRAMFS_PULL); |
| 430 | initramfs_len = read_file(this_kernel->initramfs_filepath, |
| 431 | (u8 *)this_board->linux_mem_start + INITRD_OFFSET, |
| 432 | 16 * 1024 * 1024); |
| 433 | if (initramfs_len < 0) { |
| 434 | puts("initramfs load failed\n"); |
| 435 | indicate(UI_IND_INITRAMFS_PULL_FAIL); |
| 436 | return; |
| 437 | } |
| 438 | indicate(UI_IND_INITRAMFS_PULL_OK); |
| 439 | } |
| 440 | |
| 441 | do_params(initramfs_len, commandline_rootfs_append); |
| 442 | |
| 443 | /* give board implementation a chance to shut down |
| 444 | * anything it may have going on, leave GPIO set for Linux |
| 445 | */ |
| 446 | if (this_board->close) |
| 447 | (this_board->close)(); |
| 448 | |
| 449 | puts("Starting --->\n\n"); |
| 450 | indicate(UI_IND_KERNEL_START); |
| 451 | |
| 452 | /* |
| 453 | * ooh that's it, we're gonna try boot this image! |
| 454 | * never mind the cache, Linux will take care of it |
| 455 | */ |
| 456 | the_kernel(0, this_board->linux_machine_id, |
| 457 | this_board->linux_tag_placement); |
| 458 | |
| 459 | /* we won't come back here no matter what */ |
| 460 | } |
| 461 | |
| 462 | void bootloader_second_phase(void) |
| 463 | { |
| 464 | /* give device a chance to print device-specific things */ |
| 465 | |
| 466 | if (this_board->post_serial_init) |
| 467 | (this_board->post_serial_init)(); |
| 468 | |
| 469 | /* we try the possible kernels for this board in order */ |
| 470 | |
| 471 | for (this_kernel = this_board->kernel_source; this_kernel->name; |
| 472 | this_kernel++) |
| 473 | try_this_kernel(); |
| 474 | |
| 475 | /* none of the kernels worked out */ |
| 476 | |
| 477 | puts("\nNo usable kernel image found\n"); |
| 478 | |
| 479 | /* |
| 480 | * sit there doing a memory test in this case. |
| 481 | * |
| 482 | * This phase 2 code will get destroyed but it's OK, we won't be |
| 483 | * coming back and the whole memory test and dependency functions are |
| 484 | * in phase 1 / steppingstone, so we can test entire memory range. |
| 485 | * |
| 486 | * It means we just boot with SD Card with kernel(s) renamed or removed |
| 487 | * to provoke memory test. |
| 488 | */ |
| 489 | |
| 490 | indicate(UI_IND_MEM_TEST); |
| 491 | |
| 492 | memory_test((void *)this_board->linux_mem_start, |
| 493 | this_board->linux_mem_size); |
| 494 | |
| 495 | } |
| 496 | |