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