93fd62efc4d57c8b303dc62cbaae2822ca9dade1
[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 <stdio.h>
20 #include <unistd.h>
21 #include <getopt.h>
22 #include <errno.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <fcntl.h>
26 #include <sysexits.h>
27 #include <sys/mman.h>
28 #include <sys/stat.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
39 int wrap_kernel(const char *kernel_file, const char *cmdline,
40                 const struct string_list *initrd_list, FILE *out)
41 {
42   struct initrd_info {
43     int fd;
44     struct segment seg;
45   } *ird = NULL;
46   int kernel_fd = -1;
47   char *kernel = NULL;
48   size_t kernel_len = 0;
49   struct stat st;
50   struct segment srel, ssup, scmd, skrn;
51   struct startup_info *info = (void *)reloc;
52   struct setup_header *hdr;
53   size_t setup_len;
54   size_t initrd_len;
55   size_t initrd_addr;
56   int rv = -1;
57   int ninitrd = 0;
58   int i;
59   const struct string_list *ip;
60   int setup_ver;                /* Setup protocol version */
61   int setup_space;              /* How much space for the setup */
62
63   /* Process the kernel file */
64
65   kernel_fd = open(kernel_file, O_RDONLY);
66   if (kernel_fd < 0)
67     goto err;
68
69   if (fstat(kernel_fd, &st))
70     goto err;
71
72   kernel_len = st.st_size;
73
74   if (kernel_len < 1024)
75     goto err;
76
77   kernel = mmap(NULL, kernel_len, PROT_READ|PROT_WRITE, MAP_PRIVATE,
78                 kernel_fd, 0);
79   if (kernel == MAP_FAILED) {
80     kernel = NULL;
81     goto err;
82   }
83
84   /* Pick apart the header... */
85
86   hdr = (struct setup_header *)(kernel + 0x1f1);
87   setup_len = (hdr->setup_sects + 1) << 9;
88   if (setup_len == 512)
89     setup_len += 2048;          /* Really old kernel */
90
91   if (rdle32(&hdr->header) != 0x53726448)
92     setup_ver = 0;              /* Ancient kernel */
93   else
94     setup_ver = rdle16(&hdr->version);
95
96   if (setup_ver >= 0x200 && (hdr->loadflags & LOADED_HIGH)) {
97     skrn.address = 0x100000;
98     ssup.address = 0x10000;
99     setup_space  = setup_ver >= 0x202 ? 0x10000 : 0xa000;
100   } else {
101     skrn.address = 0x10000;
102     ssup.address = 0x90000;
103     setup_space  = 0xa000;
104   }
105
106   if (setup_ver <= 0x200)
107     initrd_list = NULL;         /* No initrd for ancient kernel */
108
109   /* Process the initrd file(s) */
110   
111   ninitrd = 0;
112   for (ip = initrd_list; ip; ip = ip->next)
113     ninitrd++;
114
115   if (ninitrd) {
116     ird = calloc(ninitrd, sizeof *ird);
117     if (!ird)
118       goto err;
119   }
120
121   initrd_len = 0;
122   for (ip = initrd_list, i = 0; ip; ip = ip->next, i++) {
123     ird[i].fd = open(ip->str, O_RDONLY);
124     if (ird[i].fd < 0)
125       return -1;
126   
127     if (fstat(ird[i].fd, &st))
128       goto err;
129
130     ird[i].seg.length = st.st_size;
131     ird[i].seg.data = mmap(NULL, ird[i].seg.length, PROT_READ,
132                            MAP_SHARED, ird[i].fd, 0);
133     if (ird[i].seg.data == MAP_FAILED) {
134       ird[i].seg.data = NULL;
135       goto err;
136     }
137     
138     /* We need to pad the space between initrds to a 4-byte boundary.
139        This is safe because an mmap is always padded with zero
140        to a page boundary... */
141     if (i < ninitrd-1)
142       ird[i].seg.length = (ird[i].seg.length + 3) & ~3;
143
144     initrd_len += ird[i].seg.length;
145   }
146
147   /* Segment: relocation code */
148   /* We put this immediately after the kernel setup, in the memory
149      which will be reclaimed for setup. */
150   srel.next     = &ssup;
151   srel.address  = (ssup.address + setup_len + 15) & ~15;
152   srel.length   = reloc_size;
153   srel.sh_type  = SHT_PROGBITS;
154   srel.sh_flags = SHF_ALLOC|SHF_WRITE|SHF_EXECINSTR;
155   srel.name     = "reloc";
156   srel.data     = reloc;
157
158   /* Segment: Linux kernel setup */
159   ssup.next     = &scmd;
160   ssup.length   = setup_len;
161   ssup.sh_type  = SHT_PROGBITS;
162   ssup.sh_flags = SHF_ALLOC|SHF_WRITE|SHF_EXECINSTR;
163   ssup.name     = "setup";
164   ssup.data     = kernel;
165   if (setup_ver >= 0x200)
166     hdr->type_of_loader = 0xff; /* "Other modern loader" */
167
168   /* Segment: kernel command line */
169   scmd.next     = &skrn;
170   scmd.length   = strlen(cmdline)+1;
171   scmd.address  = (ssup.address + setup_space - scmd.length) & ~15;
172   if (srel.address + reloc_size > scmd.address) {
173     /* Uh-oh, we're short on space... push the command line
174        higher. */
175     scmd.address = (srel.address + reloc_size + 15) & ~15;
176   }
177   scmd.sh_type  = SHT_PROGBITS;
178   scmd.sh_flags = SHF_ALLOC;
179   scmd.name     = "cmdline";
180   scmd.data     = cmdline;
181   if (setup_ver >= 0x202) {
182     wrle32(scmd.address, &hdr->cmd_line_ptr);
183   } else {
184     /* Old-style command line protocol */
185     wrle16(0xA33F, (uint16_t *)(kernel+0x20));
186     wrle16(scmd.address - ssup.address, (uint16_t *)(kernel+0x22));
187   }
188   if (setup_ver >= 0x201) {
189     wrle16(scmd.address - ssup.address - 0x200, &hdr->heap_end_ptr);
190     hdr->loadflags |= CAN_USE_HEAP;
191   }
192
193   /* Segment: Linux kernel proper */
194   skrn.next     = ninitrd ? &ird[0].seg : NULL;
195   skrn.length   = kernel_len - setup_len;
196   skrn.sh_type  = SHT_PROGBITS;
197   skrn.sh_flags = SHF_ALLOC;
198   skrn.name     = "kernel";
199   skrn.data     = kernel + setup_len;
200
201   /* Additional segments: initrd */
202   if (skrn.address < 0x100000)
203     initrd_addr = 0x100000;
204   else
205     initrd_addr = (skrn.address + skrn.length + 3) & ~3;
206
207   for (i = 0; i < ninitrd; i++) {
208     char *name;
209
210     ird[i].seg.next     = (i < ninitrd-1) ? &ird[i+1].seg : 0;
211     ird[i].seg.address  = initrd_addr;
212     ird[i].seg.sh_type  = SHT_PROGBITS;
213     ird[i].seg.sh_flags = SHF_ALLOC;
214     asprintf(&name, "initrd.%d", i);
215     ird[i].seg.name = name;
216
217     initrd_addr += ird[i].seg.length;
218   }
219   if (setup_ver >= 0x200)
220     wrle32(initrd_len, &hdr->ramdisk_size);
221
222   /* Set up the startup info */
223   wrle32(ninitrd ? ird[0].seg.address : 0, &info->rd_addr);
224   wrle32(initrd_len, &info->rd_len);
225   if (setup_ver >= 0x203)
226     wrle32(rdle32(&hdr->initrd_addr_max), &info->rd_maxaddr);
227   else
228     wrle32(0x37ffffff, &info->rd_maxaddr);
229   wrle32(ssup.address, &info->setup_addr);
230   wrle32(scmd.address, &info->cmdline_addr);
231
232   rv = opt.output(&srel, srel.address + sizeof *info, out);
233
234  err:
235   if (ird) {
236     for (i = 0; i < ninitrd; i++) {
237       if (ird[i].seg.data)
238         munmap((void *)ird[i].seg.data, ird[i].seg.length);
239       if (ird[i].fd > 0)
240       close(ird[i].fd);
241     }
242     free(ird);
243   }
244
245   if (kernel)
246     munmap(kernel, kernel_len);
247   if (kernel_fd >= 0)
248     close(kernel_fd);
249   
250   return rv;
251 }