Home | History | Annotate | Download | only in syslinux
      1 /* ----------------------------------------------------------------------- *
      2  *
      3  *   Copyright 2007-2009 H. Peter Anvin - All Rights Reserved
      4  *   Copyright 2009-2013 Intel Corporation; author: H. Peter Anvin
      5  *
      6  *   Permission is hereby granted, free of charge, to any person
      7  *   obtaining a copy of this software and associated documentation
      8  *   files (the "Software"), to deal in the Software without
      9  *   restriction, including without limitation the rights to use,
     10  *   copy, modify, merge, publish, distribute, sublicense, and/or
     11  *   sell copies of the Software, and to permit persons to whom
     12  *   the Software is furnished to do so, subject to the following
     13  *   conditions:
     14  *
     15  *   The above copyright notice and this permission notice shall
     16  *   be included in all copies or substantial portions of the Software.
     17  *
     18  *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
     19  *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
     20  *   OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
     21  *   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
     22  *   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
     23  *   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
     24  *   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
     25  *   OTHER DEALINGS IN THE SOFTWARE.
     26  *
     27  * ----------------------------------------------------------------------- */
     28 
     29 /*
     30  * load_linux.c
     31  *
     32  * Load a Linux kernel (Image/zImage/bzImage).
     33  */
     34 
     35 #include <ctype.h>
     36 #include <stdbool.h>
     37 #include <stdlib.h>
     38 #include <inttypes.h>
     39 #include <string.h>
     40 #include <minmax.h>
     41 #include <errno.h>
     42 #include <suffix_number.h>
     43 #include <dprintf.h>
     44 
     45 #include <syslinux/align.h>
     46 #include <syslinux/linux.h>
     47 #include <syslinux/bootrm.h>
     48 #include <syslinux/movebits.h>
     49 #include <syslinux/firmware.h>
     50 #include <syslinux/video.h>
     51 
     52 #define BOOT_MAGIC 0xAA55
     53 #define LINUX_MAGIC ('H' + ('d' << 8) + ('r' << 16) + ('S' << 24))
     54 #define OLD_CMDLINE_MAGIC 0xA33F
     55 
     56 /* loadflags */
     57 #define LOAD_HIGH	0x01
     58 #define CAN_USE_HEAP	0x80
     59 
     60 /*
     61  * Find the last instance of a particular command line argument
     62  * (which should include the final =; do not use for boolean arguments)
     63  * Note: the resulting string is typically not null-terminated.
     64  */
     65 static const char *find_argument(const char *cmdline, const char *argument)
     66 {
     67     const char *found = NULL;
     68     const char *p = cmdline;
     69     bool was_space = true;
     70     size_t la = strlen(argument);
     71 
     72     while (*p) {
     73 	if (isspace(*p)) {
     74 	    was_space = true;
     75 	} else if (was_space) {
     76 	    if (!memcmp(p, argument, la))
     77 		found = p + la;
     78 	    was_space = false;
     79 	}
     80 	p++;
     81     }
     82 
     83     return found;
     84 }
     85 
     86 /* Truncate to 32 bits, with saturate */
     87 static inline uint32_t saturate32(unsigned long long v)
     88 {
     89     return (v > 0xffffffff) ? 0xffffffff : (uint32_t) v;
     90 }
     91 
     92 /* Create the appropriate mappings for the initramfs */
     93 static int map_initramfs(struct syslinux_movelist **fraglist,
     94 			 struct syslinux_memmap **mmap,
     95 			 struct initramfs *initramfs, addr_t addr)
     96 {
     97     struct initramfs *ip;
     98     addr_t next_addr, len, pad;
     99 
    100     for (ip = initramfs->next; ip->len; ip = ip->next) {
    101 	len = ip->len;
    102 	next_addr = addr + len;
    103 
    104 	/* If this isn't the last entry, extend the zero-pad region
    105 	   to enforce the alignment of the next chunk. */
    106 	if (ip->next->len) {
    107 	    pad = -next_addr & (ip->next->align - 1);
    108 	    len += pad;
    109 	    next_addr += pad;
    110 	}
    111 
    112 	if (ip->data_len) {
    113 	    if (syslinux_add_movelist(fraglist, addr, (addr_t) ip->data, len))
    114 		return -1;
    115 	}
    116 	if (len > ip->data_len) {
    117 	    if (syslinux_add_memmap(mmap, addr + ip->data_len,
    118 				    len - ip->data_len, SMT_ZERO))
    119 		return -1;
    120 	}
    121 	addr = next_addr;
    122     }
    123 
    124     return 0;
    125 }
    126 
    127 static size_t calc_cmdline_offset(const struct syslinux_memmap *mmap,
    128 				  const struct linux_header *hdr,
    129 				  size_t cmdline_size, addr_t base,
    130 				  addr_t start)
    131 {
    132     size_t max_offset;
    133 
    134     if (hdr->version >= 0x0202 && (hdr->loadflags & LOAD_HIGH))
    135 	max_offset = 0x10000;
    136     else
    137 	max_offset = 0xfff0 - cmdline_size;
    138 
    139     if (!syslinux_memmap_highest(mmap, SMT_FREE, &start,
    140 				 cmdline_size, 0xa0000, 16) ||
    141 	!syslinux_memmap_highest(mmap, SMT_TERMINAL, &start,
    142 				 cmdline_size, 0xa0000, 16)) {
    143 
    144 
    145 	return min(start - base, max_offset) & ~15;
    146     }
    147 
    148     dprintf("Unable to find lowmem for cmdline\n");
    149     return (0x9ff0 - cmdline_size) & ~15; /* Legacy value: pure hope... */
    150 }
    151 
    152 int bios_boot_linux(void *kernel_buf, size_t kernel_size,
    153 		    struct initramfs *initramfs,
    154 		    struct setup_data *setup_data,
    155 		    char *cmdline)
    156 {
    157     struct linux_header hdr, *whdr;
    158     size_t real_mode_size, prot_mode_size, base;
    159     addr_t real_mode_base, prot_mode_base, prot_mode_max;
    160     addr_t irf_size;
    161     size_t cmdline_size, cmdline_offset;
    162     struct setup_data *sdp;
    163     struct syslinux_rm_regs regs;
    164     struct syslinux_movelist *fraglist = NULL;
    165     struct syslinux_memmap *mmap = NULL;
    166     struct syslinux_memmap *amap = NULL;
    167     uint32_t memlimit = 0;
    168     uint16_t video_mode = 0;
    169     const char *arg;
    170 
    171     cmdline_size = strlen(cmdline) + 1;
    172 
    173     errno = EINVAL;
    174     if (kernel_size < 2 * 512) {
    175 	dprintf("Kernel size too small\n");
    176 	goto bail;
    177     }
    178 
    179     /* Look for specific command-line arguments we care about */
    180     if ((arg = find_argument(cmdline, "mem=")))
    181 	memlimit = saturate32(suffix_number(arg));
    182 
    183     if ((arg = find_argument(cmdline, "vga="))) {
    184 	switch (arg[0] | 0x20) {
    185 	case 'a':		/* "ask" */
    186 	    video_mode = 0xfffd;
    187 	    break;
    188 	case 'e':		/* "ext" */
    189 	    video_mode = 0xfffe;
    190 	    break;
    191 	case 'n':		/* "normal" */
    192 	    video_mode = 0xffff;
    193 	    break;
    194 	case 'c':		/* "current" */
    195 	    video_mode = 0x0f04;
    196 	    break;
    197 	default:
    198 	    video_mode = strtoul(arg, NULL, 0);
    199 	    break;
    200 	}
    201     }
    202 
    203     /* Copy the header into private storage */
    204     /* Use whdr to modify the actual kernel header */
    205     memcpy(&hdr, kernel_buf, sizeof hdr);
    206     whdr = (struct linux_header *)kernel_buf;
    207 
    208     if (hdr.boot_flag != BOOT_MAGIC) {
    209 	dprintf("Invalid boot magic\n");
    210 	goto bail;
    211     }
    212 
    213     if (hdr.header != LINUX_MAGIC) {
    214 	hdr.version = 0x0100;	/* Very old kernel */
    215 	hdr.loadflags = 0;
    216     }
    217 
    218     whdr->vid_mode = video_mode;
    219 
    220     if (!hdr.setup_sects)
    221 	hdr.setup_sects = 4;
    222 
    223     if (hdr.version < 0x0203 || !hdr.initrd_addr_max)
    224 	hdr.initrd_addr_max = 0x37ffffff;
    225 
    226     if (!memlimit && memlimit - 1 > hdr.initrd_addr_max)
    227 	memlimit = hdr.initrd_addr_max + 1;	/* Zero for no limit */
    228 
    229     if (hdr.version < 0x0205 || !(hdr.loadflags & LOAD_HIGH))
    230 	hdr.relocatable_kernel = 0;
    231 
    232     if (hdr.version < 0x0206)
    233 	hdr.cmdline_max_len = 256;
    234 
    235     if (cmdline_size > hdr.cmdline_max_len) {
    236 	cmdline_size = hdr.cmdline_max_len;
    237 	cmdline[cmdline_size - 1] = '\0';
    238     }
    239 
    240     real_mode_size = (hdr.setup_sects + 1) << 9;
    241     real_mode_base = (hdr.loadflags & LOAD_HIGH) ? 0x10000 : 0x90000;
    242     prot_mode_base = (hdr.loadflags & LOAD_HIGH) ? 0x100000 : 0x10000;
    243     prot_mode_max  = (hdr.loadflags & LOAD_HIGH) ? (addr_t)-1 : 0x8ffff;
    244     prot_mode_size = kernel_size - real_mode_size;
    245 
    246     /* Get the memory map */
    247     mmap = syslinux_memory_map();	/* Memory map for shuffle_boot */
    248     amap = syslinux_dup_memmap(mmap);	/* Keep track of available memory */
    249     if (!mmap || !amap) {
    250 	errno = ENOMEM;
    251 	goto bail;
    252     }
    253 
    254     cmdline_offset = calc_cmdline_offset(mmap, &hdr, cmdline_size,
    255 					 real_mode_base,
    256 					 real_mode_base + real_mode_size);
    257     dprintf("cmdline_offset at 0x%x\n", real_mode_base + cmdline_offset);
    258 
    259     if (hdr.version < 0x020a) {
    260 	/*
    261 	 * The 3* here is a total fudge factor... it's supposed to
    262 	 * account for the fact that the kernel needs to be
    263 	 * decompressed, and then followed by the BSS and BRK regions.
    264 	 * This doesn't, however, account for the fact that the kernel
    265 	 * is decompressed into a whole other place, either.
    266 	 */
    267 	hdr.init_size = 3 * prot_mode_size;
    268     }
    269 
    270     if (!(hdr.loadflags & LOAD_HIGH) && prot_mode_size > 512 * 1024) {
    271 	dprintf("Kernel cannot be loaded low\n");
    272 	goto bail;
    273     }
    274 
    275     /* Get the size of the initramfs, if there is one */
    276     irf_size = initramfs_size(initramfs);
    277 
    278     if (irf_size && hdr.version < 0x0200) {
    279 	dprintf("Initrd specified but not supported by kernel\n");
    280 	goto bail;
    281     }
    282 
    283     if (hdr.version >= 0x0200) {
    284 	whdr->type_of_loader = 0x30;	/* SYSLINUX unknown module */
    285 	if (hdr.version >= 0x0201) {
    286 	    whdr->heap_end_ptr = cmdline_offset - 0x0200;
    287 	    whdr->loadflags |= CAN_USE_HEAP;
    288 	}
    289     }
    290 
    291     dprintf("Initial memory map:\n");
    292     syslinux_dump_memmap(mmap);
    293 
    294     /* If the user has specified a memory limit, mark that as unavailable.
    295        Question: should we mark this off-limit in the mmap as well (meaning
    296        it's unavailable to the boot loader, which probably has already touched
    297        some of it), or just in the amap? */
    298     if (memlimit)
    299 	if (syslinux_add_memmap(&amap, memlimit, -memlimit, SMT_RESERVED)) {
    300 	    errno = ENOMEM;
    301 	    goto bail;
    302 	}
    303 
    304     /* Place the kernel in memory */
    305 
    306     /*
    307      * First, find a suitable place for the protected-mode code.  If
    308      * the kernel image is not relocatable, just worry if it fits (it
    309      * might not even be a Linux image, after all, and for !LOAD_HIGH
    310      * we end up decompressing into a different location anyway), but
    311      * if it is, make sure everything fits.
    312      */
    313     base = prot_mode_base;
    314     if (prot_mode_size &&
    315 	syslinux_memmap_find(amap, &base,
    316 			     hdr.relocatable_kernel ?
    317 			     hdr.init_size : prot_mode_size,
    318 			     hdr.relocatable_kernel, hdr.kernel_alignment,
    319 			     prot_mode_base, prot_mode_max,
    320 			     prot_mode_base, prot_mode_max)) {
    321 	dprintf("Could not find location for protected-mode code\n");
    322 	goto bail;
    323     }
    324 
    325     whdr->code32_start += base - prot_mode_base;
    326 
    327     /* Real mode code */
    328     if (syslinux_memmap_find(amap, &real_mode_base,
    329 			     cmdline_offset + cmdline_size, true, 16,
    330 			     real_mode_base, 0x90000, 0, 640*1024)) {
    331 	dprintf("Could not find location for real-mode code\n");
    332 	goto bail;
    333     }
    334 
    335     if (syslinux_add_movelist(&fraglist, real_mode_base, (addr_t) kernel_buf,
    336 			      real_mode_size))
    337 	goto bail;
    338     if (syslinux_add_memmap
    339 	(&amap, real_mode_base, cmdline_offset + cmdline_size, SMT_ALLOC)) {
    340 	errno = ENOMEM;
    341 	goto bail;
    342     }
    343 
    344     /* Zero region between real mode code and cmdline */
    345     if (syslinux_add_memmap(&mmap, real_mode_base + real_mode_size,
    346 			    cmdline_offset - real_mode_size, SMT_ZERO)) {
    347 	errno = ENOMEM;
    348 	goto bail;
    349     }
    350 
    351     /* Command line */
    352     if (syslinux_add_movelist(&fraglist, real_mode_base + cmdline_offset,
    353 			      (addr_t) cmdline, cmdline_size)) {
    354 	errno = ENOMEM;
    355 	goto bail;
    356     }
    357     if (hdr.version >= 0x0202) {
    358 	whdr->cmd_line_ptr = real_mode_base + cmdline_offset;
    359     } else {
    360 	whdr->old_cmd_line_magic = OLD_CMDLINE_MAGIC;
    361 	whdr->old_cmd_line_offset = cmdline_offset;
    362 	if (hdr.version >= 0x0200) {
    363 	    /* Be paranoid and round up to a multiple of 16 */
    364 	    whdr->setup_move_size = (cmdline_offset + cmdline_size + 15) & ~15;
    365 	}
    366     }
    367 
    368     /* Protected-mode code */
    369     if (prot_mode_size) {
    370 	if (syslinux_add_movelist(&fraglist, prot_mode_base,
    371 				  (addr_t) kernel_buf + real_mode_size,
    372 				  prot_mode_size)) {
    373 	    errno = ENOMEM;
    374 	    goto bail;
    375 	}
    376 	if (syslinux_add_memmap(&amap, prot_mode_base, prot_mode_size,
    377 				SMT_ALLOC)) {
    378 	    errno = ENOMEM;
    379 	    goto bail;
    380 	}
    381     }
    382 
    383     /* Figure out the size of the initramfs, and where to put it.
    384        We should put it at the highest possible address which is
    385        <= hdr.initrd_addr_max, which fits the entire initramfs. */
    386 
    387     if (irf_size) {
    388 	addr_t best_addr = 0;
    389 	struct syslinux_memmap *ml;
    390 	const addr_t align_mask = INITRAMFS_MAX_ALIGN - 1;
    391 
    392 	if (irf_size) {
    393 	    for (ml = amap; ml->type != SMT_END; ml = ml->next) {
    394 		addr_t adj_start = (ml->start + align_mask) & ~align_mask;
    395 		addr_t adj_end = ml->next->start & ~align_mask;
    396 		if (ml->type == SMT_FREE && adj_end - adj_start >= irf_size)
    397 		    best_addr = (adj_end - irf_size) & ~align_mask;
    398 	    }
    399 
    400 	    if (!best_addr) {
    401 		dprintf("Insufficient memory for initramfs\n");
    402 		goto bail;
    403 	    }
    404 
    405 	    whdr->ramdisk_image = best_addr;
    406 	    whdr->ramdisk_size = irf_size;
    407 
    408 	    if (syslinux_add_memmap(&amap, best_addr, irf_size, SMT_ALLOC)) {
    409 		errno = ENOMEM;
    410 		goto bail;
    411 	    }
    412 
    413 	    if (map_initramfs(&fraglist, &mmap, initramfs, best_addr)) {
    414 		errno = ENOMEM;
    415 		goto bail;
    416 	    }
    417 	}
    418     }
    419 
    420     if (setup_data) {
    421 	uint64_t *prev_ptr = &whdr->setup_data;
    422 
    423 	for (sdp = setup_data->next; sdp != setup_data; sdp = sdp->next) {
    424 	    struct syslinux_memmap *ml;
    425 	    const addr_t align_mask = 15; /* Header is 16 bytes */
    426 	    addr_t best_addr = 0;
    427 	    size_t size = sdp->hdr.len + sizeof(sdp->hdr);
    428 
    429 	    if (!sdp->data || !sdp->hdr.len)
    430 		continue;
    431 
    432 	    if (hdr.version < 0x0209) {
    433 		/* Setup data not supported */
    434 		errno = ENXIO;	/* Kind of arbitrary... */
    435 		goto bail;
    436 	    }
    437 
    438 	    for (ml = amap; ml->type != SMT_END; ml = ml->next) {
    439 		addr_t adj_start = (ml->start + align_mask) & ~align_mask;
    440 		addr_t adj_end = ml->next->start & ~align_mask;
    441 
    442 		if (ml->type == SMT_FREE && adj_end - adj_start >= size)
    443 		    best_addr = (adj_end - size) & ~align_mask;
    444 	    }
    445 
    446 	    if (!best_addr)
    447 		goto bail;
    448 
    449 	    *prev_ptr = best_addr;
    450 	    prev_ptr = &sdp->hdr.next;
    451 
    452 	    if (syslinux_add_memmap(&amap, best_addr, size, SMT_ALLOC)) {
    453 		errno = ENOMEM;
    454 		goto bail;
    455 	    }
    456 	    if (syslinux_add_movelist(&fraglist, best_addr,
    457 				      (addr_t)&sdp->hdr, sizeof sdp->hdr)) {
    458 		errno = ENOMEM;
    459 		goto bail;
    460 	    }
    461 	    if (syslinux_add_movelist(&fraglist, best_addr + sizeof sdp->hdr,
    462 				      (addr_t)sdp->data, sdp->hdr.len)) {
    463 		errno = ENOMEM;
    464 		goto bail;
    465 	    }
    466 	}
    467     }
    468 
    469     /* Set up the registers on entry */
    470     memset(&regs, 0, sizeof regs);
    471     regs.es = regs.ds = regs.ss = regs.fs = regs.gs = real_mode_base >> 4;
    472     regs.cs = (real_mode_base >> 4) + 0x20;
    473     /* regs.ip = 0; */
    474     /* Linux is OK with sp = 0 = 64K, but perhaps other things aren't... */
    475     regs.esp.w[0] = min(cmdline_offset, (size_t) 0xfff0);
    476 
    477     dprintf("Final memory map:\n");
    478     syslinux_dump_memmap(mmap);
    479 
    480     dprintf("Final available map:\n");
    481     syslinux_dump_memmap(amap);
    482 
    483     dprintf("Initial movelist:\n");
    484     syslinux_dump_movelist(fraglist);
    485 
    486     if (video_mode != 0x0f04) {
    487 	/*
    488 	 * video_mode is not "current", so if we are in graphics mode we
    489 	 * need to revert to text mode...
    490 	 */
    491 	dprintf("*** Calling syslinux_force_text_mode()...\n");
    492 	syslinux_force_text_mode();
    493     } else {
    494 	dprintf("*** vga=current, not calling syslinux_force_text_mode()...\n");
    495     }
    496 
    497     syslinux_shuffle_boot_rm(fraglist, mmap, 0, &regs);
    498     dprintf("shuffle_boot_rm failed\n");
    499 
    500 bail:
    501     syslinux_free_movelist(fraglist);
    502     syslinux_free_memmap(mmap);
    503     syslinux_free_memmap(amap);
    504     return -1;
    505 }
    506 
    507 int syslinux_boot_linux(void *kernel_buf, size_t kernel_size,
    508 			struct initramfs *initramfs,
    509 			struct setup_data *setup_data,
    510 			char *cmdline)
    511 {
    512     if (firmware->boot_linux)
    513 	return firmware->boot_linux(kernel_buf, kernel_size, initramfs,
    514 				    setup_data, cmdline);
    515 
    516     return bios_boot_linux(kernel_buf, kernel_size, initramfs,
    517 			   setup_data, cmdline);
    518 }
    519