| /* |
| * Copyright (C) 2009 Juergen Beisert, Pengutronix |
| * |
| * In parts from the GRUB2 project: |
| * |
| * GRUB -- GRand Unified Bootloader |
| * Copyright (C) 1999,2000,2001,2002,2003,2004,2005,2007,2008 Free Software Foundation, Inc. |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License as |
| * published by the Free Software Foundation; either version 2 of |
| * the License, or (at your option) any later version. |
| * |
| * This program is distributed in the hope that 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, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, |
| * MA 02111-1307 USA |
| * |
| */ |
| |
| #include <common.h> |
| #include <command.h> |
| #include <environment.h> |
| #include <fs.h> |
| #include <errno.h> |
| #include <getopt.h> |
| #include <malloc.h> |
| #include <asm/syslib.h> |
| |
| /** FIXME */ |
| #define LINUX_MAGIC_SIGNATURE 0x53726448 /* "HdrS" */ |
| |
| /** FIXME */ |
| #define LINUX_FLAG_BIG_KERNEL 0x1 |
| |
| /** FIXME */ |
| #define LINUX_BOOT_LOADER_TYPE 0x72 |
| |
| /** FIXME */ |
| #define LINUX_DEFAULT_SETUP_SECTS 4 |
| |
| /** FIXME */ |
| #define LINUX_MAX_SETUP_SECTS 64 |
| |
| /** FIXME */ |
| #define LINUX_OLD_REAL_MODE_SEGMT 0x9000 |
| |
| /** FIXME */ |
| #define LINUX_OLD_REAL_MODE_ADDR (LINUX_OLD_REAL_MODE_SEGMT << 4) |
| |
| /** FIXME */ |
| #define LINUX_HEAP_END_OFFSET (LINUX_OLD_REAL_MODE_SEGMT - 0x200) |
| |
| /** FIXME */ |
| #define LINUX_FLAG_CAN_USE_HEAP 0x80 |
| |
| /** Define kernel command lines's start offset in the setup segment */ |
| #define LINUX_CL_OFFSET 0x9000 |
| |
| /** Define kernel command lines's end offset */ |
| #define LINUX_CL_END_OFFSET 0x90FF |
| |
| /** FIXME */ |
| #define LINUX_CL_MAGIC 0xA33F |
| |
| /** FIXME */ |
| #define LINUX_SETUP_MOVE_SIZE 0x9100 |
| |
| /** Sector size */ |
| #define DISK_SECTOR_BITS 9 |
| #define DISK_SECTOR_SIZE 0x200 |
| |
| /** Where to load a bzImage */ |
| #define LINUX_BZIMAGE_ADDR 0x100000 |
| |
| struct linux_kernel_header { |
| /* first sector of the image */ |
| uint8_t code1[0x0020]; |
| uint16_t cl_magic; /**< Magic number 0xA33F */ |
| uint16_t cl_offset; /**< The offset of command line */ |
| uint8_t code2[0x01F1 - 0x0020 - 2 - 2]; |
| uint8_t setup_sects; /**< The size of the setup in sectors */ |
| uint16_t root_flags; /**< If the root is mounted readonly */ |
| uint16_t syssize; /**< obsolete */ |
| uint16_t swap_dev; /**< obsolete */ |
| uint16_t ram_size; /**< obsolete */ |
| uint16_t vid_mode; /**< Video mode control */ |
| uint16_t root_dev; /**< Default root device number */ |
| uint16_t boot_flag; /**< 0xAA55 magic number */ |
| |
| /* second sector of the image */ |
| uint16_t jump; /**< Jump instruction (this is code!) */ |
| uint32_t header; /**< Magic signature "HdrS" */ |
| uint16_t version; /**< Boot protocol version supported */ |
| uint32_t realmode_swtch; /**< Boot loader hook */ |
| uint16_t start_sys; /**< The load-low segment (obsolete) */ |
| uint16_t kernel_version; /**< Points to kernel version string */ |
| uint8_t type_of_loader; /**< Boot loader identifier */ |
| #define LINUX_LOADER_ID_LILO 0x0 |
| #define LINUX_LOADER_ID_LOADLIN 0x1 |
| #define LINUX_LOADER_ID_BOOTSECT 0x2 |
| #define LINUX_LOADER_ID_SYSLINUX 0x3 |
| #define LINUX_LOADER_ID_ETHERBOOT 0x4 |
| #define LINUX_LOADER_ID_ELILO 0x5 |
| #define LINUX_LOADER_ID_GRUB 0x7 |
| #define LINUX_LOADER_ID_UBOOT 0x8 |
| #define LINUX_LOADER_ID_XEN 0x9 |
| #define LINUX_LOADER_ID_GUJIN 0xa |
| #define LINUX_LOADER_ID_QEMU 0xb |
| uint8_t loadflags; /**< Boot protocol option flags */ |
| uint16_t setup_move_size; /**< Move to high memory size */ |
| uint32_t code32_start; /**< Boot loader hook */ |
| uint32_t ramdisk_image; /**< initrd load address */ |
| uint32_t ramdisk_size; /**< initrd size */ |
| uint32_t bootsect_kludge; /**< obsolete */ |
| uint16_t heap_end_ptr; /**< Free memory after setup end */ |
| uint8_t ext_loader_ver; /**< boot loader's extension of the version number */ |
| uint8_t ext_loader_type; /**< boot loader's extension of its type */ |
| char *cmd_line_ptr; /**< Points to the kernel command line */ |
| uint32_t initrd_addr_max; /**< Highest address for initrd */ |
| #if 0 |
| /* for the records only. These members are defined in |
| * more recent Linux kernels |
| */ |
| uint32_t kernel_alignment; /**< Alignment unit required by the kernel */ |
| uint8_t relocatable_kernel; /** */ |
| uint8_t min_alignment; /** */ |
| uint32_t cmdline_size; /** */ |
| uint32_t hardware_subarch; /** */ |
| uint64_t hardware_subarch_data; /** */ |
| uint32_t payload_offset; /** */ |
| uint32_t payload_length; /** */ |
| uint64_t setup_data; /** */ |
| uint64_t pref_address; /** */ |
| uint32_t init_size; /** */ |
| #endif |
| } __attribute__ ((packed)); |
| |
| /* This is -1. Keep this value in sync with the kernel */ |
| #define NORMAL_VGA 0xffff /* 80x25 mode */ |
| #define ASK_VGA 0xfffd /* ask for it at bootup */ |
| |
| /** |
| * Load an x86 Linux kernel bzImage and start it |
| * @param cmdtp FIXME |
| * @param argc parameter count |
| * @param argv list of parameter |
| * |
| * Loads an x86 bzImage, checks for its integrity, stores the two parts |
| * (setup = 'real mode code' and kernel = 'protected mode code') to their |
| * default locations, switches back to real mode and runs the setup code. |
| */ |
| static int do_linux16(struct command *cmdtp, int argc, char *argv[]) |
| { |
| struct linux_kernel_header *lh = NULL; |
| int rc, opt; |
| unsigned setup_sects; |
| unsigned real_mode_size; |
| int vid_mode = NORMAL_VGA; |
| size_t image_size; |
| const char *cmdline = getenv("bootargs"); |
| const char *kernel_file; |
| |
| while((opt = getopt(argc, argv, "v:")) > 0) { |
| switch(opt) { |
| case 'v': |
| vid_mode = simple_strtoul(optarg, NULL, 0); |
| if (vid_mode == 0) { |
| if (!strcmp(optarg, "ask")) |
| vid_mode = ASK_VGA; |
| else { |
| printf("Unknown video mode: %s\n", optarg); |
| return 1; |
| } |
| } |
| break; |
| } |
| } |
| |
| if (optind == argc) { |
| printf("No kernel filename given\n"); |
| return 1; |
| } |
| kernel_file = argv[optind]; |
| |
| lh = read_file(kernel_file, &image_size); |
| if (lh == NULL) { |
| printf("Cannot read file '%s'\n", argv[1]); |
| return 1; |
| } |
| |
| if (lh->boot_flag != 0xaa55) { |
| printf("File '%s' has invalid magic number\n", argv[1]); |
| rc = 1; |
| goto on_error; |
| } |
| |
| if (lh->setup_sects > LINUX_MAX_SETUP_SECTS) { |
| printf("File '%s' contains too many setup sectors\n", argv[1]); |
| rc = 1; |
| goto on_error; |
| } |
| |
| setup_sects = lh->setup_sects; |
| |
| printf("Found a %d.%d image header\n", lh->version >> 8, lh->version & 0xFF); |
| |
| if (lh->header == LINUX_MAGIC_SIGNATURE && lh->version >= 0x0200) { |
| /* kernel is recent enough */ |
| ; |
| if (!(lh->loadflags & LINUX_FLAG_BIG_KERNEL)) { |
| printf("Cannot load a classic zImage. Use a bzImage instead\n"); |
| goto on_error; |
| } |
| lh->type_of_loader = LINUX_BOOT_LOADER_TYPE; /* TODO */ |
| |
| if (lh->version >= 0x0201) { |
| lh->heap_end_ptr = LINUX_HEAP_END_OFFSET; |
| lh->loadflags |= LINUX_FLAG_CAN_USE_HEAP; |
| } |
| |
| if (lh->version >= 0x0202) |
| lh->cmd_line_ptr = (void*)(LINUX_OLD_REAL_MODE_ADDR + LINUX_CL_OFFSET); /* FIXME */ |
| else { |
| lh->cl_magic = LINUX_CL_MAGIC; |
| lh->cl_offset = LINUX_CL_OFFSET; |
| lh->setup_move_size = LINUX_SETUP_MOVE_SIZE; |
| } |
| } else { |
| printf("Kernel too old to handle\n"); |
| rc = 1; |
| goto on_error; |
| } |
| |
| if (strlen(cmdline) >= (LINUX_CL_END_OFFSET - LINUX_CL_OFFSET)) { |
| printf("Kernel command line exceeds the available space\n"); |
| rc = 1; |
| goto on_error; |
| } |
| |
| /* |
| * The kernel does not check for the "vga=<val>" kernel command line |
| * parameter anymore. It expects this kind of information in the |
| * boot parameters instead. |
| */ |
| if (vid_mode != NORMAL_VGA) |
| lh->vid_mode = vid_mode; |
| |
| /* If SETUP_SECTS is not set, set it to the default. */ |
| if (setup_sects == 0) { |
| printf("Fixing setup sector count\n"); |
| setup_sects = LINUX_DEFAULT_SETUP_SECTS; |
| } |
| |
| if (setup_sects >= 15) { |
| void *src = lh; |
| if (lh->kernel_version != 0) |
| printf("Kernel version: '%s'\n", |
| (char *)src + lh->kernel_version + DISK_SECTOR_SIZE); |
| } |
| |
| /* |
| * Size of the real mode part to handle in a separate way |
| */ |
| real_mode_size = (setup_sects << DISK_SECTOR_BITS) + DISK_SECTOR_SIZE; |
| |
| /* |
| * real mode space hole extended memory |
| * |---------------------------------------------->|----------->|------------------------------> |
| * 0 0xa0000 0x100000 |
| * <-1-|----------2-----------><-3- | |
| * 0x7e00 0x90000 |
| * <-4--|-5--> |---------6-------------> |
| * |
| * 1) real mode stack |
| * 2) barebox code |
| * 3) flat mode stack |
| * 4) realmode stack when starting a Linux kernel |
| * 5) Kernel's real mode setup code |
| * 6) compressed kernel image |
| */ |
| /* |
| * Parts of the image we know: |
| * - real mode part |
| * - kernel payload |
| */ |
| /* |
| * NOTE: This part is dangerous, as it copies some image content to |
| * various locations in the main memory. This could overwrite important |
| * data of the running barebox (hopefully not) |
| */ |
| /* copy the real mode part of the image to the 9th segment */ |
| memcpy((void*)LINUX_OLD_REAL_MODE_ADDR, lh, LINUX_SETUP_MOVE_SIZE); |
| |
| /* TODO add 'BOOT_IMAGE=<file>' and 'auto' if no user intervention was done (in front of all other params) */ |
| /* copy also the command line into this area */ |
| memcpy((void*)(LINUX_OLD_REAL_MODE_ADDR + LINUX_CL_OFFSET), cmdline, strlen(cmdline) + 1); |
| printf("Using kernel command line: '%s'\n", cmdline); |
| |
| /* copy the compressed image part to its final address the setup code expects it |
| * Note: The protected mode part starts at offset (setup_sects + 1) * 512 |
| */ |
| memcpy((void*)LINUX_BZIMAGE_ADDR, ((void*)lh) + real_mode_size, image_size - real_mode_size); |
| |
| /* |
| * switch back to real mode now and start the real mode part of the |
| * image at address "(LINUX_OLD_REAL_MODE_ADDR >> 4) + 0x20:0x0000" |
| * which means "0x9020:0x000" -> 0x90200 |
| */ |
| bios_start_linux(LINUX_OLD_REAL_MODE_SEGMT); /* does not return */ |
| |
| on_error: |
| if (lh != NULL) |
| free(lh); |
| |
| return rc; |
| } |
| |
| BAREBOX_CMD_HELP_START(linux16) |
| BAREBOX_CMD_HELP_USAGE("linux16 <file> [-v <mode>]\n") |
| BAREBOX_CMD_HELP_SHORT("Boot a kernel <file> on x86 via real mode code.\n") |
| BAREBOX_CMD_HELP_OPT ("-v <mode>", "VESA video mode number or 'ask'\n") |
| BAREBOX_CMD_HELP_END |
| |
| /** |
| * @page linux16_command |
| |
| <p>Only kernel images in bzImage format are supported by now. See \ref |
| x86_boot_preparation for more info about how to use this command.</p> |
| |
| <p>For the video mode refer the Linux kernel documentation |
| 'Documentation/fb/vesafb.txt' for correct VESA mode numbers. If the keyword |
| 'ask' instead of a number is given, the starting kernel will ask for a number. |
| </p> |
| */ |
| |
| BAREBOX_CMD_START(linux16) |
| .cmd = do_linux16, |
| .usage = "boot a linux kernel", |
| BAREBOX_CMD_HELP(cmd_linux16_help) |
| BAREBOX_CMD_END |
| |
| /** |
| * @file |
| * @brief Boot support for Linux on x86 |
| */ |
| |
| /** |
| * @page x86_boot_preparation Linux Preparation on x86 |
| * |
| * Due to some real mode constraints, starting Linux is somehow tricky. |
| * Currently only @p bzImages are supported, because @p zImages would |
| * interfere with the @a barebox runtime. |
| * Also older load header versions than 2.00 aren't supported. |
| * |
| * The memory layout immediately before starting the Linux kernel: |
| * |
| @verbatim |
| real mode space hole extended memory |
| |---------------------------------------------->|----------->|------------------------------> |
| 0 0x7e00 0x90000 0xa0000 0x100000 |
| <-1-|----------2-----------><-3- | |
| <-4--|-5--> |---------6-------------> |
| @endverbatim |
| * |
| * @li 1 = @a barebox's real mode stack |
| * @li 2 = @a barebox's code |
| * @li 3 = @a barebox's flat mode stack |
| * @li 4 = real mode stack, when starting the Linux kernel |
| * @li 5 = Kernel's real mode setup code |
| * @li 6 = compressed kernel image |
| * |
| * A more detailed memory layout for kernel's real mode setup code |
| * |
| @verbatim |
| |
| 0x90000 0x97fff 0x99000 0x990ff |
| ---|------------------------------------------|----------------|--------------------| |
| |<-------- max setup code size ----------->|<--heap/stack-->|<-- command line -->| |
| |
| @endverbatim |
| * |
| * The regular entry point into the setup code is 0x90200 (2nd sector) |
| * |
| * To start the kernel, it's own setup code will be called. To do so, it |
| * must be called in real mode. So, @a barebox switches back to real mode |
| * a last time and does a jump to the setup code entry point. Now its up to |
| * the setup code to deflate the kernel, switching to its own protected mode |
| * setup and starting the kernel itself. |
| * |
| * @note This scenario only works, if a BIOS is still present. In this case |
| * there is no need for @a barebox to forward any system related information |
| * to the kernel. Everything is detected by kernel's setup code. |
| * |
| */ |