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 */ 65static 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 */ 87static 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 */ 93static 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 127static 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 152int 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(®s, 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, ®s); 498 dprintf("shuffle_boot_rm failed\n"); 499 500bail: 501 syslinux_free_movelist(fraglist); 502 syslinux_free_memmap(mmap); 503 syslinux_free_memmap(amap); 504 return -1; 505} 506 507int 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