Merge branch 'syslinux-3.8x'
[profile/ivi/syslinux.git] / com32 / mboot / map.c
1 /* ----------------------------------------------------------------------- *
2  *
3  *   Copyright 2007-2008 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  * map.c
31  *
32  * Functions that deal with the memory map of various objects
33  */
34
35 #include "mboot.h"
36
37 static struct syslinux_movelist *ml = NULL;
38 static struct syslinux_memmap *mmap = NULL, *amap = NULL;
39 static struct multiboot_header *mbh;
40 static addr_t mboot_high_water_mark = 0x100000;
41
42 /*
43  * Note: although there is no such thing in the spec, at least Xen makes
44  * assumptions as to where in the memory space Grub would have loaded
45  * certain things.  To support that, if "high" is set, then allocate this
46  * at an address strictly above any previous allocations.
47  *
48  * As a precaution, this also pads the data with zero up to the next
49  * alignment datum.
50  */
51 addr_t map_data(const void *data, size_t len, size_t align, int flags)
52 {
53     addr_t start = (flags & MAP_HIGH) ? mboot_high_water_mark : 0x2000;
54     addr_t pad = (flags & MAP_NOPAD) ? 0 : -len & (align - 1);
55     addr_t xlen = len + pad;
56
57     if (syslinux_memmap_find(amap, SMT_FREE, &start, &xlen, align) ||
58         syslinux_add_memmap(&amap, start, len + pad, SMT_ALLOC) ||
59         syslinux_add_movelist(&ml, start, (addr_t) data, len) ||
60         (pad && syslinux_add_memmap(&mmap, start + len, pad, SMT_ZERO))) {
61         printf("Cannot map %zu bytes\n", len + pad);
62         return 0;
63     }
64
65     dprintf("Mapping 0x%08x bytes (%#x pad) at 0x%08x\n", len, pad, start);
66
67     if (start + len + pad > mboot_high_water_mark)
68         mboot_high_water_mark = start + len + pad;
69
70     return start;
71 }
72
73 addr_t map_string(const char *string)
74 {
75     if (!string)
76         return 0;
77     else
78         return map_data(string, strlen(string) + 1, 1, 0);
79 }
80
81 int init_map(void)
82 {
83     /*
84      * Note: mmap is the memory map (containing free and zeroed regions)
85      * needed by syslinux_shuffle_boot_pm(); amap is a map where we keep
86      * track ourselves which target memory ranges have already been
87      * allocated.
88      */
89     mmap = syslinux_memory_map();
90     amap = syslinux_dup_memmap(mmap);
91     if (!mmap || !amap) {
92         error("Failed to allocate initial memory map!\n");
93         return -1;
94     }
95 #if DEBUG
96     dprintf("Initial memory map:\n");
97     syslinux_dump_memmap(stdout, mmap);
98 #endif
99
100     return 0;
101 }
102
103 int map_image(void *ptr, size_t len)
104 {
105     int mbh_len;
106     char *cptr = ptr;
107     Elf32_Ehdr *eh = ptr;
108     Elf32_Phdr *ph;
109     Elf32_Shdr *sh;
110     unsigned int i;
111     uint32_t bad_flags;
112
113     /*
114      * Search for the multiboot header...
115      */
116     mbh_len = 0;
117     for (i = 0; i < MULTIBOOT_SEARCH; i += 4) {
118         mbh = (struct multiboot_header *)((char *)ptr + i);
119         if (mbh->magic != MULTIBOOT_MAGIC)
120             continue;
121         if (mbh->magic + mbh->flags + mbh->checksum)
122             continue;
123         if (mbh->flags & MULTIBOOT_VIDEO_MODE)
124             mbh_len = 48;
125         else if (mbh->flags & MULTIBOOT_AOUT_KLUDGE)
126             mbh_len = 32;
127         else
128             mbh_len = 12;
129
130         if (i + mbh_len < len)
131             mbh_len = 0;        /* Invalid... */
132         else
133             break;              /* Found something... */
134     }
135
136     if (mbh_len) {
137         bad_flags = mbh->flags & (MULTIBOOT_UNSUPPORTED | MULTIBOOT_VIDEO_MODE);
138         if (bad_flags) {
139             printf("Unsupported Multiboot flags set: %#x\n", bad_flags);
140             return -1;
141         }
142     }
143
144     if (len < sizeof(Elf32_Ehdr) ||
145         memcmp(eh->e_ident, "\x7f" "ELF\1\1\1", 6) ||
146         (eh->e_machine != EM_386 && eh->e_machine != EM_486 &&
147          eh->e_machine != EM_X86_64) ||
148         eh->e_version != EV_CURRENT ||
149         eh->e_ehsize < sizeof(Elf32_Ehdr) || eh->e_ehsize >= len ||
150         eh->e_phentsize < sizeof(Elf32_Phdr) ||
151         !eh->e_phnum || eh->e_phoff + eh->e_phentsize * eh->e_phnum > len)
152         eh = NULL;              /* No valid ELF header found */
153
154     /*
155      * Note: the Multiboot Specification implies that AOUT_KLUDGE should
156      * have precedence over the ELF header.  However, Grub disagrees, and
157      * Grub is "the reference bootloader" for the Multiboot Specification.
158      * This is insane, since it makes the AOUT_KLUDGE bit functionally
159      * useless, but at least Solaris apparently depends on this behavior.
160      */
161     if (eh && !(opt.aout && mbh_len && (mbh->flags & MULTIBOOT_AOUT_KLUDGE))) {
162         regs.eip = eh->e_entry; /* Can be overridden further down... */
163
164         ph = (Elf32_Phdr *) (cptr + eh->e_phoff);
165
166         for (i = 0; i < eh->e_phnum; i++) {
167             if (ph->p_type == PT_LOAD || ph->p_type == PT_PHDR) {
168                 /* 
169                  * This loads at p_paddr, which matches Grub.  However, if
170                  * e_entry falls within the p_vaddr range of this PHDR, then
171                  * adjust it to match the p_paddr range... this is how Grub
172                  * behaves, so it's by definition correct (it doesn't have to
173                  * make sense...)
174                  */
175                 addr_t addr = ph->p_paddr;
176                 addr_t msize = ph->p_memsz;
177                 addr_t dsize = min(msize, ph->p_filesz);
178
179                 if (eh->e_entry >= ph->p_vaddr
180                     && eh->e_entry < ph->p_vaddr + msize)
181                     regs.eip = eh->e_entry + (ph->p_paddr - ph->p_vaddr);
182
183                 dprintf("Segment at 0x%08x data 0x%08x len 0x%08x\n",
184                         addr, dsize, msize);
185
186                 if (syslinux_memmap_type(amap, addr, msize) != SMT_FREE) {
187                     printf
188                         ("Memory segment at 0x%08x (len 0x%08x) is unavailable\n",
189                          addr, msize);
190                     return -1;  /* Memory region unavailable */
191                 }
192
193                 /* Mark this region as allocated in the available map */
194                 if (syslinux_add_memmap(&amap, addr, msize, SMT_ALLOC)) {
195                     error("Overlapping segments found in ELF header\n");
196                     return -1;
197                 }
198
199                 if (ph->p_filesz) {
200                     /* Data present region.  Create a move entry for it. */
201                     if (syslinux_add_movelist
202                         (&ml, addr, (addr_t) cptr + ph->p_offset, dsize)) {
203                         error("Failed to map PHDR data\n");
204                         return -1;
205                     }
206                 }
207                 if (msize > dsize) {
208                     /* Zero-filled region.  Mark as a zero region in the memory map. */
209                     if (syslinux_add_memmap
210                         (&mmap, addr + dsize, msize - dsize, SMT_ZERO)) {
211                         error("Failed to map PHDR zero region\n");
212                         return -1;
213                     }
214                 }
215                 if (addr + msize > mboot_high_water_mark)
216                     mboot_high_water_mark = addr + msize;
217             } else {
218                 /* Ignore this program header */
219             }
220
221             ph = (Elf32_Phdr *) ((char *)ph + eh->e_phentsize);
222         }
223
224         /* Load the ELF symbol table */
225         if (eh->e_shoff) {
226             addr_t addr, len;
227
228             sh = (Elf32_Shdr *) ((char *)eh + eh->e_shoff);
229
230             len = eh->e_shentsize * eh->e_shnum;
231             /*
232              * Align this, but don't pad -- in general this means a bunch of
233              * smaller sections gets packed into a single page.
234              */
235             addr = map_data(sh, len, 4096, MAP_HIGH | MAP_NOPAD);
236             if (!addr) {
237                 error("Failed to map symbol table\n");
238                 return -1;
239             }
240
241             mbinfo.flags |= MB_INFO_ELF_SHDR;
242             mbinfo.syms.e.addr = addr;
243             mbinfo.syms.e.num = eh->e_shnum;
244             mbinfo.syms.e.size = eh->e_shentsize;
245             mbinfo.syms.e.shndx = eh->e_shstrndx;
246
247             for (i = 0; i < eh->e_shnum; i++) {
248                 addr_t align;
249
250                 if (!sh[i].sh_size)
251                     continue;   /* Empty section */
252                 if (sh[i].sh_flags & SHF_ALLOC)
253                     continue;   /* SHF_ALLOC sections should have PHDRs */
254
255                 align = sh[i].sh_addralign ? sh[i].sh_addralign : 0;
256                 addr = map_data((char *)ptr + sh[i].sh_offset, sh[i].sh_size,
257                                 align, MAP_HIGH);
258                 if (!addr) {
259                     error("Failed to map symbol section\n");
260                     return -1;
261                 }
262                 sh[i].sh_addr = addr;
263             }
264         }
265     } else if (mbh_len && (mbh->flags & MULTIBOOT_AOUT_KLUDGE)) {
266         /*
267          * a.out kludge thing...
268          */
269         char *data_ptr;
270         addr_t data_len, bss_len;
271
272         regs.eip = mbh->entry_addr;
273
274         data_ptr = (char *)mbh - (mbh->header_addr - mbh->load_addr);
275         data_len = mbh->load_end_addr - mbh->load_addr;
276         bss_len = mbh->bss_end_addr - mbh->load_end_addr;
277
278         if (syslinux_memmap_type(amap, mbh->load_addr, data_len + bss_len)
279             != SMT_FREE) {
280             printf("Memory segment at 0x%08x (len 0x%08x) is unavailable\n",
281                    mbh->load_addr, data_len + bss_len);
282             return -1;          /* Memory region unavailable */
283         }
284         if (syslinux_add_memmap(&amap, mbh->load_addr,
285                                 data_len + bss_len, SMT_ALLOC)) {
286             error("Failed to claim a.out address space!\n");
287             return -1;
288         }
289         if (data_len)
290             if (syslinux_add_movelist(&ml, mbh->load_addr, (addr_t) data_ptr,
291                                       data_len)) {
292                 error("Failed to map a.out data\n");
293                 return -1;
294             }
295         if (bss_len)
296             if (syslinux_add_memmap
297                 (&mmap, mbh->load_end_addr, bss_len, SMT_ZERO)) {
298                 error("Failed to map a.out bss\n");
299                 return -1;
300             }
301         if (mbh->bss_end_addr > mboot_high_water_mark)
302             mboot_high_water_mark = mbh->bss_end_addr;
303     } else {
304         error
305             ("Invalid Multiboot image: neither ELF header nor a.out kludge found\n");
306         return -1;
307     }
308
309     return 0;
310 }
311
312 /*
313  * Set up a stack.  This isn't actually required by the spec, but it seems
314  * like a prudent thing to do.  Also, put enough zeros at the top of the
315  * stack that something that looks for an ELF invocation record will know
316  * there isn't one.
317  */
318 static void mboot_map_stack(void)
319 {
320     addr_t start, len;
321
322     if (syslinux_memmap_largest(amap, SMT_FREE, &start, &len) || len < 64)
323         return;                 /* Not much we can do, here... */
324
325     regs.esp = (start + len - 32) & ~15;
326     dprintf("Mapping stack at 0x%08x\n", regs.esp);
327     syslinux_add_memmap(&mmap, regs.esp, 32, SMT_ZERO);
328 }
329
330 void mboot_run(int bootflags)
331 {
332     mboot_map_stack();
333
334     dprintf("Running, eip = 0x%08x, ebx = 0x%08x\n", regs.eip, regs.ebx);
335
336     regs.eax = MULTIBOOT_VALID;
337     syslinux_shuffle_boot_pm(ml, mmap, bootflags, &regs);
338 }