diff options
author | H. Peter Anvin <hpa@zytor.com> | 2007-03-15 11:38:16 -0700 |
---|---|---|
committer | H. Peter Anvin <hpa@zytor.com> | 2007-03-15 11:38:16 -0700 |
commit | 77481cdf403e4170785fada5a44a0efcd2a357e4 (patch) | |
tree | 0e17964a9f051fe5d2911cd6ee32eaee87fad887 /com32/lib | |
parent | 200e00ab1afbfac847c48eb608110c4726a582c4 (diff) | |
download | syslinux-77481cdf403e4170785fada5a44a0efcd2a357e4.tar.gz |
Initial library support for booting a Linux kernel
Diffstat (limited to 'com32/lib')
-rw-r--r-- | com32/lib/Makefile | 2 | ||||
-rw-r--r-- | com32/lib/syslinux/load_linux.c | 293 |
2 files changed, 295 insertions, 0 deletions
diff --git a/com32/lib/Makefile b/com32/lib/Makefile index 2424fe53..0ea60e77 100644 --- a/com32/lib/Makefile +++ b/com32/lib/Makefile @@ -69,6 +69,8 @@ LIBOBJS = \ syslinux/shuffle_rm.o syslinux/zonelist.o \ syslinux/dump_mmap.o syslinux/dump_movelist.o \ \ + syslinux/load_linux.o \ + \ syslinux/pxe_get_cached.o BINDIR = /usr/bin diff --git a/com32/lib/syslinux/load_linux.c b/com32/lib/syslinux/load_linux.c new file mode 100644 index 00000000..620ad081 --- /dev/null +++ b/com32/lib/syslinux/load_linux.c @@ -0,0 +1,293 @@ +/* ----------------------------------------------------------------------- * + * + * Copyright 2007 H. Peter Anvin - All Rights Reserved + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom + * the Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall + * be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * ----------------------------------------------------------------------- */ + +/* + * load_linux.c + * + * Load a Linux kernel (Image/zImage/bzImage). + */ + +#include <stdlib.h> +#include <inttypes.h> +#include <string.h> +#include <syslinux/linux.h> +#include <syslinux/bootrm.h> +#include <syslinux/movebits.h> + +struct linux_header { + uint8_t boot_sector_1[0x0020]; + uint16_t old_cmd_line_magic; + uint16_t old_cmd_line_offset; + uint8_t boot_sector_2[0x01f1-0x0024]; + uint8_t setup_sects; + uint16_t root_flags; + uint32_t syssize; + uint16_t ram_size; + uint16_t vid_mode; + uint16_t root_dev; + uint16_t boot_flag; + uint16_t jump; + uint32_t header; + uint16_t version; + uint32_t realmode_swtch; + uint16_t start_sys; + uint16_t kernel_version; + uint8_t type_of_loader; + uint8_t loadflags; + uint16_t setup_move_size; + uint32_t code32_start; + uint32_t ramdisk_image; + uint32_t ramdisk_size; + uint32_t bootsect_kludge; + uint16_t heap_end_ptr; + uint16_t pad1; + uint32_t cmd_line_ptr; + uint32_t initrd_addr_max; + uint32_t kernel_alignment; + uint8_t relocatable_kernel; + uint8_t pad2[3]; + uint32_t cmdline_max_len; +} __packed; + +#define BOOT_MAGIC 0xAA55 +#define LINUX_MAGIC ('H' + ('d' << 8) + ('r' << 16) + ('S' << 24)) +#define OLD_CMDLINE_MAGIC 0xA33F + +/* loadflags */ +#define LOAD_HIGH 0x01 +#define CAN_USE_HEAP 0x80 + +/* Offset for the command line versus the real mode code */ +#define CMDLINE_OFFSET 0x9000 + +/* Get the combined size of the initramfs */ +static addr_t initramfs_size(struct initramfs *initramfs) +{ + struct initramfs *ip; + addr_t size = 0; + + if (!initramfs) + return 0; + + for (ip = initramfs->next; ip->len; ip = ip->next) + size += ip->len; + + return size; +} + +/* Create the appropriate mappings for the initramfs */ +static int map_initramfs(struct syslinux_movelist **fraglist, + struct syslinux_memmap **mmap, + struct initramfs *initramfs, + addr_t addr) +{ + struct initramfs *ip; + + for (ip = initramfs->next; ip->len; ip = ip->next) { + if (ip->data_len) { + if (syslinux_add_movelist(fraglist, addr, (addr_t)ip->data, ip->len)) + return -1; + } + if (ip->len > ip->data_len) { + if (syslinux_add_memmap(mmap, addr+ip->data_len, + ip->len-ip->data_len, SMT_ZERO)) + return -1; + } + addr += ip->len; + } + + return 0; +} + +int syslinux_boot_linux(void *kernel_buf, size_t kernel_size, + struct initramfs *initramfs, + char *cmdline, + uint16_t video_mode, + uint32_t memlimit) +{ + struct linux_header hdr, *whdr; + size_t real_mode_size, prot_mode_size; + addr_t real_mode_base, prot_mode_base; + addr_t irf_size; + size_t cmdline_size; + struct syslinux_rm_regs regs; + struct syslinux_movelist *fraglist = NULL; + struct syslinux_memmap *mmap = NULL; + struct syslinux_memmap *amap = NULL; + + cmdline_size = strlen(cmdline)+1; + + if (kernel_size < 2*512) + goto bail; + + /* Copy the header into private storage */ + memcpy(&hdr, kernel_buf, sizeof hdr); + whdr = (struct linux_header *)kernel_buf; /* Writable header */ + + if (hdr.boot_flag != BOOT_MAGIC) + goto bail; + + if (hdr.header != LINUX_MAGIC) { + hdr.version = 0x0100; /* Very old kernel */ + hdr.loadflags = 0; + } + + whdr->vid_mode = video_mode; + + if (!hdr.setup_sects) + hdr.setup_sects = 4; + + if (hdr.version < 0x0203) + hdr.initrd_addr_max = 0x37ffffff; + + if (!memlimit || memlimit-1 > hdr.initrd_addr_max) + memlimit = hdr.initrd_addr_max+1; /* Zero for no limit */ + + if (hdr.version < 0x0206) + hdr.cmdline_max_len = 256; + + if (cmdline_size > hdr.cmdline_max_len) { + cmdline_size = hdr.cmdline_max_len; + cmdline[cmdline_size-1] = '\0'; + } + + real_mode_size = (hdr.setup_sects+1) << 9; + real_mode_base = (hdr.loadflags & LOAD_HIGH) ? 0x10000 : 0x90000; + prot_mode_base = (hdr.loadflags & LOAD_HIGH) ? 0x100000 : 0x10000; + prot_mode_size = kernel_size - real_mode_size; + + if (!(hdr.loadflags & LOAD_HIGH) && prot_mode_size > 512*1024) + goto bail; /* Kernel cannot be loaded low */ + + if (initramfs && hdr.version < 0x0200) + goto bail; /* initrd/initramfs not supported */ + + if (hdr.version >= 0x0200) { + whdr->type_of_loader = 0x30; /* SYSLINUX unknown module */ + if (hdr.version >= 0x0201) { + whdr->heap_end_ptr = CMDLINE_OFFSET - 0x0200; + whdr->loadflags |= CAN_USE_HEAP; + } + if (hdr.version >= 0x0202) { + whdr->cmd_line_ptr = real_mode_base + CMDLINE_OFFSET; + } else { + whdr->old_cmd_line_magic = OLD_CMDLINE_MAGIC; + whdr->old_cmd_line_offset = CMDLINE_OFFSET; + /* Be paranoid and round up to a multiple of 16 */ + whdr->setup_move_size = (CMDLINE_OFFSET+cmdline_size+15) & ~15; + } + } + + /* Get the memory map */ + mmap = syslinux_memory_map(); /* Memory map for shuffle_boot */ + amap = syslinux_dup_memmap(mmap); /* Keep track of available memory */ + if (!mmap || !amap) + goto bail; + + /* If the user has specified a memory limit, mark that as unavailable. + Question: should we mark this off-limit in the mmap as well (meaning + it's unavailable to the boot loader, which probably has already touched + some of it), or just in the amap? */ + if (memlimit) + if (syslinux_add_memmap(&amap, memlimit, -memlimit, SMT_RESERVED)) + goto bail; + + /* Place the kernel in memory */ + + /* Real mode code */ + if (syslinux_add_movelist(&fraglist, real_mode_base, (addr_t)kernel_buf, + real_mode_size)) + goto bail; + if (syslinux_add_memmap(&amap, real_mode_base, CMDLINE_OFFSET+cmdline_size, + SMT_ALLOC)) + goto bail; + + /* Zero region between real mode code and cmdline */ + if (syslinux_add_memmap(&mmap, real_mode_base+real_mode_size, + CMDLINE_OFFSET-real_mode_size, SMT_ZERO)) + goto bail; + + /* Command line */ + if (syslinux_add_movelist(&fraglist, real_mode_base+CMDLINE_OFFSET, + (addr_t)cmdline, cmdline_size)) + goto bail; + + /* Protected-mode code */ + if (syslinux_add_movelist(&fraglist, prot_mode_base, + (addr_t)kernel_buf + real_mode_size, + prot_mode_size)) + goto bail; + if (syslinux_add_memmap(&amap, prot_mode_base, prot_mode_size, SMT_ALLOC)) + goto bail; + + /* Figure out the size of the initramfs, and where to put it. + We should put it at the highest possible address which is + <= hdr.initrd_addr_max, which fits the entire initramfs. */ + + irf_size = initramfs_size(initramfs); /* Handles initramfs == NULL */ + + if (irf_size) { + addr_t best_addr = 0; + struct syslinux_memmap *ml; + + if (irf_size) { + for (ml = amap; ml->type != SMT_END; ml = ml->next) { + addr_t adj_start = (ml->start+0xfff) & ~0xfff; /* Page-aligned */ + if (ml->type == SMT_FREE && + ml->next->start - adj_start >= irf_size) + best_addr = (ml->next->start - irf_size) & ~0xfff; + } + + if (!best_addr) + goto bail; /* Insufficient memory for initramfs */ + + whdr->ramdisk_image = best_addr; + whdr->ramdisk_size = irf_size; + + if (syslinux_add_memmap(&amap, best_addr, irf_size, SMT_ALLOC)) + goto bail; + + if (map_initramfs(&fraglist, &mmap, initramfs, best_addr)) + goto bail; + } + } + + /* Set up the registers on entry */ + memset(®s, 0, sizeof regs); + regs.es = regs.ds = regs.ss = regs.fs = regs.gs = real_mode_base >> 4; + regs.cs = (real_mode_base >> 4)+0x20; + /* regs.ip = 0; */ + regs.esp.w[0] = CMDLINE_OFFSET; + + syslinux_shuffle_boot_rm(fraglist, mmap, 0, ®s); + + bail: + syslinux_free_movelist(fraglist); + syslinux_free_memmap(mmap); + syslinux_free_memmap(amap); + return -1; +} |