2009-05-05 Felix Zielcke <fzielcke@z-51.de>
[grub2/phcoder.git] / loader / multiboot2.c
blobfd8282849a91d8055fe85b90e1dada1535e8e0b4
1 /* multiboot2.c - boot a multiboot 2 OS image. */
2 /*
3 * GRUB -- GRand Unified Bootloader
4 * Copyright (C) 2007 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/>.
20 #include <multiboot2.h>
21 #include <grub/loader.h>
22 #include <grub/machine/loader.h>
23 #include <grub/multiboot2.h>
24 #include <grub/elfload.h>
25 #include <grub/file.h>
26 #include <grub/err.h>
27 #include <grub/dl.h>
28 #include <grub/mm.h>
29 #include <grub/misc.h>
30 #include <grub/gzio.h>
32 static grub_addr_t entry;
33 extern grub_dl_t my_mod;
35 static char *grub_mb2_tags;
36 static char *grub_mb2_tags_pos;
37 static grub_size_t grub_mb2_tags_len;
38 static int grub_mb2_tags_count;
40 static void
41 grub_mb2_tags_free (void)
43 grub_dprintf ("loader", "Freeing all tags...\n");
44 grub_free (grub_mb2_tags);
45 grub_mb2_tags = 0;
46 grub_mb2_tags_pos = 0;
47 grub_mb2_tags_len = 0;
48 grub_mb2_tags_count = 0;
51 grub_err_t
52 grub_mb2_tag_alloc (grub_addr_t *addr, int key, grub_size_t len)
54 struct multiboot_tag_header *tag;
55 grub_size_t used;
56 grub_size_t needed;
58 grub_dprintf ("loader", "Allocating tag: key 0x%x, size 0x%lx.\n",
59 key, (unsigned long) len);
61 used = grub_mb2_tags_pos - grub_mb2_tags;
62 len = ALIGN_UP (len, sizeof (multiboot_word));
64 needed = used + len;
66 if (needed > grub_mb2_tags_len)
68 /* Allocate new buffer. */
69 grub_size_t newsize = needed * 2;
70 char *newarea;
72 grub_dprintf ("loader", "Reallocating tag buffer (new size 0x%lx).\n",
73 (unsigned long) newsize);
75 newarea = grub_malloc (newsize);
76 if (! newarea)
77 return grub_errno;
78 grub_memcpy (newarea, grub_mb2_tags, grub_mb2_tags_len);
79 grub_free (grub_mb2_tags);
81 grub_mb2_tags_len = newsize;
82 grub_mb2_tags = newarea;
83 grub_mb2_tags_pos = newarea + used;
86 tag = (struct multiboot_tag_header *) grub_mb2_tags_pos;
87 grub_mb2_tags_pos += len;
89 tag->key = key;
90 tag->len = len;
92 if (addr)
93 *addr = (grub_addr_t) tag;
95 grub_mb2_tags_count++;
97 grub_dprintf ("loader", "Allocated tag %u at %p.\n", grub_mb2_tags_count, tag);
99 return 0;
102 static grub_err_t
103 grub_mb2_tag_start_create (void)
105 return grub_mb2_tag_alloc (0, MULTIBOOT2_TAG_START,
106 sizeof (struct multiboot_tag_start));
109 static grub_err_t
110 grub_mb2_tag_name_create (void)
112 struct multiboot_tag_name *name;
113 grub_addr_t name_addr;
114 grub_err_t err;
115 const char *grub_version = PACKAGE_STRING;
117 err = grub_mb2_tag_alloc (&name_addr, MULTIBOOT2_TAG_NAME,
118 sizeof (struct multiboot_tag_name) +
119 sizeof (grub_version) + 1);
120 if (err)
121 return err;
123 name = (struct multiboot_tag_name *) name_addr;
124 grub_strcpy (name->name, grub_version);
126 return GRUB_ERR_NONE;
129 typedef grub_err_t (*tag_create_t) (void);
130 static tag_create_t grub_mb2_tag_creators[] = {
131 grub_mb2_tag_start_create,
132 grub_mb2_tag_name_create,
133 grub_mb2_tags_arch_create,
137 static grub_err_t
138 grub_mb2_tags_create (void)
140 tag_create_t *creator;
141 grub_err_t err;
143 for (creator = grub_mb2_tag_creators; *creator != 0; creator++)
145 err = (*creator) ();
146 if (err)
147 goto error;
150 return GRUB_ERR_NONE;
152 error:
153 grub_error_push ();
154 grub_mb2_tags_free ();
155 grub_error_pop ();
156 return err;
159 static grub_err_t
160 grub_mb2_tags_finish (void)
162 struct multiboot_tag_start *start;
163 grub_err_t err;
165 /* Create the `end' tag. */
166 err = grub_mb2_tag_alloc (0, MULTIBOOT2_TAG_END,
167 sizeof (struct multiboot_tag_end));
168 if (err)
169 goto error;
171 /* We created the `start' tag first. Update it now. */
172 start = (struct multiboot_tag_start *) grub_mb2_tags;
173 start->size = grub_mb2_tags_pos - grub_mb2_tags;
174 return GRUB_ERR_NONE;
176 error:
177 grub_error_push ();
178 grub_mb2_tags_free ();
179 grub_error_pop ();
180 return err;
183 static grub_err_t
184 grub_mb2_boot (void)
186 grub_mb2_tags_finish ();
188 grub_dprintf ("loader", "Tags at %p\n", grub_mb2_tags);
189 grub_mb2_arch_boot (entry, grub_mb2_tags);
191 /* Not reached. */
192 return GRUB_ERR_NONE;
195 static grub_err_t
196 grub_mb2_unload (void)
198 struct multiboot_tag_header *tag;
199 struct multiboot_tag_header *tags =
200 (struct multiboot_tag_header *) grub_mb2_tags;
202 /* Free all module memory in the tag list. */
203 for_each_tag (tag, tags)
205 if (tag->key == MULTIBOOT2_TAG_MODULE)
207 struct multiboot_tag_module *module =
208 (struct multiboot_tag_module *) tag;
209 grub_free ((void *) module->addr);
213 /* Allow architecture to un-reserve memory. */
214 grub_mb2_arch_unload (tags);
216 /* Free the tags themselves. */
217 grub_mb2_tags_free ();
219 grub_dl_unref (my_mod);
221 return GRUB_ERR_NONE;
224 static grub_err_t
225 grub_mb2_load_other (UNUSED grub_file_t file, UNUSED void *buffer)
227 /* XXX Create module tag here. */
228 return grub_error (GRUB_ERR_UNKNOWN_OS, "currently only ELF is supported");
231 /* Create the tag containing the cmdline and the address of the module data. */
232 static grub_err_t
233 grub_mb2_tag_module_create (grub_addr_t modaddr, grub_size_t modsize,
234 char *type, int key, int argc, char *argv[])
236 struct multiboot_tag_module *module;
237 grub_ssize_t argslen = 0;
238 grub_err_t err;
239 char *p;
240 grub_addr_t module_addr;
241 int i;
243 /* Allocate enough space for the arguments and spaces between them. */
244 for (i = 0; i < argc; i++)
245 argslen += grub_strlen (argv[i]) + 1;
247 /* Note: includes implicit 1-byte cmdline. */
248 err = grub_mb2_tag_alloc (&module_addr, key,
249 sizeof (struct multiboot_tag_module) + argslen);
250 if (err)
251 return grub_errno;
253 module = (struct multiboot_tag_module *) module_addr;
254 module->addr = modaddr;
255 module->size = modsize;
256 grub_strcpy(module->type, type);
258 /* Fill in the command line. */
259 p = module->cmdline;
260 for (i = 0; i < argc; i++)
262 p = grub_stpcpy (p, argv[i]);
263 *p++ = ' ';
265 module->cmdline[argslen] = '\0';
267 return GRUB_ERR_NONE;
270 /* Load ELF32 or ELF64. */
271 static grub_err_t
272 grub_mb2_load_elf (grub_elf_t elf, int argc, char *argv[])
274 grub_addr_t kern_base;
275 grub_size_t kern_size;
276 grub_err_t err;
278 if (grub_elf_is_elf32 (elf))
280 entry = elf->ehdr.ehdr32.e_entry;
281 err = grub_elf32_load (elf, grub_mb2_arch_elf32_hook, &kern_base,
282 &kern_size);
284 else if (grub_elf_is_elf64 (elf))
286 entry = elf->ehdr.ehdr64.e_entry;
287 err = grub_elf64_load (elf, grub_mb2_arch_elf64_hook, &kern_base,
288 &kern_size);
290 else
291 err = grub_error (GRUB_ERR_UNKNOWN_OS, "unknown ELF class");
293 if (err)
294 goto fail;
296 grub_dprintf ("loader", "Entry point is 0x%lx.\n", (unsigned long) entry);
298 grub_mb2_tag_module_create (kern_base, kern_size, "kernel",
299 MULTIBOOT2_TAG_MODULE, argc, argv);
301 fail:
302 return err;
305 void
306 grub_multiboot2 (int argc, char *argv[])
308 char *buffer;
309 grub_file_t file = 0;
310 grub_elf_t elf = 0;
311 struct multiboot_header *header = 0;
312 char *p;
313 grub_ssize_t len;
314 grub_err_t err;
315 int header_found = 0;
317 grub_loader_unset ();
319 if (argc == 0)
321 grub_error (GRUB_ERR_BAD_ARGUMENT, "No kernel specified");
322 goto fail;
325 file = grub_gzfile_open (argv[0], 1);
326 if (! file)
328 grub_error (GRUB_ERR_BAD_ARGUMENT, "Couldn't open file");
329 goto fail;
332 buffer = grub_malloc (MULTIBOOT2_HEADER_SEARCH);
333 if (! buffer)
334 return;
336 len = grub_file_read (file, buffer, MULTIBOOT2_HEADER_SEARCH);
337 if (len < 32)
339 grub_error (GRUB_ERR_BAD_OS, "File too small");
340 goto fail;
343 /* Look for the multiboot header in the buffer. The header should
344 be at least 8 bytes and aligned on a 8-byte boundary. */
345 for (p = buffer; p <= buffer + len - 8; p += 8)
347 header = (struct multiboot_header *) p;
348 if (header->magic == MULTIBOOT2_HEADER_MAGIC)
350 header_found = 1;
351 break;
355 if (! header_found)
356 grub_dprintf ("loader", "No multiboot 2 header found.\n");
359 /* Create the basic tags. */
360 grub_dprintf ("loader", "Creating multiboot 2 tags\n");
361 grub_mb2_tags_create ();
363 /* Load the kernel and create its tag. */
364 elf = grub_elf_file (file);
365 if (elf)
367 grub_dprintf ("loader", "Loading ELF multiboot 2 file.\n");
368 err = grub_mb2_load_elf (elf, argc-1, &argv[1]);
369 grub_elf_close (elf);
371 else
373 grub_errno = 0;
374 grub_dprintf ("loader", "Loading non-ELF multiboot 2 file.\n");
376 if (header)
377 err = grub_mb2_load_other (file, header);
378 else
379 err = grub_error (GRUB_ERR_BAD_OS,
380 "Need multiboot 2 header to load non-ELF files.");
381 grub_file_close (file);
384 grub_free (buffer);
386 if (err)
387 goto fail;
389 /* Good to go. */
390 grub_loader_set (grub_mb2_boot, grub_mb2_unload, 1);
391 return;
393 fail:
394 grub_mb2_tags_free ();
395 grub_dl_unref (my_mod);
398 void
399 grub_module2 (int argc, char *argv[])
401 grub_file_t file;
402 grub_addr_t modaddr = 0;
403 grub_ssize_t modsize = 0;
404 grub_err_t err;
406 if (argc == 0)
408 grub_error (GRUB_ERR_BAD_ARGUMENT, "No module specified");
409 return;
412 if (argc == 1)
414 grub_error (GRUB_ERR_BAD_ARGUMENT, "No module type specified");
415 return;
418 if (entry == 0)
420 grub_error (GRUB_ERR_BAD_ARGUMENT,
421 "You need to load the multiboot kernel first");
422 return;
425 /* Load module data. */
426 file = grub_gzfile_open (argv[0], 1);
427 if (! file)
428 goto out;
430 modsize = grub_file_size (file);
431 err = grub_mb2_arch_module_alloc (modsize, &modaddr);
432 if (err)
433 goto out;
435 grub_dprintf ("loader", "Loading module at 0x%x - 0x%x\n", modaddr,
436 modaddr + modsize);
437 if (grub_file_read (file, (char *) modaddr, modsize) != modsize)
439 grub_error (GRUB_ERR_FILE_READ_ERROR, "Couldn't read file");
440 goto out;
443 /* Create the module tag. */
444 err = grub_mb2_tag_module_create (modaddr, modsize,
445 argv[1], MULTIBOOT2_TAG_MODULE,
446 argc-2, &argv[2]);
447 if (err)
448 goto out;
450 out:
451 grub_error_push ();
453 if (file)
454 grub_file_close (file);
456 if (modaddr)
457 grub_mb2_arch_module_free (modaddr, modsize);
459 grub_error_pop ();