setup_data: check to make sure kernel version >= 0x0209
[profile/ivi/syslinux.git] / com32 / lib / syslinux / load_linux.c
1 /* ----------------------------------------------------------------------- *
2  *
3  *   Copyright 2007-2009 H. Peter Anvin - All Rights Reserved
4  *   Copyright 2009-2011 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 <syslinux/align.h>
44 #include <syslinux/linux.h>
45 #include <syslinux/bootrm.h>
46 #include <syslinux/movebits.h>
47 #include <dprintf.h>
48
49 struct linux_header {
50     uint8_t boot_sector_1[0x0020];
51     uint16_t old_cmd_line_magic;
52     uint16_t old_cmd_line_offset;
53     uint8_t boot_sector_2[0x01f1 - 0x0024];
54     uint8_t setup_sects;
55     uint16_t root_flags;
56     uint32_t syssize;
57     uint16_t ram_size;
58     uint16_t vid_mode;
59     uint16_t root_dev;
60     uint16_t boot_flag;
61     uint16_t jump;
62     uint32_t header;
63     uint16_t version;
64     uint32_t realmode_swtch;
65     uint16_t start_sys;
66     uint16_t kernel_version;
67     uint8_t type_of_loader;
68     uint8_t loadflags;
69     uint16_t setup_move_size;
70     uint32_t code32_start;
71     uint32_t ramdisk_image;
72     uint32_t ramdisk_size;
73     uint32_t bootsect_kludge;
74     uint16_t heap_end_ptr;
75     uint16_t pad1;
76     uint32_t cmd_line_ptr;
77     uint32_t initrd_addr_max;
78     uint32_t kernel_alignment;
79     uint8_t relocatable_kernel;
80     uint8_t pad2[3];
81     uint32_t cmdline_max_len;
82     uint32_t hardware_subarch;
83     uint64_t hardware_subarch_data;
84     uint32_t payload_offset;
85     uint32_t payload_length;
86     uint64_t setup_data;
87     uint64_t pref_address;
88     uint32_t init_size;
89 } __packed;
90
91 #define BOOT_MAGIC 0xAA55
92 #define LINUX_MAGIC ('H' + ('d' << 8) + ('r' << 16) + ('S' << 24))
93 #define OLD_CMDLINE_MAGIC 0xA33F
94
95 /* loadflags */
96 #define LOAD_HIGH       0x01
97 #define CAN_USE_HEAP    0x80
98
99 /* 
100  * Find the last instance of a particular command line argument
101  * (which should include the final =; do not use for boolean arguments)
102  * Note: the resulting string is typically not null-terminated.
103  */
104 static const char *find_argument(const char *cmdline, const char *argument)
105 {
106     const char *found = NULL;
107     const char *p = cmdline;
108     bool was_space = true;
109     size_t la = strlen(argument);
110
111     while (*p) {
112         if (isspace(*p)) {
113             was_space = true;
114         } else if (was_space) {
115             if (!memcmp(p, argument, la))
116                 found = p + la;
117             was_space = false;
118         }
119         p++;
120     }
121
122     return found;
123 }
124
125 /* Truncate to 32 bits, with saturate */
126 static inline uint32_t saturate32(unsigned long long v)
127 {
128     return (v > 0xffffffff) ? 0xffffffff : (uint32_t) v;
129 }
130
131 /* Get the combined size of the initramfs */
132 static addr_t initramfs_size(struct initramfs *initramfs)
133 {
134     struct initramfs *ip;
135     addr_t size = 0;
136
137     if (!initramfs)
138         return 0;
139
140     for (ip = initramfs->next; ip->len; ip = ip->next) {
141         size = (size + ip->align - 1) & ~(ip->align - 1);       /* Alignment */
142         size += ip->len;
143     }
144
145     return size;
146 }
147
148 /* Create the appropriate mappings for the initramfs */
149 static int map_initramfs(struct syslinux_movelist **fraglist,
150                          struct syslinux_memmap **mmap,
151                          struct initramfs *initramfs, addr_t addr)
152 {
153     struct initramfs *ip;
154     addr_t next_addr, len, pad;
155
156     for (ip = initramfs->next; ip->len; ip = ip->next) {
157         len = ip->len;
158         next_addr = addr + len;
159
160         /* If this isn't the last entry, extend the zero-pad region
161            to enforce the alignment of the next chunk. */
162         if (ip->next->len) {
163             pad = -next_addr & (ip->next->align - 1);
164             len += pad;
165             next_addr += pad;
166         }
167
168         if (ip->data_len) {
169             if (syslinux_add_movelist(fraglist, addr, (addr_t) ip->data, len))
170                 return -1;
171         }
172         if (len > ip->data_len) {
173             if (syslinux_add_memmap(mmap, addr + ip->data_len,
174                                     len - ip->data_len, SMT_ZERO))
175                 return -1;
176         }
177         addr = next_addr;
178     }
179
180     return 0;
181 }
182
183 int syslinux_boot_linux(void *kernel_buf, size_t kernel_size,
184                         struct initramfs *initramfs,
185                         struct setup_data *setup_data,
186                         char *cmdline)
187 {
188     struct linux_header hdr, *whdr;
189     size_t real_mode_size, prot_mode_size;
190     addr_t real_mode_base, prot_mode_base;
191     addr_t irf_size;
192     size_t cmdline_size, cmdline_offset;
193     struct setup_data *sdp;
194     struct syslinux_rm_regs regs;
195     struct syslinux_movelist *fraglist = NULL;
196     struct syslinux_memmap *mmap = NULL;
197     struct syslinux_memmap *amap = NULL;
198     bool ok;
199     uint32_t memlimit = 0;
200     uint16_t video_mode = 0;
201     const char *arg;
202
203     cmdline_size = strlen(cmdline) + 1;
204
205     if (kernel_size < 2 * 512)
206         goto bail;
207
208     /* Look for specific command-line arguments we care about */
209     if ((arg = find_argument(cmdline, "mem=")))
210         memlimit = saturate32(suffix_number(arg));
211
212     if ((arg = find_argument(cmdline, "vga="))) {
213         switch (arg[0] | 0x20) {
214         case 'a':               /* "ask" */
215             video_mode = 0xfffd;
216             break;
217         case 'e':               /* "ext" */
218             video_mode = 0xfffe;
219             break;
220         case 'n':               /* "normal" */
221             video_mode = 0xffff;
222             break;
223         case 'c':               /* "current" */
224             video_mode = 0x0f04;
225             break;
226         default:
227             video_mode = strtoul(arg, NULL, 0);
228             break;
229         }
230     }
231
232     /* Copy the header into private storage */
233     /* Use whdr to modify the actual kernel header */
234     memcpy(&hdr, kernel_buf, sizeof hdr);
235     whdr = (struct linux_header *)kernel_buf;
236
237     if (hdr.boot_flag != BOOT_MAGIC)
238         goto bail;
239
240     if (hdr.header != LINUX_MAGIC) {
241         hdr.version = 0x0100;   /* Very old kernel */
242         hdr.loadflags = 0;
243     }
244
245     whdr->vid_mode = video_mode;
246
247     if (!hdr.setup_sects)
248         hdr.setup_sects = 4;
249
250     if (hdr.version < 0x0203)
251         hdr.initrd_addr_max = 0x37ffffff;
252
253     if (!memlimit && memlimit - 1 > hdr.initrd_addr_max)
254         memlimit = hdr.initrd_addr_max + 1;     /* Zero for no limit */
255
256     if (hdr.version < 0x0205 || !(hdr.loadflags & LOAD_HIGH))
257         hdr.relocatable_kernel = 0;
258
259     if (hdr.version < 0x0206)
260         hdr.cmdline_max_len = 256;
261
262     if (cmdline_size > hdr.cmdline_max_len) {
263         cmdline_size = hdr.cmdline_max_len;
264         cmdline[cmdline_size - 1] = '\0';
265     }
266
267     if (hdr.version < 0x0202 || !(hdr.loadflags & 0x01))
268         cmdline_offset = (0x9ff0 - cmdline_size) & ~15;
269     else
270         cmdline_offset = 0x10000;
271
272     real_mode_size = (hdr.setup_sects + 1) << 9;
273     real_mode_base = (hdr.loadflags & LOAD_HIGH) ? 0x10000 : 0x90000;
274     prot_mode_base = (hdr.loadflags & LOAD_HIGH) ? 0x100000 : 0x10000;
275     prot_mode_size = kernel_size - real_mode_size;
276
277     if (hdr.version < 0x020a) {
278         /*
279          * The 3* here is a total fudge factor... it's supposed to
280          * account for the fact that the kernel needs to be
281          * decompressed, and then followed by the BSS and BRK regions.
282          * This doesn't, however, account for the fact that the kernel
283          * is decompressed into a whole other place, either.
284          */
285         hdr.init_size = 3 * prot_mode_size;
286     }
287
288     if (!(hdr.loadflags & LOAD_HIGH) && prot_mode_size > 512 * 1024)
289         goto bail;              /* Kernel cannot be loaded low */
290
291     if (initramfs && hdr.version < 0x0200)
292         goto bail;              /* initrd/initramfs not supported */
293
294     if (hdr.version >= 0x0200) {
295         whdr->type_of_loader = 0x30;    /* SYSLINUX unknown module */
296         if (hdr.version >= 0x0201) {
297             whdr->heap_end_ptr = cmdline_offset - 0x0200;
298             whdr->loadflags |= CAN_USE_HEAP;
299         }
300         if (hdr.version >= 0x0202) {
301             whdr->cmd_line_ptr = real_mode_base + cmdline_offset;
302         } else {
303             whdr->old_cmd_line_magic = OLD_CMDLINE_MAGIC;
304             whdr->old_cmd_line_offset = cmdline_offset;
305             /* Be paranoid and round up to a multiple of 16 */
306             whdr->setup_move_size = (cmdline_offset + cmdline_size + 15) & ~15;
307         }
308     }
309
310     /* Get the memory map */
311     mmap = syslinux_memory_map();       /* Memory map for shuffle_boot */
312     amap = syslinux_dup_memmap(mmap);   /* Keep track of available memory */
313     if (!mmap || !amap)
314         goto bail;
315
316     dprintf("Initial memory map:\n");
317     syslinux_dump_memmap(mmap);
318
319     /* If the user has specified a memory limit, mark that as unavailable.
320        Question: should we mark this off-limit in the mmap as well (meaning
321        it's unavailable to the boot loader, which probably has already touched
322        some of it), or just in the amap? */
323     if (memlimit)
324         if (syslinux_add_memmap(&amap, memlimit, -memlimit, SMT_RESERVED))
325             goto bail;
326
327     /* Place the kernel in memory */
328
329     /* First, find a suitable place for the protected-mode code */
330     if (syslinux_memmap_type(amap, prot_mode_base, prot_mode_size)
331         != SMT_FREE) {
332         const struct syslinux_memmap *mp;
333         if (!hdr.relocatable_kernel)
334             goto bail;          /* Can't relocate - no hope */
335
336         ok = false;
337         for (mp = amap; mp; mp = mp->next) {
338             addr_t start, end;
339             start = mp->start;
340             end = mp->next->start;
341
342             if (mp->type != SMT_FREE)
343                 continue;
344
345             if (end <= prot_mode_base)
346                 continue;       /* Only relocate upwards */
347
348             if (start <= prot_mode_base)
349                 start = prot_mode_base;
350
351             start = ALIGN_UP(start, hdr.kernel_alignment);
352             if (start >= end)
353                 continue;
354
355             if (end - start >= hdr.init_size) {
356                 whdr->code32_start += start - prot_mode_base;
357                 prot_mode_base = start;
358                 ok = true;
359                 break;
360             }
361         }
362
363         if (!ok)
364             goto bail;
365     }
366
367     /* Real mode code */
368     if (syslinux_memmap_type(amap, real_mode_base,
369                              cmdline_offset + cmdline_size) != SMT_FREE) {
370         const struct syslinux_memmap *mp;
371
372         ok = false;
373         for (mp = amap; mp; mp = mp->next) {
374             addr_t start, end;
375             start = mp->start;
376             end = mp->next->start;
377
378             if (mp->type != SMT_FREE)
379                 continue;
380
381             if (start < real_mode_base)
382                 start = real_mode_base; /* Lowest address we'll use */
383             if (end > 640 * 1024)
384                 end = 640 * 1024;
385
386             start = ALIGN_UP(start, 16);
387             if (start > 0x90000 || start >= end)
388                 continue;
389
390             if (end - start >= cmdline_offset + cmdline_size) {
391                 real_mode_base = start;
392                 ok = true;
393                 break;
394             }
395         }
396     }
397
398     if (syslinux_add_movelist(&fraglist, real_mode_base, (addr_t) kernel_buf,
399                               real_mode_size))
400         goto bail;
401     if (syslinux_add_memmap
402         (&amap, real_mode_base, cmdline_offset + cmdline_size, SMT_ALLOC))
403         goto bail;
404
405     /* Zero region between real mode code and cmdline */
406     if (syslinux_add_memmap(&mmap, real_mode_base + real_mode_size,
407                             cmdline_offset - real_mode_size, SMT_ZERO))
408         goto bail;
409
410     /* Command line */
411     if (syslinux_add_movelist(&fraglist, real_mode_base + cmdline_offset,
412                               (addr_t) cmdline, cmdline_size))
413         goto bail;
414
415     /* Protected-mode code */
416     if (syslinux_add_movelist(&fraglist, prot_mode_base,
417                               (addr_t) kernel_buf + real_mode_size,
418                               prot_mode_size))
419         goto bail;
420     if (syslinux_add_memmap(&amap, prot_mode_base, prot_mode_size, SMT_ALLOC))
421         goto bail;
422
423     /* Figure out the size of the initramfs, and where to put it.
424        We should put it at the highest possible address which is
425        <= hdr.initrd_addr_max, which fits the entire initramfs. */
426
427     irf_size = initramfs_size(initramfs);       /* Handles initramfs == NULL */
428
429     if (irf_size) {
430         addr_t best_addr = 0;
431         struct syslinux_memmap *ml;
432         const addr_t align_mask = INITRAMFS_MAX_ALIGN - 1;
433
434         if (irf_size) {
435             for (ml = amap; ml->type != SMT_END; ml = ml->next) {
436                 addr_t adj_start = (ml->start + align_mask) & ~align_mask;
437                 addr_t adj_end = ml->next->start & ~align_mask;
438                 if (ml->type == SMT_FREE && adj_end - adj_start >= irf_size)
439                     best_addr = (adj_end - irf_size) & ~align_mask;
440             }
441
442             if (!best_addr)
443                 goto bail;      /* Insufficient memory for initramfs */
444
445             whdr->ramdisk_image = best_addr;
446             whdr->ramdisk_size = irf_size;
447
448             if (syslinux_add_memmap(&amap, best_addr, irf_size, SMT_ALLOC))
449                 goto bail;
450
451             if (map_initramfs(&fraglist, &mmap, initramfs, best_addr))
452                 goto bail;
453         }
454     }
455
456     if (setup_data) {
457         uint64_t *prev_ptr = &whdr->setup_data;
458
459         for (sdp = setup_data->next; sdp != setup_data; sdp = sdp->next) {
460             struct syslinux_memmap *ml;
461             const addr_t align_mask = 15; /* Header is 16 bytes */
462             addr_t best_addr = 0;
463             size_t size = sdp->hdr.len + sizeof(sdp->hdr);
464
465             if (hdr.version < 0x0209) {
466                 /* Setup data not supported */
467                 errno = ENXIO;  /* Kind of arbitrary... */
468                 goto bail;
469             }
470
471             if (!sdp->data || !sdp->hdr.len)
472                 continue;
473
474             for (ml = amap; ml->type != SMT_END; ml = ml->next) {
475                 addr_t adj_start = (ml->start + align_mask) & ~align_mask;
476                 addr_t adj_end = ml->next->start & ~align_mask;
477
478                 if (ml->type == SMT_FREE && adj_end - adj_start >= size)
479                     best_addr = (adj_end - size) & ~align_mask;
480             }
481
482             if (!best_addr)
483                 goto bail;
484
485             *prev_ptr = best_addr;
486             prev_ptr = &sdp->hdr.next;
487
488             if (syslinux_add_memmap(&amap, best_addr, size, SMT_ALLOC))
489                 goto bail;
490             if (syslinux_add_movelist(&fraglist, best_addr,
491                                       (addr_t)&sdp->hdr, sizeof sdp->hdr))
492                 goto bail;
493             if (syslinux_add_movelist(&fraglist, best_addr + sizeof sdp->hdr,
494                                       (addr_t)sdp->data, sdp->hdr.len))
495                 goto bail;
496         }
497     }
498
499     /* Set up the registers on entry */
500     memset(&regs, 0, sizeof regs);
501     regs.es = regs.ds = regs.ss = regs.fs = regs.gs = real_mode_base >> 4;
502     regs.cs = (real_mode_base >> 4) + 0x20;
503     /* regs.ip = 0; */
504     /* Linux is OK with sp = 0 = 64K, but perhaps other things aren't... */
505     regs.esp.w[0] = min(cmdline_offset, (size_t) 0xfff0);
506
507     dprintf("Final memory map:\n");
508     syslinux_dump_memmap(mmap);
509
510     dprintf("Final available map:\n");
511     syslinux_dump_memmap(amap);
512
513     dprintf("Initial movelist:\n");
514     syslinux_dump_movelist(fraglist);
515
516     syslinux_shuffle_boot_rm(fraglist, mmap, 0, &regs);
517
518 bail:
519     syslinux_free_movelist(fraglist);
520     syslinux_free_memmap(mmap);
521     syslinux_free_memmap(amap);
522     return -1;
523 }