Populate multiboot information structure before calling OS.
[people/xl0/gpxe.git] / src / arch / i386 / image / multiboot.c
1 /*
2  * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 /**
20  * @file
21  *
22  * Multiboot image format
23  *
24  */
25
26 #include <errno.h>
27 #include <multiboot.h>
28 #include <gpxe/uaccess.h>
29 #include <gpxe/image.h>
30 #include <gpxe/segment.h>
31 #include <gpxe/memmap.h>
32 #include <gpxe/elf.h>
33
34 /** Multiboot flags that we support */
35 #define MB_SUPPORTED_FLAGS ( MB_FLAG_PGALIGN | MB_FLAG_MEMMAP | \
36                              MB_FLAG_VIDMODE | MB_FLAG_RAW )
37
38 /** Compulsory feature multiboot flags */
39 #define MB_COMPULSORY_FLAGS 0x0000ffff
40
41 /** Optional feature multiboot flags */
42 #define MB_OPTIONAL_FLAGS 0xffff0000
43
44 /**
45  * Multiboot flags that we don't support
46  *
47  * We only care about the compulsory feature flags (bits 0-15); we are
48  * allowed to ignore the optional feature flags.
49  */
50 #define MB_UNSUPPORTED_FLAGS ( MB_COMPULSORY_FLAGS & ~MB_SUPPORTED_FLAGS )
51
52 /** A multiboot header descriptor */
53 struct multiboot_header_info {
54         /** The actual multiboot header */
55         struct multiboot_header mb;
56         /** Offset of header within the multiboot image */
57         size_t offset;
58 };
59
60 /**
61  * Execute multiboot image
62  *
63  * @v image             ELF file
64  * @ret rc              Return status code
65  */
66 static int multiboot_execute ( struct image *image ) {
67         static const char *bootloader_name = "gPXE " VERSION;
68         struct multiboot_info mbinfo;
69         struct memory_map memmap;
70         struct multiboot_memory_map mbmemmap[ sizeof ( memmap.regions ) /
71                                               sizeof ( memmap.regions[0] ) ];
72         unsigned int i;
73
74         /* Populate multiboot information structure */
75         memset ( &mbinfo, 0, sizeof ( mbinfo ) );
76
77         /* Set boot loader name */
78         mbinfo.flags |= MBI_FLAG_LOADER;
79         mbinfo.boot_loader_name = virt_to_phys ( bootloader_name );
80         
81         /* Get memory map */
82         get_memmap ( &memmap );
83         memset ( mbmemmap, 0, sizeof ( mbmemmap ) );
84         for ( i = 0 ; i < memmap.count ; i++ ) {
85                 mbmemmap[i].size = sizeof ( mbmemmap[i] );
86                 mbmemmap[i].base_addr = memmap.regions[i].start;
87                 mbmemmap[i].length = ( memmap.regions[i].end -
88                                        memmap.regions[i].start );
89                 mbmemmap[i].type = MBMEM_RAM;
90                 mbinfo.mmap_length += sizeof ( mbmemmap[i] );
91                 if ( memmap.regions[i].start == 0 )
92                         mbinfo.mem_lower = memmap.regions[i].end;
93                 if ( memmap.regions[i].start == 0x100000 )
94                         mbinfo.mem_upper = ( memmap.regions[i].end - 0x100000);
95         }
96         mbinfo.flags |= ( MBI_FLAG_MEM | MBI_FLAG_MMAP );
97         mbinfo.mmap_addr = virt_to_phys ( &mbmemmap[0].base_addr );
98
99         /* Set command line, if present */
100         if ( image->cmdline ) {
101                 mbinfo.flags |= MBI_FLAG_CMDLINE;
102                 mbinfo.cmdline = virt_to_phys ( image->cmdline );
103         }
104
105         /* Jump to OS with flat physical addressing */
106         __asm__ __volatile__ ( PHYS_CODE ( "xchgw %%bx,%%bx\n\t"
107                                            "call *%%edi\n\t" )
108                                : : "a" ( MULTIBOOT_BOOTLOADER_MAGIC ),
109                                    "b" ( virt_to_phys ( &mbinfo ) ),
110                                    "D" ( image->entry )
111                                : "ecx", "edx", "esi", "ebp" );
112
113         return -ECANCELED;
114 }
115
116 /**
117  * Find multiboot header
118  *
119  * @v image             Multiboot file
120  * @v hdr               Multiboot header descriptor to fill in
121  * @ret rc              Return status code
122  */
123 static int multiboot_find_header ( struct image *image,
124                                    struct multiboot_header_info *hdr ) {
125         uint32_t buf[64];
126         size_t offset;
127         unsigned int buf_idx;
128         uint32_t checksum;
129
130         /* Scan through first 8kB of image file 256 bytes at a time.
131          * (Use the buffering to avoid the overhead of a
132          * copy_from_user() for every dword.)
133          */
134         for ( offset = 0 ; offset < 8192 ; offset += sizeof ( buf[0] ) ) {
135                 /* Check for end of image */
136                 if ( offset > image->len )
137                         break;
138                 /* Refill buffer if applicable */
139                 buf_idx = ( ( offset % sizeof ( buf ) ) / sizeof ( buf[0] ) );
140                 if ( buf_idx == 0 ) {
141                         copy_from_user ( buf, image->data, offset,
142                                          sizeof ( buf ) );
143                 }
144                 /* Check signature */
145                 if ( buf[buf_idx] != MULTIBOOT_HEADER_MAGIC )
146                         continue;
147                 /* Copy header and verify checksum */
148                 copy_from_user ( &hdr->mb, image->data, offset,
149                                  sizeof ( hdr->mb ) );
150                 checksum = ( hdr->mb.magic + hdr->mb.flags +
151                              hdr->mb.checksum );
152                 if ( checksum != 0 )
153                         continue;
154                 /* Record offset of multiboot header and return */
155                 hdr->offset = offset;
156                 return 0;
157         }
158
159         /* No multiboot header found */
160         return -ENOEXEC;
161 }
162
163 /**
164  * Load raw multiboot image into memory
165  *
166  * @v image             Multiboot file
167  * @v hdr               Multiboot header descriptor
168  * @ret rc              Return status code
169  */
170 static int multiboot_load_raw ( struct image *image,
171                                 struct multiboot_header_info *hdr ) {
172         size_t offset;
173         size_t filesz;
174         size_t memsz;
175         userptr_t buffer;
176         int rc;
177
178         /* Verify and prepare segment */
179         offset = ( hdr->offset - hdr->mb.header_addr + hdr->mb.load_addr );
180         filesz = ( hdr->mb.load_end_addr - hdr->mb.load_addr );
181         memsz = ( hdr->mb.bss_end_addr - hdr->mb.load_addr );
182         buffer = phys_to_user ( hdr->mb.load_addr );
183         if ( ( rc = prep_segment ( buffer, filesz, memsz ) ) != 0 ) {
184                 DBG ( "Multiboot could not prepare segment: %s\n",
185                       strerror ( rc ) );
186                 return rc;
187         }
188
189         /* Copy image to segment */
190         copy_user ( buffer, 0, image->data, offset, filesz );
191
192         /* Record execution entry point */
193         image->entry = hdr->mb.entry_addr;
194         image->execute = multiboot_execute;
195
196         return 0;
197 }
198
199 /**
200  * Load ELF multiboot image into memory
201  *
202  * @v image             Multiboot file
203  * @ret rc              Return status code
204  */
205 static int multiboot_load_elf ( struct image *image ) {
206         int rc;
207
208         /* Load ELF image*/
209         if ( ( rc = elf_load ( image ) ) != 0 ) {
210                 DBG ( "Multiboot ELF image failed to load: %s\n",
211                       strerror ( rc ) );
212                 /* We must translate "not an ELF image" (i.e. ENOEXEC)
213                  * into "invalid multiboot image", to avoid screwing
214                  * up the image probing logic.
215                  */
216                 if ( rc == -ENOEXEC ) {
217                         return -ENOTSUP;
218                 } else {
219                         return rc;
220                 }
221         }
222
223         /* Capture execution method */
224         if ( image->execute )
225                 image->execute = multiboot_execute;
226
227         return 0;
228 }
229
230 /**
231  * Load multiboot image into memory
232  *
233  * @v image             Multiboot file
234  * @ret rc              Return status code
235  */
236 int multiboot_load ( struct image *image ) {
237         struct multiboot_header_info hdr;
238         int rc;
239
240         /* Locate multiboot header, if present */
241         if ( ( rc = multiboot_find_header ( image, &hdr ) ) != 0 ) {
242                 DBG ( "No multiboot header\n" );
243                 return rc;
244         }
245         DBG ( "Found multiboot header with flags %08lx\n", hdr.mb.flags );
246
247         /* Abort if we detect flags that we cannot support */
248         if ( hdr.mb.flags & MB_UNSUPPORTED_FLAGS ) {
249                 DBG ( "Multiboot flags %08lx not supported\n",
250                       ( hdr.mb.flags & MB_UNSUPPORTED_FLAGS ) );
251                 return -ENOTSUP;
252         }
253
254         /* Load the actual image */
255         if ( hdr.mb.flags & MB_FLAG_RAW ) {
256                 if ( ( rc = multiboot_load_raw ( image, &hdr ) ) != 0 )
257                         return rc;
258         } else {
259                 if ( ( rc = multiboot_load_elf ( image ) ) != 0 )
260                         return rc;
261         }
262
263         return 0;
264 }
265
266 /** Multiboot image type */
267 struct image_type multiboot_image_type __image_type = {
268         .name = "Multiboot",
269         .load = multiboot_load,
270 };