Initial commit: functional for newer bzImage kernels
[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 const 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, sinf, ssup, scmd, skrn;
51   struct startup_info info;
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
61   ninitrd = 0;
62   for (ip = initrd_list; ip; ip = ip->next)
63     ninitrd++;
64
65   if (ninitrd) {
66     ird = calloc(ninitrd, sizeof *ird);
67     if (!ird)
68       goto err;
69   }
70
71   kernel_fd = open(kernel_file, O_RDONLY);
72   if (kernel_fd < 0)
73     goto err;
74
75   if (fstat(kernel_fd, &st))
76     goto err;
77
78   kernel_len = st.st_size;
79
80   if (kernel_len < 1024)
81     goto err;
82
83   kernel = mmap(NULL, kernel_len, PROT_READ|PROT_WRITE, MAP_PRIVATE,
84                 kernel_fd, 0);
85   if (kernel == MAP_FAILED) {
86     kernel = NULL;
87     goto err;
88   }
89
90   initrd_len = 0;
91   for (ip = initrd_list, i = 0; ip; ip = ip->next, i++) {
92     ird[i].fd = open(ip->str, O_RDONLY);
93     if (ird[i].fd < 0)
94       return -1;
95   
96     if (fstat(ird[i].fd, &st))
97       goto err;
98
99     ird[i].seg.length = st.st_size;
100     ird[i].seg.data = mmap(NULL, ird[i].seg.length, PROT_READ,
101                            MAP_SHARED, ird[i].fd, 0);
102     if (ird[i].seg.data == MAP_FAILED) {
103       ird[i].seg.data = NULL;
104       goto err;
105     }
106     
107     /* We need to pad the space between initrds to a 4-byte boundary.
108        This is safe because an mmap is always padded with zero
109        to a page boundary... */
110     if (i < ninitrd-1)
111       ird[i].seg.length = (ird[i].seg.length + 3) & ~3;
112
113     initrd_len += ird[i].seg.length;
114   }
115
116   hdr = (struct setup_header *)(kernel + 0x1f1);
117   setup_len = (hdr->setup_sects + 1) << 9;
118   if (setup_len == 512)
119     setup_len += 2048;          /* Really old kernel */
120   
121   /* Segment 1 is the relocation code */
122   srel.next     = &ssup;
123   srel.address  = 0xc000;       /* Can be adjusted (align 16),
124                                    code is relocatable */
125   srel.length   = reloc_size;
126   srel.sh_type  = SHT_PROGBITS;
127   srel.sh_flags = SHF_ALLOC|SHF_WRITE|SHF_EXECINSTR;
128   srel.name     = "reloc";
129   srel.data     = reloc;
130
131   /* Segment 0 is the struct startup_info */
132   sinf.next     = &srel;
133   sinf.address  = srel.address - sizeof info;
134   sinf.length   = sizeof info;
135   sinf.sh_type  = SHT_PROGBITS;
136   sinf.sh_flags = SHF_ALLOC;
137   sinf.name     = "startup_info";
138   sinf.data     = &info;
139
140   /* Segment 2 is the Linux kernel setup */
141   ssup.next     = &scmd;
142   ssup.address  = 0x10000;      /* XXX */
143   ssup.length   = setup_len;
144   ssup.sh_type  = SHT_PROGBITS;
145   ssup.sh_flags = SHF_ALLOC|SHF_WRITE|SHF_EXECINSTR;
146   ssup.name     = "setup";
147   ssup.data     = kernel;
148   hdr->type_of_loader = 0xff;   /* "Other modern loader" */
149
150   /* Segment 3 is the kernel command line */
151   scmd.next     = &skrn;
152   scmd.address  = 0x20000;      /* XXX */
153   scmd.length   = strlen(cmdline)+1;
154   scmd.sh_type  = SHT_PROGBITS;
155   scmd.sh_flags = SHF_ALLOC;
156   scmd.name     = "cmdline";
157   scmd.data     = cmdline;
158   wrle32(scmd.address, &hdr->cmd_line_ptr); /* XXX */
159   wrle16(scmd.address - ssup.address - 0x200, &hdr->heap_end_ptr);
160   hdr->loadflags |= CAN_USE_HEAP;
161
162   /* Segment 4 is the Linux kernel proper */
163   skrn.next     = ninitrd ? &ird[0].seg : NULL;
164   skrn.address  = 0x100000;     /* XXX */
165   skrn.length   = kernel_len - setup_len;
166   skrn.sh_type  = SHT_PROGBITS;
167   skrn.sh_flags = SHF_ALLOC;
168   skrn.name     = "kernel";
169   skrn.data     = kernel + setup_len;
170
171   /* Segment 5 is the initrd */
172   if (skrn.address < 0x100000)
173     initrd_addr = 0x100000;
174   else
175     initrd_addr = (skrn.address + skrn.length + 3) & ~3;
176
177   for (i = 0; i < ninitrd; i++) {
178     char *name;
179
180     ird[i].seg.next     = (i < ninitrd-1) ? &ird[i+1].seg : 0;
181     ird[i].seg.address  = initrd_addr;
182     ird[i].seg.sh_type  = SHT_PROGBITS;
183     ird[i].seg.sh_flags = SHF_ALLOC;
184     asprintf(&name, "initrd.%d", i);
185     ird[i].seg.name = name;
186
187     initrd_addr += ird[i].seg.length;
188   }
189   wrle32(initrd_len, &hdr->ramdisk_size);
190
191   /* Set up the startup info */
192   wrle32(ninitrd ? ird[0].seg.address : 0, &info.rd_addr);
193   wrle32(initrd_len, &info.rd_len);
194   wrle32(rdle32(&hdr->initrd_addr_max), &info.rd_maxaddr); /* XXX */
195   wrle32(ssup.address, &info.setup_addr);
196   wrle32(scmd.address, &info.cmdline_addr);
197
198   rv = output_elf(&sinf, srel.address, out);
199
200  err:
201   if (ird) {
202     for (i = 0; i < ninitrd; i++) {
203       if (ird[i].seg.data)
204         munmap((void *)ird[i].seg.data, ird[i].seg.length);
205       if (ird[i].fd > 0)
206       close(ird[i].fd);
207     }
208     free(ird);
209   }
210
211   if (kernel)
212     munmap(kernel, kernel_len);
213   if (kernel_fd >= 0)
214     close(kernel_fd);
215   
216   return rv;
217 }