loader: updates from review
[unleashed.git] / usr / src / boot / sys / boot / i386 / libi386 / multiboot.c
blob37c132e0887251721b4f2223d8e0db3c6eb9ba04
1 /*-
2 * Copyright (c) 2014 Roger Pau Monné <royger@FreeBSD.org>
3 * All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
28 * This multiboot implementation only implements a subset of the full
29 * multiboot specification in order to be able to boot Xen and a
30 * FreeBSD Dom0. Trying to use it to boot other multiboot compliant
31 * kernels will most surely fail.
33 * The full multiboot specification can be found here:
34 * http://www.gnu.org/software/grub/manual/multiboot/multiboot.html
37 #include <sys/cdefs.h>
39 #include <sys/param.h>
40 #include <sys/exec.h>
41 #include <sys/linker.h>
42 #include <sys/module.h>
43 #include <sys/stdint.h>
44 #define _MACHINE_ELF_WANT_32BIT
45 #include <machine/elf.h>
46 #include <machine/metadata.h>
47 #include <machine/pc/bios.h>
48 #include <string.h>
49 #include <stand.h>
51 #include "bootstrap.h"
52 #include "multiboot.h"
53 #include "pxe.h"
54 #include "../zfs/libzfs.h"
55 #include "../i386/libi386/libi386.h"
56 #include "../i386/btx/lib/btxv86.h"
58 #define MULTIBOOT_SUPPORTED_FLAGS \
59 (MULTIBOOT_AOUT_KLUDGE|MULTIBOOT_PAGE_ALIGN|MULTIBOOT_MEMORY_INFO)
60 #define NUM_MODULES 2
61 #define METADATA_FIXED_SIZE (PAGE_SIZE*4)
62 #define METADATA_MODULE_SIZE PAGE_SIZE
64 #define METADATA_RESV_SIZE(mod_num) \
65 roundup(METADATA_FIXED_SIZE + METADATA_MODULE_SIZE * mod_num, PAGE_SIZE)
67 /* MB data heap pointer */
68 static vm_offset_t last_addr;
70 extern int elf32_loadfile_raw(char *filename, u_int64_t dest,
71 struct preloaded_file **result, int multiboot);
72 extern int elf64_load_modmetadata(struct preloaded_file *fp, u_int64_t dest);
73 extern int elf64_obj_loadfile(char *filename, u_int64_t dest,
74 struct preloaded_file **result);
76 static int multiboot_loadfile(char *, u_int64_t, struct preloaded_file **);
77 static int multiboot_exec(struct preloaded_file *);
79 static int multiboot_obj_loadfile(char *, u_int64_t, struct preloaded_file **);
80 static int multiboot_obj_exec(struct preloaded_file *fp);
82 struct file_format multiboot = { multiboot_loadfile, multiboot_exec };
83 struct file_format multiboot_obj =
84 { multiboot_obj_loadfile, multiboot_obj_exec };
86 extern void multiboot_tramp();
88 static const char mbl_name[] = "illumos Loader";
90 static int
91 num_modules(struct preloaded_file *kfp)
93 struct kernel_module *kmp;
94 int mod_num = 0;
96 for (kmp = kfp->f_modules; kmp != NULL; kmp = kmp->m_next)
97 mod_num++;
99 return (mod_num);
102 static vm_offset_t
103 max_addr(void)
105 struct preloaded_file *fp;
106 vm_offset_t addr = 0;
108 for (fp = file_findfile(NULL, NULL); fp != NULL; fp = fp->f_next) {
109 if (addr < (fp->f_addr + fp->f_size))
110 addr = fp->f_addr + fp->f_size;
113 return (addr);
116 static int
117 multiboot_loadfile(char *filename, u_int64_t dest,
118 struct preloaded_file **result)
120 uint32_t *magic;
121 int i, error;
122 caddr_t header_search;
123 ssize_t search_size;
124 int fd;
125 struct multiboot_header *header;
126 char *cmdline;
127 struct preloaded_file *fp;
129 if (filename == NULL)
130 return (EFTYPE);
132 /* is kernel already loaded? */
133 fp = file_findfile(NULL, NULL);
134 if (fp != NULL) {
135 return (EFTYPE);
138 if ((fd = open(filename, O_RDONLY)) == -1)
139 return (errno);
142 * Read MULTIBOOT_SEARCH size in order to search for the
143 * multiboot magic header.
145 header_search = malloc(MULTIBOOT_SEARCH);
146 if (header_search == NULL) {
147 close(fd);
148 return (ENOMEM);
151 search_size = read(fd, header_search, MULTIBOOT_SEARCH);
152 magic = (uint32_t *)header_search;
154 header = NULL;
155 for (i = 0; i < (search_size / sizeof(uint32_t)); i++) {
156 if (magic[i] == MULTIBOOT_HEADER_MAGIC) {
157 header = (struct multiboot_header *)&magic[i];
158 break;
162 if (header == NULL) {
163 error = EFTYPE;
164 goto out;
167 /* Valid multiboot header has been found, validate checksum */
168 if (header->magic + header->flags + header->checksum != 0) {
169 printf(
170 "Multiboot checksum failed, magic: 0x%x flags: 0x%x checksum: 0x%x\n",
171 header->magic, header->flags, header->checksum);
172 error = EFTYPE;
173 goto out;
176 if ((header->flags & ~MULTIBOOT_SUPPORTED_FLAGS) != 0) {
177 printf("Unsupported multiboot flags found: 0x%x\n",
178 header->flags);
179 error = EFTYPE;
180 goto out;
182 /* AOUT KLUDGE means we just load entire flat file as blob */
183 if (header->flags & MULTIBOOT_AOUT_KLUDGE) {
184 vm_offset_t laddr;
185 int got;
187 dest = header->load_addr;
188 if (lseek(fd, 0, SEEK_SET) == -1) {
189 printf("lseek failed\n");
190 error = EIO;
191 goto out;
193 laddr = dest;
194 for (;;) {
195 got = archsw.arch_readin(fd, laddr, 4096);
196 if (got == 0)
197 break;
198 if (got < 0) {
199 printf("error reading: %s", strerror(errno));
200 error = EIO;
201 goto out;
203 laddr += got;
206 fp = file_alloc();
207 if (fp == NULL) {
208 error = ENOMEM;
209 goto out;
211 fp->f_name = strdup(filename);
212 fp->f_type = strdup("aout multiboot kernel");
213 fp->f_addr = header->entry_addr;
214 fp->f_size = laddr - dest;
215 if (fp->f_size == 0) {
216 file_discard(fp);
217 error = EIO;
218 goto out;
220 fp->f_metadata = NULL;
222 *result = fp;
223 error = 0;
224 } else {
226 error = elf32_loadfile_raw(filename, dest, result, 1);
227 if (error != 0) {
228 printf("elf32_loadfile_raw failed: %d unable to "
229 "load multiboot kernel\n", error);
230 goto out;
234 setenv("kernelname", (*result)->f_name, 1);
235 bios_addsmapdata(*result);
236 out:
237 free(header_search);
238 close(fd);
239 return (error);
243 * returns allocated virtual address from MB info area
245 static vm_offset_t
246 mb_malloc(size_t n)
248 vm_offset_t ptr = last_addr;
249 if (ptr + n >= high_heap_base)
250 return (0);
251 last_addr = roundup(last_addr + n, MULTIBOOT_INFO_ALIGN);
252 return (ptr);
256 * Since for now we have no way to pass the environment to the kernel other than
257 * through arguments, we need to take care of console setup.
259 * If the console is in mirror mode, set the kernel console from $os_console.
260 * If it's unset, use first item from $console.
261 * If $console is "ttyX", also pass $ttyX-mode, since it may have been set by
262 * the user.
264 * In case of memory allocation errors, just return original command line,
265 * so we have chance of booting.
267 * On success, cl will be freed and a new, allocated command line string is
268 * returned.
270 static char *
271 update_cmdline(char *cl)
273 char *os_console = getenv("os_console");
274 char *ttymode = NULL;
275 char mode[10];
276 char *tmp;
277 int len;
279 if (os_console == NULL) {
280 tmp = strdup(getenv("console"));
281 os_console = strsep(&tmp, ", ");
282 } else
283 os_console = strdup(os_console);
285 if (os_console == NULL)
286 return (cl);
288 if (strncmp(os_console, "tty", 3) == 0) {
289 snprintf(mode, sizeof (mode), "%s-mode", os_console);
290 ttymode = getenv(mode); /* never NULL */
293 if (strstr(cl, "-B") != NULL) {
294 len = strlen(cl) + 1;
296 * if console is not present, add it
297 * if console is ttyX, add ttymode
299 tmp = strstr(cl, "console");
300 if (tmp == NULL) {
301 len += 12; /* " -B console=" */
302 len += strlen(os_console);
303 if (ttymode != NULL) {
304 len += 13; /* ",ttyX-mode=\"\"" */
305 len += strlen(ttymode);
307 tmp = malloc(len);
308 if (tmp == NULL) {
309 free(os_console);
310 return (cl);
312 if (ttymode != NULL)
313 sprintf(tmp,
314 "%s -B console=%s,%s-mode=\"%s\"",
315 cl, os_console, os_console, ttymode);
316 else
317 sprintf(tmp, "%s -B console=%s",
318 cl, os_console);
319 } else {
320 /* console is set, do we need tty mode? */
321 tmp += 8;
322 if (strstr(tmp, "tty") == tmp) {
323 strncpy(mode, tmp, 4);
324 mode[4] = '\0';
325 strcat(mode, "-mode");
326 ttymode = getenv(mode); /* never NULL */
327 } else { /* nope */
328 free(os_console);
329 return (cl);
331 len = strlen(cl) + 1;
332 len += 13; /* ",ttyX-mode=\"\"" */
333 len += strlen(ttymode);
334 tmp = malloc(len);
335 if (tmp == NULL) {
336 free(os_console);
337 return (cl);
339 sprintf(tmp, "%s,%s=\"%s\"", cl, mode, ttymode);
341 } else {
343 * no -B, so we need to add " -B console=%s[,ttyX-mode=\"%s\"]"
345 len = strlen(cl) + 1;
346 len += 12; /* " -B console=" */
347 len += strlen(os_console);
348 if (ttymode != NULL) {
349 len += 13; /* ",ttyX-mode=\"\"" */
350 len += strlen(ttymode);
352 tmp = malloc(len);
353 if (tmp == NULL) {
354 free(os_console);
355 return (cl);
357 if (ttymode != NULL)
358 sprintf(tmp, "%s -B console=%s,%s-mode=\"%s\"", cl,
359 os_console, os_console, ttymode);
360 else
361 sprintf(tmp, "%s -B console=%s", cl, os_console);
363 free(os_console);
364 free(cl);
365 return (tmp);
368 static char *
369 kernel_cmdline(struct preloaded_file *fp, struct i386_devdesc *rootdev)
371 char *cmdline = NULL;
372 size_t len;
374 if (fp->f_args == NULL)
375 fp->f_args = getenv("boot-args");
377 len = strlen(fp->f_name) + 1;
379 if (fp->f_args != NULL)
380 len += strlen(fp->f_args) + 1;
382 if (rootdev->d_type == DEVT_ZFS)
383 len += 3 + strlen(zfs_bootfs(rootdev)) + 1;
385 cmdline = malloc(len);
386 if (cmdline == NULL)
387 return (cmdline);
389 if (rootdev->d_type == DEVT_ZFS) {
390 if (fp->f_args != NULL)
391 snprintf(cmdline, len, "%s %s -B %s", fp->f_name,
392 fp->f_args, zfs_bootfs(rootdev));
393 else
394 snprintf(cmdline, len, "%s -B %s", fp->f_name,
395 zfs_bootfs(rootdev));
396 } else if (fp->f_args != NULL)
397 snprintf(cmdline, len, "%s %s", fp->f_name, fp->f_args);
398 else
399 snprintf(cmdline, len, "%s", fp->f_name);
401 return (update_cmdline(cmdline));
404 static int
405 multiboot_exec(struct preloaded_file *fp)
407 struct preloaded_file *mfp;
408 vm_offset_t module_start, metadata_size;
409 vm_offset_t modulep, kernend, entry;
410 struct file_metadata *md;
411 Elf_Ehdr *ehdr;
412 struct multiboot_info *mb_info = NULL;
413 struct multiboot_mod_list *mb_mod = NULL;
414 multiboot_memory_map_t *mmap;
415 struct bios_smap *smap;
416 struct i386_devdesc *rootdev;
417 extern BOOTPLAYER bootplayer; /* dhcp info */
418 char *cmdline = NULL;
419 size_t len;
420 int error, num, i;
421 int rootfs = 0; /* flag for rootfs */
422 int xen = 0; /* flag for xen */
423 int kernel = 0; /* flag for kernel */
425 /* set up base for mb_malloc */
426 for (mfp = fp; mfp->f_next != NULL; mfp = mfp->f_next);
428 /* start info block from new page */
429 last_addr = roundup(mfp->f_addr + mfp->f_size, MULTIBOOT_MOD_ALIGN);
431 /* Allocate the multiboot struct and fill the basic details. */
432 mb_info = (struct multiboot_info *)PTOV(mb_malloc(sizeof (*mb_info)));
434 bzero(mb_info, sizeof(struct multiboot_info));
435 mb_info->flags = MULTIBOOT_INFO_MEMORY|MULTIBOOT_INFO_BOOT_LOADER_NAME;
436 mb_info->mem_lower = bios_basemem / 1024;
437 mb_info->mem_upper = bios_extmem / 1024;
438 mb_info->boot_loader_name = mb_malloc(strlen(mbl_name) + 1);
440 i386_copyin(mbl_name, mb_info->boot_loader_name, strlen(mbl_name)+1);
442 i386_getdev((void **)(&rootdev), NULL, NULL);
443 if (rootdev == NULL) {
444 printf("can't determine root device\n");
445 error = EINVAL;
446 goto error;
450 * boot image command line. if args were not provided, we need to set
451 * args here, and that depends on image type...
452 * fortunately we only have following options:
453 * 64 or 32 bit unix or xen. so we just check if f_name has unix.
455 /* do we boot xen? */
456 if (strstr(fp->f_name, "unix") == NULL)
457 xen = 1;
459 entry = fp->f_addr;
461 num = 0;
462 for (mfp = fp->f_next; mfp != NULL; mfp = mfp->f_next) {
463 num++;
464 if (mfp->f_type != NULL && strcmp(mfp->f_type, "rootfs") == 0)
465 rootfs++;
466 if (mfp->f_type != NULL && strcmp(mfp->f_type, "kernel") == 0)
467 kernel++;
470 if (num == 0 || rootfs == 0) {
471 /* need at least one module - rootfs */
472 printf("No rootfs module provided, aborting\n");
473 error = EINVAL;
474 goto error;
476 if (xen == 1 && kernel == 0) {
477 printf("No kernel module provided for xen, aborting\n");
478 error = EINVAL;
479 goto error;
481 mb_mod = (struct multiboot_mod_list *) PTOV(last_addr);
482 last_addr += roundup(sizeof(*mb_mod) * num, MULTIBOOT_INFO_ALIGN);
484 bzero(mb_mod, sizeof(*mb_mod) * num);
486 num = 0;
487 for (mfp = fp->f_next; mfp != NULL; mfp = mfp->f_next) {
488 mb_mod[num].mod_start = mfp->f_addr;
489 mb_mod[num].mod_end = mfp->f_addr + mfp->f_size;
491 if (strcmp(mfp->f_type, "kernel") == 0) {
492 cmdline = kernel_cmdline(mfp, rootdev);
493 if (cmdline == NULL) {
494 error = ENOMEM;
495 goto error;
497 } else {
498 len = strlen(mfp->f_name) + 1;
499 len += strlen(mfp->f_type) + 5 + 1;
500 if (mfp->f_args != NULL) {
501 len += strlen(mfp->f_args) + 1;
503 cmdline = malloc(len);
504 if (cmdline == NULL) {
505 error = ENOMEM;
506 goto error;
509 if (mfp->f_args != NULL)
510 snprintf(cmdline, len, "%s type=%s %s",
511 mfp->f_name, mfp->f_type, mfp->f_args);
512 else
513 snprintf(cmdline, len, "%s type=%s",
514 mfp->f_name, mfp->f_type);
517 mb_mod[num].cmdline = mb_malloc(strlen(cmdline)+1);
518 i386_copyin(cmdline, mb_mod[num].cmdline, strlen(cmdline)+1);
519 free(cmdline);
520 num++;
523 mb_info->mods_count = num;
524 mb_info->mods_addr = VTOP(mb_mod);
525 mb_info->flags |= MULTIBOOT_INFO_MODS;
527 md = file_findmetadata(fp, MODINFOMD_SMAP);
528 if (md == NULL) {
529 printf("no memory smap\n");
530 error = EINVAL;
531 goto error;
534 num = md->md_size / sizeof(struct bios_smap); /* number of entries */
535 mmap = (multiboot_memory_map_t *)PTOV(mb_malloc(sizeof(*mmap) * num));
537 mb_info->mmap_length = num * sizeof(*mmap);
538 smap = (struct bios_smap *)md->md_data;
540 for (i = 0; i < num; i++) {
541 mmap[i].size = sizeof(*smap);
542 mmap[i].addr = smap[i].base;
543 mmap[i].len = smap[i].length;
544 mmap[i].type = smap[i].type;
546 mb_info->mmap_addr = VTOP(mmap);
547 mb_info->flags |= MULTIBOOT_INFO_MEM_MAP;
549 if (strstr(getenv("loaddev"), "pxe") != NULL) {
550 mb_info->drives_length = sizeof (BOOTPLAYER);
551 mb_info->drives_addr = mb_malloc(mb_info->drives_length);
552 i386_copyin(&bootplayer, mb_info->drives_addr,
553 mb_info->drives_length);
554 mb_info->flags &= ~MULTIBOOT_INFO_DRIVE_INFO;
557 * Set the image command line. Need to do this as last thing,
558 * as Illumos kernel dboot_startkern will check cmdline
559 * address as last check to find first free address.
561 if (fp->f_args == NULL) {
562 if (xen)
563 cmdline = getenv("xen_cmdline");
564 else
565 cmdline = getenv("boot-args");
566 if (cmdline != NULL) {
567 fp->f_args = strdup(cmdline);
568 if (fp->f_args == NULL) {
569 error = ENOMEM;
570 goto error;
576 * if image is xen, we just use f_name + f_args for commandline
577 * for unix, we need to add zfs-bootfs.
579 if (xen) {
580 len = strlen(fp->f_name) + 1;
581 if (fp->f_args != NULL)
582 len += strlen(fp->f_args) + 1;
584 if (fp->f_args != NULL) {
585 if((cmdline = malloc(len)) == NULL) {
586 error = ENOMEM;
587 goto error;
589 snprintf(cmdline, len, "%s %s", fp->f_name, fp->f_args);
590 } else {
591 cmdline = strdup(fp->f_name);
593 } else {
594 cmdline = kernel_cmdline(fp, rootdev);
597 if (cmdline == NULL) {
598 error = ENOMEM;
599 goto error;
602 mb_info->cmdline = mb_malloc(strlen(cmdline)+1);
603 i386_copyin(cmdline, mb_info->cmdline, strlen(cmdline)+1);
604 mb_info->flags |= MULTIBOOT_INFO_CMDLINE;
605 free(cmdline);
606 cmdline = NULL;
608 dev_cleanup();
609 __exec((void *)VTOP(multiboot_tramp), (void *)entry,
610 (void *)VTOP(mb_info));
612 panic("exec returned");
614 error:
615 return (error);
618 static int
619 multiboot_obj_loadfile(char *filename, u_int64_t dest,
620 struct preloaded_file **result)
622 struct preloaded_file *mfp, *kfp, *rfp;
623 struct kernel_module *kmp;
624 int error, mod_num;
626 /* See if there's a aout multiboot kernel loaded */
627 mfp = file_findfile(NULL, "aout multiboot kernel");
628 if (mfp != NULL) {
629 /* we have normal kernel loaded, add module */
630 rfp = file_loadraw(filename, "module", 0, NULL, 0);
631 if (rfp == NULL) {
632 printf(
633 "Unable to load %s as a multiboot payload module\n",
634 filename);
635 return (EINVAL);
637 rfp->f_size = roundup(rfp->f_size, PAGE_SIZE);
638 *result = rfp;
639 return (0);
642 /* See if there's a multiboot kernel loaded */
643 mfp = file_findfile(NULL, "elf multiboot kernel");
644 if (mfp == NULL) {
645 return (EFTYPE); /* this allows to check other methods */
649 * We have a multiboot kernel loaded, see if there's a
650 * kernel loaded also.
652 kfp = file_findfile(NULL, "elf kernel");
653 if (kfp == NULL) {
655 * No kernel loaded, this must be it. The kernel has to
656 * be loaded as a raw file, it will be processed by
657 * Xen and correctly loaded as an ELF file.
659 rfp = file_loadraw(filename, "elf kernel", 0, NULL, 0);
660 if (rfp == NULL) {
661 printf(
662 "Unable to load %s as a multiboot payload kernel\n",
663 filename);
664 return (EINVAL);
667 /* Load kernel metadata... */
668 setenv("kernelname", filename, 1);
669 error = elf64_load_modmetadata(rfp, rfp->f_addr + rfp->f_size);
670 if (error) {
671 printf("Unable to load kernel %s metadata error: %d\n",
672 rfp->f_name, error);
673 return (EINVAL);
677 * Save space at the end of the kernel in order to place
678 * the metadata information. We do an approximation of the
679 * max metadata size, this is not optimal but it's probably
680 * the best we can do at this point. Once all modules are
681 * loaded and the size of the metadata is known this
682 * space will be recovered if not used.
684 mod_num = num_modules(rfp);
685 rfp->f_size = roundup(rfp->f_size, PAGE_SIZE);
686 rfp->f_size += METADATA_RESV_SIZE(mod_num);
687 *result = rfp;
688 } else {
689 /* The rest should be loaded as regular modules */
690 error = elf64_obj_loadfile(filename, dest, result);
691 if (error != 0) {
692 printf("Unable to load %s as an object file, error: %d",
693 filename, error);
694 return (error);
698 return (0);
701 static int
702 multiboot_obj_exec(struct preloaded_file *fp)
705 return (EFTYPE);