make the linux-ppc packags be in synch with other platforms
[tangerine.git] / arch / common / boot / grub2 / loader / i386 / pc / multiboot.c
blob46402318b47907b2e76dbe5df0abca1bce62a79e
1 /* multiboot.c - boot a multiboot OS image. */
2 /*
3 * GRUB -- GRand Unified Bootloader
4 * Copyright (C) 2003,2004,2005,2007,2008 Free Software Foundation, Inc.
6 * GRUB 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, either version 3 of the License, or
9 * (at your option) any later version.
11 * GRUB is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
21 * FIXME: The following features from the Multiboot specification still
22 * need to be implemented:
23 * - VBE support
24 * - a.out support
25 * - boot device
26 * - symbol table
27 * - memory map
28 * - drives table
29 * - ROM configuration table
30 * - APM table
33 #include <grub/loader.h>
34 #include <grub/machine/loader.h>
35 #include <grub/multiboot.h>
36 #include <grub/machine/init.h>
37 #include <grub/machine/memory.h>
38 #include <grub/elf.h>
39 #include <grub/aout.h>
40 #include <grub/file.h>
41 #include <grub/err.h>
42 #include <grub/rescue.h>
43 #include <grub/dl.h>
44 #include <grub/mm.h>
45 #include <grub/misc.h>
46 #include <grub/gzio.h>
47 #include <grub/env.h>
49 extern grub_dl_t my_mod;
50 static struct grub_multiboot_info *mbi;
51 static grub_addr_t entry;
53 static grub_err_t
54 grub_multiboot_boot (void)
56 grub_multiboot_real_boot (entry, mbi);
58 /* Not reached. */
59 return GRUB_ERR_NONE;
62 static grub_err_t
63 grub_multiboot_unload (void)
65 if (mbi)
67 unsigned int i;
68 for (i = 0; i < mbi->mods_count; i++)
70 grub_free ((void *)
71 ((struct grub_mod_list *) mbi->mods_addr)[i].mod_start);
72 grub_free ((void *)
73 ((struct grub_mod_list *) mbi->mods_addr)[i].cmdline);
75 grub_free ((void *) mbi->mods_addr);
76 grub_free ((void *) mbi->cmdline);
77 grub_free (mbi);
81 mbi = 0;
82 grub_dl_unref (my_mod);
84 return GRUB_ERR_NONE;
87 /* Check if BUFFER contains ELF32. */
88 static int
89 grub_multiboot_is_elf32 (void *buffer)
91 Elf32_Ehdr *ehdr = (Elf32_Ehdr *) buffer;
93 return ehdr->e_ident[EI_CLASS] == ELFCLASS32;
96 static grub_err_t
97 grub_multiboot_load_elf32 (grub_file_t file, void *buffer)
99 Elf32_Ehdr *ehdr = (Elf32_Ehdr *) buffer;
100 Elf32_Phdr *phdr;
101 grub_addr_t physical_entry_addr = 0;
102 int i;
104 if (ehdr->e_ident[EI_CLASS] != ELFCLASS32)
105 return grub_error (GRUB_ERR_UNKNOWN_OS, "invalid ELF class");
107 if (grub_dl_check_header (ehdr, sizeof(Elf32_Ehdr)))
108 return grub_error (GRUB_ERR_UNKNOWN_OS, "no valid ELF header found");
110 if (ehdr->e_type != ET_EXEC)
111 return grub_error (GRUB_ERR_UNKNOWN_OS, "invalid ELF file type");
113 /* FIXME: Should we support program headers at strange locations? */
114 if (ehdr->e_phoff + ehdr->e_phnum * ehdr->e_phentsize > MULTIBOOT_SEARCH)
115 return grub_error (GRUB_ERR_BAD_OS, "program header at a too high offset");
117 entry = ehdr->e_entry;
119 /* Load every loadable segment in memory. */
120 for (i = 0; i < ehdr->e_phnum; i++)
122 phdr = (Elf32_Phdr *) ((char *) buffer + ehdr->e_phoff
123 + i * ehdr->e_phentsize);
124 if (phdr->p_type == PT_LOAD)
126 /* The segment should fit in the area reserved for the OS. */
127 if (phdr->p_paddr < grub_os_area_addr)
128 return grub_error (GRUB_ERR_BAD_OS,
129 "segment doesn't fit in memory reserved for the OS (0x%lx < 0x%lx)",
130 phdr->p_paddr, grub_os_area_addr);
131 if (phdr->p_paddr + phdr->p_memsz > grub_os_area_addr + grub_os_area_size)
132 return grub_error (GRUB_ERR_BAD_OS,
133 "segment doesn't fit in memory reserved for the OS (0x%lx > 0x%lx)",
134 phdr->p_paddr + phdr->p_memsz,
135 grub_os_area_addr + grub_os_area_size);
137 if (grub_file_seek (file, (grub_off_t) phdr->p_offset)
138 == (grub_off_t) -1)
139 return grub_error (GRUB_ERR_BAD_OS,
140 "invalid offset in program header");
142 if ((phdr->p_filesz > 0) && (grub_file_read (file, (void *) phdr->p_paddr, phdr->p_filesz)
143 != (grub_ssize_t) phdr->p_filesz))
144 return grub_error (GRUB_ERR_BAD_OS,
145 "couldn't read segment from file");
147 if (phdr->p_filesz < phdr->p_memsz)
148 grub_memset ((char *) phdr->p_paddr + phdr->p_filesz, 0,
149 phdr->p_memsz - phdr->p_filesz);
151 if ((entry >= phdr->p_vaddr) &&
152 (entry < phdr->p_vaddr + phdr->p_memsz))
153 physical_entry_addr = entry + phdr->p_paddr - phdr->p_vaddr;
157 if (physical_entry_addr)
158 entry = physical_entry_addr;
160 return grub_errno;
163 /* Check if BUFFER contains ELF64. */
164 static int
165 grub_multiboot_is_elf64 (void *buffer)
167 Elf64_Ehdr *ehdr = (Elf64_Ehdr *) buffer;
169 return ehdr->e_ident[EI_CLASS] == ELFCLASS64;
172 static grub_err_t
173 grub_multiboot_load_elf64 (grub_file_t file, void *buffer)
175 Elf64_Ehdr *ehdr = (Elf64_Ehdr *) buffer;
176 Elf64_Phdr *phdr;
177 grub_addr_t physical_entry_addr = 0;
178 int i;
180 if (ehdr->e_ident[EI_CLASS] != ELFCLASS64)
181 return grub_error (GRUB_ERR_UNKNOWN_OS, "invalid ELF class");
183 if (ehdr->e_ident[EI_MAG0] != ELFMAG0
184 || ehdr->e_ident[EI_MAG1] != ELFMAG1
185 || ehdr->e_ident[EI_MAG2] != ELFMAG2
186 || ehdr->e_ident[EI_MAG3] != ELFMAG3
187 || ehdr->e_version != EV_CURRENT
188 || ehdr->e_ident[EI_DATA] != ELFDATA2LSB
189 || ehdr->e_machine != EM_X86_64)
190 return grub_error(GRUB_ERR_UNKNOWN_OS, "no valid ELF header found");
192 if (ehdr->e_type != ET_EXEC)
193 return grub_error (GRUB_ERR_UNKNOWN_OS, "invalid ELF file type");
195 /* FIXME: Should we support program headers at strange locations? */
196 if (ehdr->e_phoff + ehdr->e_phnum * ehdr->e_phentsize > MULTIBOOT_SEARCH)
197 return grub_error (GRUB_ERR_BAD_OS, "program header at a too high offset");
199 /* We still in 32-bit mode */
200 if (ehdr->e_entry > 0xffffffff)
201 return grub_error (GRUB_ERR_BAD_OS, "invalid entry point for ELF64");
203 entry = ehdr->e_entry;
205 /* Load every loadable segment in memory. */
206 for (i = 0; i < ehdr->e_phnum; i++)
208 phdr = (Elf64_Phdr *) ((char *) buffer + ehdr->e_phoff
209 + i * ehdr->e_phentsize);
210 if (phdr->p_type == PT_LOAD)
212 /* The segment should fit in the area reserved for the OS. */
213 if (phdr->p_paddr < (grub_uint64_t) grub_os_area_addr)
214 return grub_error (GRUB_ERR_BAD_OS,
215 "segment doesn't fit in memory reserved for the OS (0x%lx < 0x%lx)",
216 phdr->p_paddr, (grub_uint64_t) grub_os_area_addr);
217 if (phdr->p_paddr + phdr->p_memsz
218 > (grub_uint64_t) grub_os_area_addr + (grub_uint64_t) grub_os_area_size)
219 return grub_error (GRUB_ERR_BAD_OS,
220 "segment doesn't fit in memory reserved for the OS (0x%lx > 0x%lx)",
221 phdr->p_paddr + phdr->p_memsz,
222 (grub_uint64_t) grub_os_area_addr + (grub_uint64_t) grub_os_area_size);
224 if (grub_file_seek (file, (grub_off_t) phdr->p_offset)
225 == (grub_off_t) -1)
226 return grub_error (GRUB_ERR_BAD_OS,
227 "invalid offset in program header");
229 if ((phdr->p_filesz > 0) && (grub_file_read (file, (void *) ((grub_uint32_t) phdr->p_paddr),
230 phdr->p_filesz))
231 != (grub_ssize_t) phdr->p_filesz)
232 return grub_error (GRUB_ERR_BAD_OS,
233 "couldn't read segment from file");
235 if (phdr->p_filesz < phdr->p_memsz)
236 grub_memset (((char *) ((grub_uint32_t) phdr->p_paddr)
237 + phdr->p_filesz),
239 phdr->p_memsz - phdr->p_filesz);
241 if ((entry >= phdr->p_vaddr) &&
242 (entry < phdr->p_vaddr + phdr->p_memsz))
243 physical_entry_addr = entry + phdr->p_paddr - phdr->p_vaddr;
247 if (physical_entry_addr)
248 entry = physical_entry_addr;
250 return grub_errno;
253 /* Load ELF32 or ELF64. */
254 static grub_err_t
255 grub_multiboot_load_elf (grub_file_t file, void *buffer)
257 if (grub_multiboot_is_elf32 (buffer))
258 return grub_multiboot_load_elf32 (file, buffer);
259 else if (grub_multiboot_is_elf64 (buffer))
260 return grub_multiboot_load_elf64 (file, buffer);
262 return grub_error (GRUB_ERR_UNKNOWN_OS, "unknown ELF class");
265 static int
266 grub_multiboot_get_bootdev (grub_uint32_t *bootdev)
268 char *p;
270 p = grub_env_get ("root");
271 if ((p) && ((p[0] == 'h') || (p[0] == 'f')) && (p[1] == 'd') &&
272 (p[2] >= '0') && (p[2] <= '9'))
274 grub_uint32_t bd;
276 bd = (p[0] == 'h') ? 0x80 : 0;
277 bd += grub_strtoul (p + 2, &p, 0);
278 bd <<= 24;
280 if ((p) && (p[0] == ','))
282 if ((p[1] >= '0') && (p[1] <= '9'))
285 bd += ((grub_strtoul (p + 1, &p, 0) - 1) & 0xFF) << 16;
287 if ((p) && (p[0] == ','))
288 p++;
290 else
291 bd += 0xFF0000;
293 if ((p[0] >= 'a') && (p[0] <= 'z'))
294 bd += (p[0] - 'a') << 8;
295 else
296 bd += 0xFF00;
298 else
299 bd += 0xFFFF00;
301 bd += 0xFF;
303 *bootdev = bd;
304 return 1;
307 return 0;
310 void
311 grub_multiboot (int argc, char *argv[])
313 grub_file_t file = 0;
314 char buffer[MULTIBOOT_SEARCH], *cmdline = 0, *p;
315 struct grub_multiboot_header *header;
316 grub_ssize_t len;
317 int i;
319 grub_loader_unset ();
321 if (argc == 0)
323 grub_error (GRUB_ERR_BAD_ARGUMENT, "No kernel specified");
324 goto fail;
327 file = grub_gzfile_open (argv[0], 1);
328 if (! file)
330 grub_error (GRUB_ERR_BAD_ARGUMENT, "Couldn't open file");
331 goto fail;
334 len = grub_file_read (file, buffer, MULTIBOOT_SEARCH);
335 if (len < 32)
337 grub_error (GRUB_ERR_BAD_OS, "File too small");
338 goto fail;
341 /* Look for the multiboot header in the buffer. The header should
342 be at least 12 bytes and aligned on a 4-byte boundary. */
343 for (header = (struct grub_multiboot_header *) buffer;
344 ((char *) header <= buffer + len - 12) || (header = 0);
345 header = (struct grub_multiboot_header *) ((char *) header + 4))
347 if (header->magic == MULTIBOOT_MAGIC
348 && !(header->magic + header->flags + header->checksum))
349 break;
352 if (header == 0)
354 grub_error (GRUB_ERR_BAD_ARGUMENT, "No multiboot header found");
355 goto fail;
358 if (header->flags & MULTIBOOT_UNSUPPORTED)
360 grub_error (GRUB_ERR_UNKNOWN_OS,
361 "Unsupported flag: 0x%x", header->flags);
362 goto fail;
365 if (header->flags & MULTIBOOT_AOUT_KLUDGE)
367 int ofs;
369 ofs = (char *) header - buffer -
370 (header->header_addr - header->load_addr);
371 if ((grub_aout_load (file, ofs, header->load_addr,
372 ((header->load_end_addr == 0) ? 0 :
373 header->load_end_addr - header->load_addr),
374 header->bss_end_addr))
375 !=GRUB_ERR_NONE)
376 goto fail;
378 entry = header->entry_addr;
380 else if (grub_multiboot_load_elf (file, buffer) != GRUB_ERR_NONE)
381 goto fail;
383 mbi = grub_malloc (sizeof (struct grub_multiboot_info));
384 if (! mbi)
385 goto fail;
387 grub_memset (mbi, 0, sizeof (struct grub_multiboot_info));
389 mbi->flags = MULTIBOOT_INFO_MEMORY;
391 /* Convert from bytes to kilobytes. */
392 mbi->mem_lower = grub_lower_mem / 1024;
393 mbi->mem_upper = grub_upper_mem / 1024;
395 for (i = 0, len = 0; i < argc; i++)
396 len += grub_strlen (argv[i]) + 1;
398 cmdline = p = grub_malloc (len);
399 if (! cmdline)
400 goto fail;
402 for (i = 0; i < argc; i++)
404 p = grub_stpcpy (p, argv[i]);
405 *(p++) = ' ';
408 /* Remove the space after the last word. */
409 *(--p) = '\0';
411 mbi->flags |= MULTIBOOT_INFO_CMDLINE;
412 mbi->cmdline = (grub_uint32_t) cmdline;
414 mbi->flags |= MULTIBOOT_INFO_BOOT_LOADER_NAME;
415 mbi->boot_loader_name = (grub_uint32_t) grub_strdup (PACKAGE_STRING);
417 if (grub_multiboot_get_bootdev (&mbi->boot_device))
418 mbi->flags |= MULTIBOOT_INFO_BOOTDEV;
420 grub_loader_set (grub_multiboot_boot, grub_multiboot_unload, 1);
422 fail:
423 if (file)
424 grub_file_close (file);
426 if (grub_errno != GRUB_ERR_NONE)
428 grub_free (cmdline);
429 grub_free (mbi);
430 grub_dl_unref (my_mod);
435 void
436 grub_module (int argc, char *argv[])
438 grub_file_t file = 0;
439 grub_ssize_t size, len = 0;
440 char *module = 0, *cmdline = 0, *p;
441 int i;
443 if (argc == 0)
445 grub_error (GRUB_ERR_BAD_ARGUMENT, "No module specified");
446 goto fail;
449 if (!mbi)
451 grub_error (GRUB_ERR_BAD_ARGUMENT,
452 "You need to load the multiboot kernel first");
453 goto fail;
456 file = grub_gzfile_open (argv[0], 1);
457 if (! file)
458 goto fail;
460 size = grub_file_size (file);
461 module = grub_memalign (MULTIBOOT_MOD_ALIGN, size);
462 if (! module)
463 goto fail;
465 if (grub_file_read (file, module, size) != size)
467 grub_error (GRUB_ERR_FILE_READ_ERROR, "Couldn't read file");
468 goto fail;
471 for (i = 0; i < argc; i++)
472 len += grub_strlen (argv[i]) + 1;
474 cmdline = p = grub_malloc (len);
475 if (! cmdline)
476 goto fail;
478 for (i = 0; i < argc; i++)
480 p = grub_stpcpy (p, argv[i]);
481 *(p++) = ' ';
484 /* Remove the space after the last word. */
485 *(--p) = '\0';
487 if (mbi->flags & MULTIBOOT_INFO_MODS)
489 struct grub_mod_list *modlist = (struct grub_mod_list *) mbi->mods_addr;
491 modlist = grub_realloc (modlist, (mbi->mods_count + 1)
492 * sizeof (struct grub_mod_list));
493 if (! modlist)
494 goto fail;
495 mbi->mods_addr = (grub_uint32_t) modlist;
496 modlist += mbi->mods_count;
497 modlist->mod_start = (grub_uint32_t) module;
498 modlist->mod_end = (grub_uint32_t) module + size;
499 modlist->cmdline = (grub_uint32_t) cmdline;
500 modlist->pad = 0;
501 mbi->mods_count++;
503 else
505 struct grub_mod_list *modlist = grub_malloc (sizeof (struct grub_mod_list));
506 if (! modlist)
507 goto fail;
508 modlist->mod_start = (grub_uint32_t) module;
509 modlist->mod_end = (grub_uint32_t) module + size;
510 modlist->cmdline = (grub_uint32_t) cmdline;
511 modlist->pad = 0;
512 mbi->mods_count = 1;
513 mbi->mods_addr = (grub_uint32_t) modlist;
514 mbi->flags |= MULTIBOOT_INFO_MODS;
517 fail:
518 if (file)
519 grub_file_close (file);
521 if (grub_errno != GRUB_ERR_NONE)
523 grub_free (module);
524 grub_free (cmdline);