Adding upstream version 6.01~pre5+dfsg.
[syslinux-debian/hramrach.git] / com32 / lib / syslinux / load_linux.c
blob914258ba633ca5b9c35d73f82e89c2534b62e6b0
1 /* ----------------------------------------------------------------------- *
3 * Copyright 2007-2009 H. Peter Anvin - All Rights Reserved
4 * Copyright 2009-2013 Intel Corporation; author: H. Peter Anvin
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:
15 * The above copyright notice and this permission notice shall
16 * be included in all copies or substantial portions of the Software.
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.
27 * ----------------------------------------------------------------------- */
30 * load_linux.c
32 * Load a Linux kernel (Image/zImage/bzImage).
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 <graphics.h>
44 #include <dprintf.h>
46 #include <syslinux/align.h>
47 #include <syslinux/linux.h>
48 #include <syslinux/bootrm.h>
49 #include <syslinux/movebits.h>
50 #include <syslinux/firmware.h>
52 #define BOOT_MAGIC 0xAA55
53 #define LINUX_MAGIC ('H' + ('d' << 8) + ('r' << 16) + ('S' << 24))
54 #define OLD_CMDLINE_MAGIC 0xA33F
56 /* loadflags */
57 #define LOAD_HIGH 0x01
58 #define CAN_USE_HEAP 0x80
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.
65 static const char *find_argument(const char *cmdline, const char *argument)
67 const char *found = NULL;
68 const char *p = cmdline;
69 bool was_space = true;
70 size_t la = strlen(argument);
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;
80 p++;
83 return found;
86 /* Truncate to 32 bits, with saturate */
87 static inline uint32_t saturate32(unsigned long long v)
89 return (v > 0xffffffff) ? 0xffffffff : (uint32_t) v;
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)
97 struct initramfs *ip;
98 addr_t next_addr, len, pad;
100 for (ip = initramfs->next; ip->len; ip = ip->next) {
101 len = ip->len;
102 next_addr = addr + len;
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;
112 if (ip->data_len) {
113 if (syslinux_add_movelist(fraglist, addr, (addr_t) ip->data, len))
114 return -1;
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;
121 addr = next_addr;
124 return 0;
127 int bios_boot_linux(void *kernel_buf, size_t kernel_size,
128 struct initramfs *initramfs,
129 struct setup_data *setup_data,
130 char *cmdline)
132 struct linux_header hdr, *whdr;
133 size_t real_mode_size, prot_mode_size;
134 addr_t real_mode_base, prot_mode_base;
135 addr_t irf_size;
136 size_t cmdline_size, cmdline_offset;
137 struct setup_data *sdp;
138 struct syslinux_rm_regs regs;
139 struct syslinux_movelist *fraglist = NULL;
140 struct syslinux_memmap *mmap = NULL;
141 struct syslinux_memmap *amap = NULL;
142 bool ok;
143 uint32_t memlimit = 0;
144 uint16_t video_mode = 0;
145 const char *arg;
147 cmdline_size = strlen(cmdline) + 1;
149 errno = EINVAL;
150 if (kernel_size < 2 * 512)
151 goto bail;
153 /* Look for specific command-line arguments we care about */
154 if ((arg = find_argument(cmdline, "mem=")))
155 memlimit = saturate32(suffix_number(arg));
157 if ((arg = find_argument(cmdline, "vga="))) {
158 switch (arg[0] | 0x20) {
159 case 'a': /* "ask" */
160 video_mode = 0xfffd;
161 break;
162 case 'e': /* "ext" */
163 video_mode = 0xfffe;
164 break;
165 case 'n': /* "normal" */
166 video_mode = 0xffff;
167 break;
168 case 'c': /* "current" */
169 video_mode = 0x0f04;
170 break;
171 default:
172 video_mode = strtoul(arg, NULL, 0);
173 break;
177 /* Copy the header into private storage */
178 /* Use whdr to modify the actual kernel header */
179 memcpy(&hdr, kernel_buf, sizeof hdr);
180 whdr = (struct linux_header *)kernel_buf;
182 if (hdr.boot_flag != BOOT_MAGIC)
183 goto bail;
185 if (hdr.header != LINUX_MAGIC) {
186 hdr.version = 0x0100; /* Very old kernel */
187 hdr.loadflags = 0;
190 whdr->vid_mode = video_mode;
192 if (!hdr.setup_sects)
193 hdr.setup_sects = 4;
195 if (hdr.version < 0x0203)
196 hdr.initrd_addr_max = 0x37ffffff;
198 if (!memlimit && memlimit - 1 > hdr.initrd_addr_max)
199 memlimit = hdr.initrd_addr_max + 1; /* Zero for no limit */
201 if (hdr.version < 0x0205 || !(hdr.loadflags & LOAD_HIGH))
202 hdr.relocatable_kernel = 0;
204 if (hdr.version < 0x0206)
205 hdr.cmdline_max_len = 256;
207 if (cmdline_size > hdr.cmdline_max_len) {
208 cmdline_size = hdr.cmdline_max_len;
209 cmdline[cmdline_size - 1] = '\0';
212 if (hdr.version < 0x0202 || !(hdr.loadflags & 0x01))
213 cmdline_offset = (0x9ff0 - cmdline_size) & ~15;
214 else
215 cmdline_offset = 0x10000;
217 real_mode_size = (hdr.setup_sects + 1) << 9;
218 real_mode_base = (hdr.loadflags & LOAD_HIGH) ? 0x10000 : 0x90000;
219 prot_mode_base = (hdr.loadflags & LOAD_HIGH) ? 0x100000 : 0x10000;
220 prot_mode_size = kernel_size - real_mode_size;
222 if (hdr.version < 0x020a) {
224 * The 3* here is a total fudge factor... it's supposed to
225 * account for the fact that the kernel needs to be
226 * decompressed, and then followed by the BSS and BRK regions.
227 * This doesn't, however, account for the fact that the kernel
228 * is decompressed into a whole other place, either.
230 hdr.init_size = 3 * prot_mode_size;
233 if (!(hdr.loadflags & LOAD_HIGH) && prot_mode_size > 512 * 1024)
234 goto bail; /* Kernel cannot be loaded low */
236 /* Get the size of the initramfs, if there is one */
237 irf_size = initramfs_size(initramfs);
239 if (irf_size && hdr.version < 0x0200)
240 goto bail; /* initrd/initramfs not supported */
242 if (hdr.version >= 0x0200) {
243 whdr->type_of_loader = 0x30; /* SYSLINUX unknown module */
244 if (hdr.version >= 0x0201) {
245 whdr->heap_end_ptr = cmdline_offset - 0x0200;
246 whdr->loadflags |= CAN_USE_HEAP;
250 /* Get the memory map */
251 mmap = syslinux_memory_map(); /* Memory map for shuffle_boot */
252 amap = syslinux_dup_memmap(mmap); /* Keep track of available memory */
253 if (!mmap || !amap) {
254 errno = ENOMEM;
255 goto bail;
258 dprintf("Initial memory map:\n");
259 syslinux_dump_memmap(mmap);
261 /* If the user has specified a memory limit, mark that as unavailable.
262 Question: should we mark this off-limit in the mmap as well (meaning
263 it's unavailable to the boot loader, which probably has already touched
264 some of it), or just in the amap? */
265 if (memlimit)
266 if (syslinux_add_memmap(&amap, memlimit, -memlimit, SMT_RESERVED)) {
267 errno = ENOMEM;
268 goto bail;
271 /* Place the kernel in memory */
273 /* First, find a suitable place for the protected-mode code */
274 if (prot_mode_size &&
275 syslinux_memmap_type(amap, prot_mode_base, prot_mode_size)
276 != SMT_FREE) {
277 const struct syslinux_memmap *mp;
278 if (!hdr.relocatable_kernel)
279 goto bail; /* Can't relocate - no hope */
281 ok = false;
282 for (mp = amap; mp; mp = mp->next) {
283 addr_t start, end;
284 start = mp->start;
285 end = mp->next->start;
287 if (mp->type != SMT_FREE)
288 continue;
290 if (end <= prot_mode_base)
291 continue; /* Only relocate upwards */
293 if (start <= prot_mode_base)
294 start = prot_mode_base;
296 start = ALIGN_UP(start, hdr.kernel_alignment);
297 if (start >= end)
298 continue;
300 if (end - start >= hdr.init_size) {
301 whdr->code32_start += start - prot_mode_base;
302 prot_mode_base = start;
303 ok = true;
304 break;
308 if (!ok)
309 goto bail;
312 /* Real mode code */
313 if (syslinux_memmap_type(amap, real_mode_base,
314 cmdline_offset + cmdline_size) != SMT_FREE) {
315 const struct syslinux_memmap *mp;
317 ok = false;
318 for (mp = amap; mp; mp = mp->next) {
319 addr_t start, end;
320 start = mp->start;
321 end = mp->next->start;
323 if (mp->type != SMT_FREE)
324 continue;
326 if (start < real_mode_base)
327 start = real_mode_base; /* Lowest address we'll use */
328 if (end > 640 * 1024)
329 end = 640 * 1024;
331 start = ALIGN_UP(start, 16);
332 if (start > 0x90000 || start >= end)
333 continue;
335 if (end - start >= cmdline_offset + cmdline_size) {
336 real_mode_base = start;
337 ok = true;
338 break;
342 if (!ok)
343 goto bail;
346 if (syslinux_add_movelist(&fraglist, real_mode_base, (addr_t) kernel_buf,
347 real_mode_size))
348 goto bail;
349 if (syslinux_add_memmap
350 (&amap, real_mode_base, cmdline_offset + cmdline_size, SMT_ALLOC)) {
351 errno = ENOMEM;
352 goto bail;
355 /* Zero region between real mode code and cmdline */
356 if (syslinux_add_memmap(&mmap, real_mode_base + real_mode_size,
357 cmdline_offset - real_mode_size, SMT_ZERO)) {
358 errno = ENOMEM;
359 goto bail;
362 /* Command line */
363 if (syslinux_add_movelist(&fraglist, real_mode_base + cmdline_offset,
364 (addr_t) cmdline, cmdline_size)) {
365 errno = ENOMEM;
366 goto bail;
368 if (hdr.version >= 0x0202) {
369 whdr->cmd_line_ptr = real_mode_base + cmdline_offset;
370 } else {
371 whdr->old_cmd_line_magic = OLD_CMDLINE_MAGIC;
372 whdr->old_cmd_line_offset = cmdline_offset;
373 if (hdr.version >= 0x0200) {
374 /* Be paranoid and round up to a multiple of 16 */
375 whdr->setup_move_size = (cmdline_offset + cmdline_size + 15) & ~15;
379 /* Protected-mode code */
380 if (prot_mode_size) {
381 if (syslinux_add_movelist(&fraglist, prot_mode_base,
382 (addr_t) kernel_buf + real_mode_size,
383 prot_mode_size)) {
384 errno = ENOMEM;
385 goto bail;
387 if (syslinux_add_memmap(&amap, prot_mode_base, prot_mode_size,
388 SMT_ALLOC)) {
389 errno = ENOMEM;
390 goto bail;
394 /* Figure out the size of the initramfs, and where to put it.
395 We should put it at the highest possible address which is
396 <= hdr.initrd_addr_max, which fits the entire initramfs. */
398 if (irf_size) {
399 addr_t best_addr = 0;
400 struct syslinux_memmap *ml;
401 const addr_t align_mask = INITRAMFS_MAX_ALIGN - 1;
403 if (irf_size) {
404 for (ml = amap; ml->type != SMT_END; ml = ml->next) {
405 addr_t adj_start = (ml->start + align_mask) & ~align_mask;
406 addr_t adj_end = ml->next->start & ~align_mask;
407 if (ml->type == SMT_FREE && adj_end - adj_start >= irf_size)
408 best_addr = (adj_end - irf_size) & ~align_mask;
411 if (!best_addr)
412 goto bail; /* Insufficient memory for initramfs */
414 whdr->ramdisk_image = best_addr;
415 whdr->ramdisk_size = irf_size;
417 if (syslinux_add_memmap(&amap, best_addr, irf_size, SMT_ALLOC)) {
418 errno = ENOMEM;
419 goto bail;
422 if (map_initramfs(&fraglist, &mmap, initramfs, best_addr)) {
423 errno = ENOMEM;
424 goto bail;
429 if (setup_data) {
430 uint64_t *prev_ptr = &whdr->setup_data;
432 for (sdp = setup_data->next; sdp != setup_data; sdp = sdp->next) {
433 struct syslinux_memmap *ml;
434 const addr_t align_mask = 15; /* Header is 16 bytes */
435 addr_t best_addr = 0;
436 size_t size = sdp->hdr.len + sizeof(sdp->hdr);
438 if (!sdp->data || !sdp->hdr.len)
439 continue;
441 if (hdr.version < 0x0209) {
442 /* Setup data not supported */
443 errno = ENXIO; /* Kind of arbitrary... */
444 goto bail;
447 for (ml = amap; ml->type != SMT_END; ml = ml->next) {
448 addr_t adj_start = (ml->start + align_mask) & ~align_mask;
449 addr_t adj_end = ml->next->start & ~align_mask;
451 if (ml->type == SMT_FREE && adj_end - adj_start >= size)
452 best_addr = (adj_end - size) & ~align_mask;
455 if (!best_addr)
456 goto bail;
458 *prev_ptr = best_addr;
459 prev_ptr = &sdp->hdr.next;
461 if (syslinux_add_memmap(&amap, best_addr, size, SMT_ALLOC)) {
462 errno = ENOMEM;
463 goto bail;
465 if (syslinux_add_movelist(&fraglist, best_addr,
466 (addr_t)&sdp->hdr, sizeof sdp->hdr)) {
467 errno = ENOMEM;
468 goto bail;
470 if (syslinux_add_movelist(&fraglist, best_addr + sizeof sdp->hdr,
471 (addr_t)sdp->data, sdp->hdr.len)) {
472 errno = ENOMEM;
473 goto bail;
478 /* Set up the registers on entry */
479 memset(&regs, 0, sizeof regs);
480 regs.es = regs.ds = regs.ss = regs.fs = regs.gs = real_mode_base >> 4;
481 regs.cs = (real_mode_base >> 4) + 0x20;
482 /* regs.ip = 0; */
483 /* Linux is OK with sp = 0 = 64K, but perhaps other things aren't... */
484 regs.esp.w[0] = min(cmdline_offset, (size_t) 0xfff0);
486 dprintf("Final memory map:\n");
487 syslinux_dump_memmap(mmap);
489 dprintf("Final available map:\n");
490 syslinux_dump_memmap(amap);
492 dprintf("Initial movelist:\n");
493 syslinux_dump_movelist(fraglist);
495 if (video_mode != 0x0f04) {
497 * video_mode is not "current", so if we are in graphics mode we
498 * need to revert to text mode...
500 dprintf("*** Calling syslinux_force_text_mode()...\n");
501 syslinux_force_text_mode();
502 } else {
503 dprintf("*** vga=current, not calling syslinux_force_text_mode()...\n");
506 syslinux_shuffle_boot_rm(fraglist, mmap, 0, &regs);
508 bail:
509 syslinux_free_movelist(fraglist);
510 syslinux_free_memmap(mmap);
511 syslinux_free_memmap(amap);
512 return -1;
515 int syslinux_boot_linux(void *kernel_buf, size_t kernel_size,
516 struct initramfs *initramfs,
517 struct setup_data *setup_data,
518 char *cmdline)
520 if (firmware->boot_linux)
521 return firmware->boot_linux(kernel_buf, kernel_size, initramfs,
522 setup_data, cmdline);
524 return bios_boot_linux(kernel_buf, kernel_size, initramfs,
525 setup_data, cmdline);