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(&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
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