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 */
find_argument(const char * cmdline,const char * argument)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 */
saturate32(unsigned long long v)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 */
map_initramfs(struct syslinux_movelist ** fraglist,struct syslinux_memmap ** mmap,struct initramfs * initramfs,addr_t addr)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
calc_cmdline_offset(const struct syslinux_memmap * mmap,const struct linux_header * hdr,size_t cmdline_size,addr_t base,addr_t start)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
bios_boot_linux(void * kernel_buf,size_t kernel_size,struct initramfs * initramfs,struct setup_data * setup_data,char * cmdline)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(®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
500 bail:
501 syslinux_free_movelist(fraglist);
502 syslinux_free_memmap(mmap);
503 syslinux_free_memmap(amap);
504 return -1;
505 }
506
syslinux_boot_linux(void * kernel_buf,size_t kernel_size,struct initramfs * initramfs,struct setup_data * setup_data,char * cmdline)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