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