Handle mem= and vga= in the Linux kernel command line.
[wraplinux.git] / linux.c
1 /* ----------------------------------------------------------------------- *
2  *   
3  *   Copyright 2008 H. Peter Anvin - All Rights Reserved
4  *
5  *   This program is free software; you can redistribute it and/or modify
6  *   it under the terms of the GNU General Public License as published by
7  *   the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
8  *   Boston MA 02110-1301, USA; either version 2 of the License, or
9  *   (at your option) any later version; incorporated herein by reference.
10  *
11  * ----------------------------------------------------------------------- */
12
13 /*
14  * wrap.c
15  *
16  * Actually wrap the kernel image...
17  */
18
19 #include <ctype.h>
20 #include <stdio.h>
21 #include <unistd.h>
22 #include <getopt.h>
23 #include <errno.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <fcntl.h>
27 #include <sysexits.h>
28 #include <sys/mman.h>
29 #include <sys/stat.h>
30 #include <sys/types.h>
31 #include "segment.h"
32 #include "setup.h"
33 #include "elf32.h"
34 #include "le.h"
35 #include "wraplinux.h"
36
37 extern char reloc[];
38 extern uint32_t reloc_size;
39
40 /* Find the last instance of a particular command line argument
41    (which should include the final =; do not use for boolean arguments) */
42 static const char *find_argument(const char *cmdline, const char *argument)
43 {
44   int la = strlen(argument);
45   const char *p = cmdline;
46   int wasspace = 1;
47   
48   while (*p) {
49     if (wasspace && !memcmp(p, argument, la))
50       return p+la;
51
52     wasspace = isspace(*p++);
53   }
54
55   return NULL;
56 }
57
58 /* Truncate to 32 bits, with saturate */
59 static inline uint32_t saturate32(unsigned long long v)
60 {
61   return (v > 0xffffffff) ? 0xffffffff : (uint32_t)v;
62 }
63
64 /* Get a value with a potential suffix (k/m/g/t/p/e) */
65 static unsigned long long suffix_number(const char *str)
66 {
67   char *ep;
68   unsigned long long v;
69   int shift;
70
71   v = strtoull(str, &ep, 0);
72   switch (*ep|0x20) {
73   case 'k':
74     shift = 10;
75     break;
76   case 'm':
77     shift = 20;
78     break;
79   case 'g':
80     shift = 30;
81     break;
82   case 't':
83     shift = 40;
84     break;
85   case 'p':
86     shift = 50;
87     break;
88   case 'e':
89     shift = 60;
90     break;
91   default:
92     shift = 0;
93     break;
94   }
95   v <<= shift;
96
97   return v;
98 }
99
100 int wrap_kernel(const char *kernel_file, const char *cmdline,
101                 const struct string_list *initrd_list, FILE *out)
102 {
103   struct initrd_info {
104     int fd;
105     struct segment seg;
106   } *ird = NULL;
107   int kernel_fd = -1;
108   char *kernel = NULL;
109   size_t kernel_len = 0;
110   struct stat st;
111   struct segment srel, ssup, scmd, skrn;
112   struct startup_info *info = (void *)reloc;
113   struct setup_header *hdr;
114   size_t setup_len;
115   size_t initrd_len;
116   size_t initrd_addr;
117   int rv = -1;
118   int ninitrd = 0;
119   int i;
120   const struct string_list *ip;
121   int setup_ver;                /* Setup protocol version */
122   int setup_space;              /* How much space for the setup */
123   const char *cmd;
124   uint32_t initrd_max;
125
126   /* Process the kernel file */
127
128   kernel_fd = open(kernel_file, O_RDONLY);
129   if (kernel_fd < 0)
130     goto err;
131
132   if (fstat(kernel_fd, &st))
133     goto err;
134
135   kernel_len = st.st_size;
136
137   if (kernel_len < 1024)
138     goto err;
139
140   kernel = mmap(NULL, kernel_len, PROT_READ|PROT_WRITE, MAP_PRIVATE,
141                 kernel_fd, 0);
142   if (kernel == MAP_FAILED) {
143     kernel = NULL;
144     goto err;
145   }
146
147   /* Pick apart the header... */
148
149   hdr = (struct setup_header *)(kernel + 0x1f1);
150   setup_len = (hdr->setup_sects + 1) << 9;
151   if (setup_len == 512)
152     setup_len += 2048;          /* Really old kernel */
153
154   if (rdle32(&hdr->header) != LINUX_MAGIC)
155     setup_ver = 0;              /* Ancient kernel */
156   else
157     setup_ver = rdle16(&hdr->version);
158
159   if (setup_ver >= 0x200 && (hdr->loadflags & LOADED_HIGH)) {
160     skrn.address = 0x100000;
161     ssup.address = 0x10000;
162     setup_space  = setup_ver >= 0x202 ? 0x10000 : 0xa000;
163   } else {
164     skrn.address = 0x10000;
165     ssup.address = 0x90000;
166     setup_space  = 0xa000;
167   }
168
169   if (setup_ver <= 0x200)
170     initrd_list = NULL;         /* No initrd for ancient kernel */
171
172   if (setup_ver >= 0x203)
173     initrd_max = rdle32(&hdr->initrd_addr_max);
174   else
175     initrd_max = 0x37ffffff;
176
177   if ((cmd = find_argument(cmdline, "mem="))) {
178     uint32_t mem = saturate32(suffix_number(cmd));
179     if (initrd_max >= mem)
180       initrd_max = mem-1;
181   }
182
183   if ((cmd = find_argument(cmdline, "vga="))) {
184     uint16_t vga;
185
186     switch (cmd[0] | 0x20) {
187     case 'a':                   /* "ask" */
188       vga = 0xfffd;
189       break;
190     case 'e':                   /* "ext" */
191       vga = 0xfffe;
192       break;
193     case 'n':                   /* "normal" */
194       vga = 0xffff;
195       break;
196     default:
197       vga = strtoul(cmd, NULL, 0);
198       break;
199     }
200     wrle16(vga, &hdr->vid_mode);
201   }
202
203   /* Process the initrd file(s) */
204   
205   ninitrd = 0;
206   for (ip = initrd_list; ip; ip = ip->next)
207     ninitrd++;
208
209   if (ninitrd) {
210     ird = calloc(ninitrd, sizeof *ird);
211     if (!ird)
212       goto err;
213   }
214
215   initrd_len = 0;
216   for (ip = initrd_list, i = 0; ip; ip = ip->next, i++) {
217     ird[i].fd = open(ip->str, O_RDONLY);
218     if (ird[i].fd < 0)
219       return -1;
220   
221     if (fstat(ird[i].fd, &st))
222       goto err;
223
224     ird[i].seg.length = st.st_size;
225     ird[i].seg.data = mmap(NULL, ird[i].seg.length, PROT_READ,
226                            MAP_SHARED, ird[i].fd, 0);
227     if (ird[i].seg.data == MAP_FAILED) {
228       ird[i].seg.data = NULL;
229       goto err;
230     }
231     
232     /* We need to pad the space between initrds to a 4-byte boundary.
233        This is safe because an mmap is always padded with zero
234        to a page boundary... */
235     if (i < ninitrd-1)
236       ird[i].seg.length = (ird[i].seg.length + 3) & ~3;
237
238     initrd_len += ird[i].seg.length;
239   }
240
241   /* Segment: relocation code */
242   /* We put this immediately after the kernel setup, in the memory
243      which will be reclaimed for setup. */
244   srel.next     = &ssup;
245   srel.address  = (ssup.address + setup_len + 15) & ~15;
246   srel.length   = reloc_size;
247   srel.sh_type  = SHT_PROGBITS;
248   srel.sh_flags = SHF_ALLOC|SHF_WRITE|SHF_EXECINSTR;
249   srel.name     = "reloc";
250   srel.data     = reloc;
251
252   /* Segment: Linux kernel setup */
253   ssup.next     = &scmd;
254   ssup.length   = setup_len;
255   ssup.sh_type  = SHT_PROGBITS;
256   ssup.sh_flags = SHF_ALLOC|SHF_WRITE|SHF_EXECINSTR;
257   ssup.name     = "setup";
258   ssup.data     = kernel;
259   if (setup_ver >= 0x200)
260     hdr->type_of_loader = 0xff; /* "Other modern loader" */
261
262   /* Segment: kernel command line */
263   scmd.next     = &skrn;
264   scmd.length   = strlen(cmdline)+1;
265   scmd.address  = (ssup.address + setup_space - scmd.length) & ~15;
266   if (srel.address + reloc_size > scmd.address) {
267     /* Uh-oh, we're short on space... push the command line
268        higher. */
269     scmd.address = (srel.address + reloc_size + 15) & ~15;
270   }
271   scmd.sh_type  = SHT_PROGBITS;
272   scmd.sh_flags = SHF_ALLOC;
273   scmd.name     = "cmdline";
274   scmd.data     = cmdline;
275   if (setup_ver >= 0x202) {
276     wrle32(scmd.address, &hdr->cmd_line_ptr);
277   } else {
278     /* Old-style command line protocol */
279     wrle16(OLD_CMDLINE_MAGIC, (uint16_t *)(kernel+0x20));
280     wrle16(scmd.address - ssup.address, (uint16_t *)(kernel+0x22));
281   }
282   if (setup_ver >= 0x201) {
283     wrle16(scmd.address - ssup.address - 0x200, &hdr->heap_end_ptr);
284     hdr->loadflags |= CAN_USE_HEAP;
285   }
286
287   /* Segment: Linux kernel proper */
288   skrn.next     = ninitrd ? &ird[0].seg : NULL;
289   skrn.length   = kernel_len - setup_len;
290   skrn.sh_type  = SHT_PROGBITS;
291   skrn.sh_flags = SHF_ALLOC;
292   skrn.name     = "kernel";
293   skrn.data     = kernel + setup_len;
294
295   /* Additional segments: initrd */
296   if (skrn.address < 0x100000)
297     initrd_addr = 0x100000;
298   else
299     initrd_addr = (skrn.address + skrn.length + 3) & ~3;
300
301   for (i = 0; i < ninitrd; i++) {
302     char *name;
303
304     ird[i].seg.next     = (i < ninitrd-1) ? &ird[i+1].seg : 0;
305     ird[i].seg.address  = initrd_addr;
306     ird[i].seg.sh_type  = SHT_PROGBITS;
307     ird[i].seg.sh_flags = SHF_ALLOC;
308     asprintf(&name, "initrd.%d", i);
309     ird[i].seg.name = name;
310
311     initrd_addr += ird[i].seg.length;
312   }
313   if (setup_ver >= 0x200)
314     wrle32(initrd_len, &hdr->ramdisk_size);
315
316   /* Set up the startup info */
317   wrle32(ninitrd ? ird[0].seg.address : 0, &info->rd_addr);
318   wrle32(initrd_len, &info->rd_len);
319   wrle32(initrd_max, &info->rd_maxaddr);
320   wrle32(ssup.address, &info->setup_addr);
321   wrle32(scmd.address, &info->cmdline_addr);
322
323   rv = opt.output(&srel, srel.address + sizeof *info, out);
324
325  err:
326   if (ird) {
327     for (i = 0; i < ninitrd; i++) {
328       if (ird[i].seg.data)
329         munmap((void *)ird[i].seg.data, ird[i].seg.length);
330       if (ird[i].fd > 0)
331       close(ird[i].fd);
332     }
333     free(ird);
334   }
335
336   if (kernel)
337     munmap(kernel, kernel_len);
338   if (kernel_fd >= 0)
339     close(kernel_fd);
340   
341   return rv;
342 }