diff options
author | James <james.mckenzie@citrix.com> | 2012-11-16 10:41:01 +0000 |
---|---|---|
committer | James <james.mckenzie@citrix.com> | 2012-11-16 10:41:01 +0000 |
commit | 041d1ea37802bf7178a31a53f96c26efa6b8fb7b (patch) | |
tree | c193e84ad1237f25a79d0f6a267722e44c73f56a /grub-core/loader | |
download | grub-1.99-041d1ea37802bf7178a31a53f96c26efa6b8fb7b.tar.gz grub-1.99-041d1ea37802bf7178a31a53f96c26efa6b8fb7b.tar.bz2 grub-1.99-041d1ea37802bf7178a31a53f96c26efa6b8fb7b.zip |
fish
Diffstat (limited to 'grub-core/loader')
26 files changed, 11986 insertions, 0 deletions
diff --git a/grub-core/loader/aout.c b/grub-core/loader/aout.c new file mode 100644 index 0000000..69bf6e6 --- /dev/null +++ b/grub-core/loader/aout.c @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2008 Free Software Foundation, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <grub/file.h> +#include <grub/err.h> +#include <grub/dl.h> +#include <grub/misc.h> +#include <grub/aout.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +int +grub_aout_get_type (union grub_aout_header *header) +{ + int magic; + + magic = AOUT_GETMAGIC (header->aout32); + if ((magic == AOUT32_OMAGIC) || (magic == AOUT32_NMAGIC) || + (magic == AOUT32_ZMAGIC) || (magic == AOUT32_QMAGIC)) + return AOUT_TYPE_AOUT32; + else if ((magic == AOUT64_OMAGIC) || (magic == AOUT64_NMAGIC) || + (magic == AOUT64_ZMAGIC)) + return AOUT_TYPE_AOUT64; + else + return AOUT_TYPE_NONE; +} + +grub_err_t +grub_aout_load (grub_file_t file, int offset, + void *load_addr, + int load_size, grub_size_t bss_size) +{ + if ((grub_file_seek (file, offset)) == (grub_off_t) - 1) + return grub_errno; + + if (!load_size) + load_size = file->size - offset; + + grub_file_read (file, load_addr, load_size); + + if (grub_errno) + return grub_errno; + + if (bss_size) + grub_memset ((char *) load_addr + load_size, 0, bss_size); + + return GRUB_ERR_NONE; +} diff --git a/grub-core/loader/efi/appleloader.c b/grub-core/loader/efi/appleloader.c new file mode 100644 index 0000000..db57a57 --- /dev/null +++ b/grub-core/loader/efi/appleloader.c @@ -0,0 +1,230 @@ +/* appleloader.c - apple legacy boot loader. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2008,2009 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <grub/loader.h> +#include <grub/err.h> +#include <grub/mm.h> +#include <grub/dl.h> +#include <grub/misc.h> +#include <grub/efi/api.h> +#include <grub/efi/efi.h> +#include <grub/command.h> +#include <grub/i18n.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +static grub_dl_t my_mod; + +static grub_efi_handle_t image_handle; +static grub_efi_char16_t *cmdline; + +static grub_err_t +grub_appleloader_unload (void) +{ + grub_efi_boot_services_t *b; + + b = grub_efi_system_table->boot_services; + efi_call_1 (b->unload_image, image_handle); + + grub_free (cmdline); + cmdline = 0; + + grub_dl_unref (my_mod); + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_appleloader_boot (void) +{ + grub_efi_boot_services_t *b; + + b = grub_efi_system_table->boot_services; + efi_call_3 (b->start_image, image_handle, 0, 0); + + grub_appleloader_unload (); + + return grub_errno; +} + +struct piwg_full_device_path +{ + struct grub_efi_memory_mapped_device_path comp1; + struct grub_efi_piwg_device_path comp2; + struct grub_efi_device_path end; +}; + +#define MAKE_PIWG_PATH(st, en) \ + { \ + .comp1 = \ + { \ + .header = { \ + .type = GRUB_EFI_HARDWARE_DEVICE_PATH_TYPE, \ + .subtype = GRUB_EFI_MEMORY_MAPPED_DEVICE_PATH_SUBTYPE, \ + .length = {sizeof (struct grub_efi_memory_mapped_device_path), 0} \ + }, \ + .memory_type = GRUB_EFI_MEMORY_MAPPED_IO, \ + .start_address = st, \ + .end_address = en \ + }, \ + .comp2 = \ + { \ + .header = { \ + .type = GRUB_EFI_MEDIA_DEVICE_PATH_TYPE, \ + .subtype = GRUB_EFI_PIWG_DEVICE_PATH_SUBTYPE, \ + .length = {sizeof (struct grub_efi_piwg_device_path), 0} \ + }, \ + .guid = {0x2B0585EB, 0xD8B8, 0x49A9, {0x8B, 0x8C, 0xE2, 0x1B, \ + 0x01, 0xAE, 0xF2, 0xB7}} \ + }, \ + .end = \ + { \ + .type = GRUB_EFI_END_DEVICE_PATH_TYPE, \ + .subtype = GRUB_EFI_END_ENTIRE_DEVICE_PATH_SUBTYPE, \ + .length = {sizeof (struct grub_efi_device_path), 0} \ + } \ + } + +/* early 2006 Core Duo / Core Solo models */ +static struct piwg_full_device_path devpath_1 = MAKE_PIWG_PATH (0xffe00000, + 0xfff9ffff); + +/* mid-2006 Mac Pro (and probably other Core 2 models) */ +static struct piwg_full_device_path devpath_2 = MAKE_PIWG_PATH (0xffe00000, + 0xfff7ffff); + +/* mid-2007 MBP ("Santa Rosa" based models) */ +static struct piwg_full_device_path devpath_3 = MAKE_PIWG_PATH (0xffe00000, + 0xfff8ffff); + +/* early-2008 MBA */ +static struct piwg_full_device_path devpath_4 = MAKE_PIWG_PATH (0xffc00000, + 0xfff8ffff); + +/* late-2008 MB/MBP (NVidia chipset) */ +static struct piwg_full_device_path devpath_5 = MAKE_PIWG_PATH (0xffcb4000, + 0xffffbfff); + +/* mid-2010 MB/MBP (NVidia chipset) */ +static struct piwg_full_device_path devpath_6 = MAKE_PIWG_PATH (0xffcc4000, + 0xffffbfff); + +struct devdata +{ + char *model; + grub_efi_device_path_t *devpath; +}; + +struct devdata devs[] = +{ + {"Core Duo/Solo", (grub_efi_device_path_t *) &devpath_1}, + {"Mac Pro", (grub_efi_device_path_t *) &devpath_2}, + {"MBP", (grub_efi_device_path_t *) &devpath_3}, + {"MBA", (grub_efi_device_path_t *) &devpath_4}, + {"MB NV", (grub_efi_device_path_t *) &devpath_5}, + {"MB NV2", (grub_efi_device_path_t *) &devpath_6}, + {NULL, NULL}, +}; + +static grub_err_t +grub_cmd_appleloader (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_efi_boot_services_t *b; + grub_efi_loaded_image_t *loaded_image; + struct devdata *pdev; + + grub_dl_ref (my_mod); + + /* Initialize some global variables. */ + image_handle = 0; + + b = grub_efi_system_table->boot_services; + + for (pdev = devs ; pdev->devpath ; pdev++) + if (efi_call_6 (b->load_image, 0, grub_efi_image_handle, pdev->devpath, + NULL, 0, &image_handle) == GRUB_EFI_SUCCESS) + break; + + if (! pdev->devpath) + { + grub_error (GRUB_ERR_BAD_OS, "can't find model"); + goto fail; + } + + grub_printf ("Model : %s\n", pdev->model); + + loaded_image = grub_efi_get_loaded_image (image_handle); + if (! loaded_image) + { + grub_error (GRUB_ERR_BAD_OS, "no loaded image available"); + goto fail; + } + + if (argc > 0) + { + int i, len; + grub_efi_char16_t *p16; + + for (i = 0, len = 0; i < argc; i++) + len += grub_strlen (argv[i]) + 1; + + len *= sizeof (grub_efi_char16_t); + cmdline = p16 = grub_malloc (len); + if (! cmdline) + goto fail; + + for (i = 0; i < argc; i++) + { + char *p8; + + p8 = argv[i]; + while (*p8) + *(p16++) = *(p8++); + + *(p16++) = ' '; + } + *(--p16) = 0; + + loaded_image->load_options = cmdline; + loaded_image->load_options_size = len; + } + + grub_loader_set (grub_appleloader_boot, grub_appleloader_unload, 0); + + return 0; + + fail: + + grub_dl_unref (my_mod); + return grub_errno; +} + +static grub_command_t cmd; + +GRUB_MOD_INIT(appleloader) +{ + cmd = grub_register_command ("appleloader", grub_cmd_appleloader, + "[OPTS]", N_("Boot legacy system.")); + my_mod = mod; +} + +GRUB_MOD_FINI(appleloader) +{ + grub_unregister_command (cmd); +} diff --git a/grub-core/loader/efi/chainloader.c b/grub-core/loader/efi/chainloader.c new file mode 100644 index 0000000..5dd5ada --- /dev/null +++ b/grub-core/loader/efi/chainloader.c @@ -0,0 +1,355 @@ +/* chainloader.c - boot another boot loader */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2002,2004,2006,2007,2008 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see <http://www.gnu.org/licenses/>. + */ + +/* TODO: support load options. */ + +#include <grub/loader.h> +#include <grub/file.h> +#include <grub/err.h> +#include <grub/device.h> +#include <grub/disk.h> +#include <grub/misc.h> +#include <grub/charset.h> +#include <grub/mm.h> +#include <grub/types.h> +#include <grub/dl.h> +#include <grub/efi/api.h> +#include <grub/efi/efi.h> +#include <grub/efi/disk.h> +#include <grub/command.h> +#include <grub/i18n.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +static grub_dl_t my_mod; + +static grub_efi_physical_address_t address; +static grub_efi_uintn_t pages; +static grub_efi_device_path_t *file_path; +static grub_efi_handle_t image_handle; +static grub_efi_char16_t *cmdline; + +static grub_err_t +grub_chainloader_unload (void) +{ + grub_efi_boot_services_t *b; + + b = grub_efi_system_table->boot_services; + efi_call_1 (b->unload_image, image_handle); + efi_call_2 (b->free_pages, address, pages); + + grub_free (file_path); + grub_free (cmdline); + cmdline = 0; + file_path = 0; + + grub_dl_unref (my_mod); + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_chainloader_boot (void) +{ + grub_efi_boot_services_t *b; + grub_efi_status_t status; + grub_efi_uintn_t exit_data_size; + grub_efi_char16_t *exit_data = NULL; + + b = grub_efi_system_table->boot_services; + status = efi_call_3 (b->start_image, image_handle, &exit_data_size, &exit_data); + if (status != GRUB_EFI_SUCCESS) + { + if (exit_data) + { + char *buf; + + buf = grub_malloc (exit_data_size * 4 + 1); + if (buf) + { + *grub_utf16_to_utf8 ((grub_uint8_t *) buf, + exit_data, exit_data_size) = 0; + + grub_error (GRUB_ERR_BAD_OS, buf); + grub_free (buf); + } + else + grub_error (GRUB_ERR_BAD_OS, "unknown error"); + } + } + + if (exit_data) + efi_call_1 (b->free_pool, exit_data); + + grub_loader_unset (); + + return grub_errno; +} + +static void +copy_file_path (grub_efi_file_path_device_path_t *fp, + const char *str, grub_efi_uint16_t len) +{ + grub_efi_char16_t *p; + grub_efi_uint16_t size; + + fp->header.type = GRUB_EFI_MEDIA_DEVICE_PATH_TYPE; + fp->header.subtype = GRUB_EFI_FILE_PATH_DEVICE_PATH_SUBTYPE; + size = len * sizeof (grub_efi_char16_t) + sizeof (*fp); + fp->header.length[0] = (grub_efi_uint8_t) (size & 0xff); + fp->header.length[1] = (grub_efi_uint8_t) (size >> 8); + for (p = fp->path_name; len > 0; len--, p++, str++) + { + /* FIXME: this assumes that the path is in ASCII. */ + *p = (grub_efi_char16_t) (*str == '/' ? '\\' : *str); + } +} + +static grub_efi_device_path_t * +make_file_path (grub_efi_device_path_t *dp, const char *filename) +{ + char *dir_start; + char *dir_end; + grub_size_t size; + grub_efi_device_path_t *d; + + dir_start = grub_strchr (filename, ')'); + if (! dir_start) + dir_start = (char *) filename; + else + dir_start++; + + dir_end = grub_strrchr (dir_start, '/'); + if (! dir_end) + { + grub_error (GRUB_ERR_BAD_FILENAME, "invalid EFI file path"); + return 0; + } + + size = 0; + d = dp; + while (1) + { + size += GRUB_EFI_DEVICE_PATH_LENGTH (d); + if ((GRUB_EFI_END_ENTIRE_DEVICE_PATH (d))) + break; + d = GRUB_EFI_NEXT_DEVICE_PATH (d); + } + + file_path = grub_malloc (size + + ((grub_strlen (dir_start) + 1) + * sizeof (grub_efi_char16_t)) + + sizeof (grub_efi_file_path_device_path_t) * 2); + if (! file_path) + return 0; + + grub_memcpy (file_path, dp, size); + + /* Fill the file path for the directory. */ + d = (grub_efi_device_path_t *) ((char *) file_path + + ((char *) d - (char *) dp)); + grub_efi_print_device_path (d); + copy_file_path ((grub_efi_file_path_device_path_t *) d, + dir_start, dir_end - dir_start); + + /* Fill the file path for the file. */ + d = GRUB_EFI_NEXT_DEVICE_PATH (d); + copy_file_path ((grub_efi_file_path_device_path_t *) d, + dir_end + 1, grub_strlen (dir_end + 1)); + + /* Fill the end of device path nodes. */ + d = GRUB_EFI_NEXT_DEVICE_PATH (d); + d->type = GRUB_EFI_END_DEVICE_PATH_TYPE; + d->subtype = GRUB_EFI_END_ENTIRE_DEVICE_PATH_SUBTYPE; + d->length[0] = sizeof (*d); + d->length[1] = 0; + + return file_path; +} + +static grub_err_t +grub_cmd_chainloader (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_file_t file = 0; + grub_ssize_t size; + grub_efi_status_t status; + grub_efi_boot_services_t *b; + grub_efi_handle_t dev_handle = 0; + grub_device_t dev = 0; + grub_efi_device_path_t *dp = 0; + grub_efi_loaded_image_t *loaded_image; + char *filename; + + if (argc == 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "no file specified"); + filename = argv[0]; + + grub_dl_ref (my_mod); + + /* Initialize some global variables. */ + address = 0; + image_handle = 0; + file_path = 0; + + b = grub_efi_system_table->boot_services; + + file = grub_file_open (filename); + if (! file) + goto fail; + + /* Get the root device's device path. */ + dev = grub_device_open (0); + if (! dev) + goto fail; + + if (dev->disk) + { + dev_handle = grub_efidisk_get_device_handle (dev->disk); + if (dev_handle) + dp = grub_efi_get_device_path (dev_handle); + } + + if (! dev->disk || ! dev_handle || ! dp) + { + grub_error (GRUB_ERR_BAD_DEVICE, "not a valid root device"); + goto fail; + } + + file_path = make_file_path (dp, filename); + if (! file_path) + goto fail; + + grub_printf ("file path: "); + grub_efi_print_device_path (file_path); + + size = grub_file_size (file); + if (!size) + { + grub_error (GRUB_ERR_BAD_OS, "file is empty"); + goto fail; + } + pages = (((grub_efi_uintn_t) size + ((1 << 12) - 1)) >> 12); + + status = efi_call_4 (b->allocate_pages, GRUB_EFI_ALLOCATE_ANY_PAGES, + GRUB_EFI_LOADER_CODE, + pages, &address); + if (status != GRUB_EFI_SUCCESS) + { + grub_error (GRUB_ERR_OUT_OF_MEMORY, "cannot allocate %u pages", pages); + goto fail; + } + + if (grub_file_read (file, (void *) ((grub_addr_t) address), size) != size) + { + if (grub_errno == GRUB_ERR_NONE) + grub_error (GRUB_ERR_BAD_OS, "too small"); + + goto fail; + } + + status = efi_call_6 (b->load_image, 0, grub_efi_image_handle, file_path, + (void *) ((grub_addr_t) address), size, + &image_handle); + if (status != GRUB_EFI_SUCCESS) + { + if (status == GRUB_EFI_OUT_OF_RESOURCES) + grub_error (GRUB_ERR_OUT_OF_MEMORY, "out of resources"); + else + grub_error (GRUB_ERR_BAD_OS, "cannot load image"); + + goto fail; + } + + /* LoadImage does not set a device handler when the image is + loaded from memory, so it is necessary to set it explicitly here. + This is a mess. */ + loaded_image = grub_efi_get_loaded_image (image_handle); + if (! loaded_image) + { + grub_error (GRUB_ERR_BAD_OS, "no loaded image available"); + goto fail; + } + loaded_image->device_handle = dev_handle; + + grub_file_close (file); + + if (argc > 1) + { + int i, len; + grub_efi_char16_t *p16; + + for (i = 1, len = 0; i < argc; i++) + len += grub_strlen (argv[i]) + 1; + + len *= sizeof (grub_efi_char16_t); + cmdline = p16 = grub_malloc (len); + if (! cmdline) + goto fail; + + for (i = 1; i < argc; i++) + { + char *p8; + + p8 = argv[i]; + while (*p8) + *(p16++) = *(p8++); + + *(p16++) = ' '; + } + *(--p16) = 0; + + loaded_image->load_options = cmdline; + loaded_image->load_options_size = len; + } + + grub_loader_set (grub_chainloader_boot, grub_chainloader_unload, 0); + return 0; + + fail: + + if (dev) + grub_device_close (dev); + + if (file) + grub_file_close (file); + + if (file_path) + grub_free (file_path); + + if (address) + efi_call_2 (b->free_pages, address, pages); + + grub_dl_unref (my_mod); + + return grub_errno; +} + +static grub_command_t cmd; + +GRUB_MOD_INIT(chainloader) +{ + cmd = grub_register_command ("chainloader", grub_cmd_chainloader, + 0, N_("Load another boot loader.")); + my_mod = mod; +} + +GRUB_MOD_FINI(chainloader) +{ + grub_unregister_command (cmd); +} diff --git a/grub-core/loader/i386/bsd.c b/grub-core/loader/i386/bsd.c new file mode 100644 index 0000000..6487dc3 --- /dev/null +++ b/grub-core/loader/i386/bsd.c @@ -0,0 +1,1992 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2008,2009,2010 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <grub/loader.h> +#include <grub/i386/bsd.h> +#include <grub/i386/cpuid.h> +#include <grub/memory.h> +#include <grub/i386/memory.h> +#include <grub/file.h> +#include <grub/err.h> +#include <grub/dl.h> +#include <grub/mm.h> +#include <grub/elfload.h> +#include <grub/env.h> +#include <grub/misc.h> +#include <grub/aout.h> +#include <grub/command.h> +#include <grub/extcmd.h> +#include <grub/i18n.h> +#include <grub/ns8250.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +#include <grub/video.h> +#ifdef GRUB_MACHINE_PCBIOS +#include <grub/machine/biosnum.h> +#endif +#ifdef GRUB_MACHINE_EFI +#include <grub/efi/efi.h> +#define NETBSD_DEFAULT_VIDEO_MODE "800x600" +#else +#define NETBSD_DEFAULT_VIDEO_MODE "text" +#include <grub/i386/pc/vbe.h> +#endif +#include <grub/video.h> + +#include <grub/disk.h> +#include <grub/device.h> +#include <grub/partition.h> +#include <grub/relocator.h> +#include <grub/i386/relocator.h> + +#define ALIGN_DWORD(a) ALIGN_UP (a, 4) +#define ALIGN_QWORD(a) ALIGN_UP (a, 8) +#define ALIGN_VAR(a) ((is_64bit) ? (ALIGN_QWORD(a)) : (ALIGN_DWORD(a))) +#define ALIGN_PAGE(a) ALIGN_UP (a, 4096) + +static int kernel_type = KERNEL_TYPE_NONE; +static grub_dl_t my_mod; +static grub_addr_t entry, entry_hi, kern_start, kern_end; +static void *kern_chunk_src; +static grub_uint32_t bootflags; +static int is_elf_kernel, is_64bit; +static grub_uint32_t openbsd_root; +static struct grub_relocator *relocator = NULL; +static struct grub_openbsd_ramdisk_descriptor openbsd_ramdisk; + +struct bsd_tag +{ + struct bsd_tag *next; + grub_size_t len; + grub_uint32_t type; + union { + grub_uint8_t a; + grub_uint16_t b; + grub_uint32_t c; + } data[0]; +}; + +static struct bsd_tag *tags, *tags_last; + +struct netbsd_module +{ + struct netbsd_module *next; + struct grub_netbsd_btinfo_module mod; +}; + +static struct netbsd_module *netbsd_mods, *netbsd_mods_last; + +static const struct grub_arg_option freebsd_opts[] = + { + {"dual", 'D', 0, N_("Display output on all consoles."), 0, 0}, + {"serial", 'h', 0, N_("Use serial console."), 0, 0}, + {"askname", 'a', 0, N_("Ask for file name to reboot from."), 0, 0}, + {"cdrom", 'C', 0, N_("Use CDROM as root."), 0, 0}, + {"config", 'c', 0, N_("Invoke user configuration routing."), 0, 0}, + {"kdb", 'd', 0, N_("Enter in KDB on boot."), 0, 0}, + {"gdb", 'g', 0, N_("Use GDB remote debugger instead of DDB."), 0, 0}, + {"mute", 'm', 0, N_("Disable all boot output."), 0, 0}, + {"nointr", 'n', 0, "", 0, 0}, + {"pause", 'p', 0, N_("Wait for keypress after every line of output."), 0, 0}, + {"quiet", 'q', 0, "", 0, 0}, + {"dfltroot", 'r', 0, N_("Use compiled-in rootdev."), 0, 0}, + {"single", 's', 0, N_("Boot into single mode."), 0, 0}, + {"verbose", 'v', 0, N_("Boot with verbose messages."), 0, 0}, + {0, 0, 0, 0, 0, 0} + }; + +static const grub_uint32_t freebsd_flags[] = +{ + FREEBSD_RB_DUAL, FREEBSD_RB_SERIAL, FREEBSD_RB_ASKNAME, + FREEBSD_RB_CDROM, FREEBSD_RB_CONFIG, FREEBSD_RB_KDB, + FREEBSD_RB_GDB, FREEBSD_RB_MUTE, FREEBSD_RB_NOINTR, + FREEBSD_RB_PAUSE, FREEBSD_RB_QUIET, FREEBSD_RB_DFLTROOT, + FREEBSD_RB_SINGLE, FREEBSD_RB_VERBOSE, 0 +}; + +static const struct grub_arg_option openbsd_opts[] = + { + {"askname", 'a', 0, N_("Ask for file name to reboot from."), 0, 0}, + {"halt", 'b', 0, N_("Don't reboot, just halt."), 0, 0}, + {"config", 'c', 0, N_("Change configured devices."), 0, 0}, + {"single", 's', 0, N_("Boot into single mode."), 0, 0}, + {"kdb", 'd', 0, N_("Enter in KDB on boot."), 0, 0}, + {"root", 'r', 0, N_("Set root device."), "wdXY", ARG_TYPE_STRING}, + {"serial", 'h', GRUB_ARG_OPTION_OPTIONAL, + N_("Use serial console."), N_("comUNIT[,SPEED]"), ARG_TYPE_STRING}, + {0, 0, 0, 0, 0, 0} + }; + +static const grub_uint32_t openbsd_flags[] = +{ + OPENBSD_RB_ASKNAME, OPENBSD_RB_HALT, OPENBSD_RB_CONFIG, + OPENBSD_RB_SINGLE, OPENBSD_RB_KDB, 0 +}; + +#define OPENBSD_ROOT_ARG (ARRAY_SIZE (openbsd_flags) - 1) +#define OPENBSD_SERIAL_ARG (ARRAY_SIZE (openbsd_flags)) + +static const struct grub_arg_option netbsd_opts[] = + { + {"no-smp", '1', 0, N_("Disable SMP."), 0, 0}, + {"no-acpi", '2', 0, N_("Disable ACPI."), 0, 0}, + {"askname", 'a', 0, N_("Ask for file name to reboot from."), 0, 0}, + {"halt", 'b', 0, N_("Don't reboot, just halt."), 0, 0}, + {"config", 'c', 0, N_("Change configured devices."), 0, 0}, + {"kdb", 'd', 0, N_("Enter in KDB on boot."), 0, 0}, + {"miniroot", 'm', 0, "", 0, 0}, + {"quiet", 'q', 0, N_("Don't display boot diagnostic messages."), 0, 0}, + {"single", 's', 0, N_("Boot into single mode."), 0, 0}, + {"verbose", 'v', 0, N_("Boot with verbose messages."), 0, 0}, + {"debug", 'x', 0, N_("Boot with debug messages."), 0, 0}, + {"silent", 'z', 0, N_("Supress normal output (warnings remain)."), 0, 0}, + {"root", 'r', 0, N_("Set root device."), N_("DEVICE"), ARG_TYPE_STRING}, + {"serial", 'h', GRUB_ARG_OPTION_OPTIONAL, + N_("Use serial console."), N_("[ADDR|comUNIT][,SPEED]"), ARG_TYPE_STRING}, + {0, 0, 0, 0, 0, 0} + }; + +static const grub_uint32_t netbsd_flags[] = +{ + NETBSD_AB_NOSMP, NETBSD_AB_NOACPI, NETBSD_RB_ASKNAME, + NETBSD_RB_HALT, NETBSD_RB_USERCONFIG, NETBSD_RB_KDB, + NETBSD_RB_MINIROOT, NETBSD_AB_QUIET, NETBSD_RB_SINGLE, + NETBSD_AB_VERBOSE, NETBSD_AB_DEBUG, NETBSD_AB_SILENT, 0 +}; + +#define NETBSD_ROOT_ARG (ARRAY_SIZE (netbsd_flags) - 1) +#define NETBSD_SERIAL_ARG (ARRAY_SIZE (netbsd_flags)) + +static void +grub_bsd_get_device (grub_uint32_t * biosdev, + grub_uint32_t * unit, + grub_uint32_t * slice, grub_uint32_t * part) +{ + grub_device_t dev; + +#ifdef GRUB_MACHINE_PCBIOS + *biosdev = grub_get_root_biosnumber () & 0xff; +#else + *biosdev = 0xff; +#endif + *unit = (*biosdev & 0x7f); + *slice = 0xff; + *part = 0xff; + dev = grub_device_open (0); + if (dev && dev->disk && dev->disk->partition) + { + if (dev->disk->partition->parent) + { + *part = dev->disk->partition->number; + *slice = dev->disk->partition->parent->number + 1; + } + else + *slice = dev->disk->partition->number + 1; + } + if (dev) + grub_device_close (dev); +} + +grub_err_t +grub_bsd_add_meta (grub_uint32_t type, void *data, grub_uint32_t len) +{ + struct bsd_tag *newtag; + + newtag = grub_malloc (len + sizeof (struct bsd_tag)); + if (!newtag) + return grub_errno; + newtag->len = len; + newtag->type = type; + newtag->next = NULL; + if (len) + grub_memcpy (newtag->data, data, len); + + if (kernel_type == KERNEL_TYPE_FREEBSD + && type == (FREEBSD_MODINFO_METADATA | FREEBSD_MODINFOMD_SMAP)) + { + struct bsd_tag *p; + for (p = tags; + p->type != (FREEBSD_MODINFO_METADATA | FREEBSD_MODINFOMD_KERNEND); + p = p->next); + + if (p) + { + newtag->next = p->next; + p->next = newtag; + if (newtag->next == NULL) + tags_last = newtag; + return GRUB_ERR_NONE; + } + } + + if (tags_last) + tags_last->next = newtag; + else + tags = newtag; + tags_last = newtag; + + return GRUB_ERR_NONE; +} + +struct grub_e820_mmap +{ + grub_uint64_t addr; + grub_uint64_t size; + grub_uint32_t type; +} __attribute__((packed)); +#define GRUB_E820_RAM 1 +#define GRUB_E820_RESERVED 2 +#define GRUB_E820_ACPI 3 +#define GRUB_E820_NVS 4 +#define GRUB_E820_BADRAM 5 + +static void +generate_e820_mmap (grub_size_t *len, grub_size_t *cnt, void *buf) +{ + int count = 0; + struct grub_e820_mmap *mmap = buf; + struct grub_e820_mmap prev, cur; + + auto int NESTED_FUNC_ATTR hook (grub_uint64_t, grub_uint64_t, + grub_memory_type_t); + int NESTED_FUNC_ATTR hook (grub_uint64_t addr, grub_uint64_t size, + grub_memory_type_t type) + { + cur.addr = addr; + cur.size = size; + switch (type) + { + case GRUB_MEMORY_AVAILABLE: + cur.type = GRUB_E820_RAM; + break; + + case GRUB_MEMORY_ACPI: + cur.type = GRUB_E820_ACPI; + break; + + case GRUB_MEMORY_NVS: + cur.type = GRUB_E820_NVS; + break; + + default: + case GRUB_MEMORY_CODE: + case GRUB_MEMORY_RESERVED: + cur.type = GRUB_E820_RESERVED; + break; + } + + /* Merge regions if possible. */ + if (count && cur.type == prev.type && cur.addr == prev.addr + prev.size) + { + prev.size += cur.size; + if (mmap) + mmap[-1] = prev; + } + else + { + if (mmap) + *mmap++ = cur; + prev = cur; + count++; + } + + if (kernel_type == KERNEL_TYPE_OPENBSD && prev.addr < 0x100000 + && prev.addr + prev.size > 0x100000) + { + cur.addr = 0x100000; + cur.size = prev.addr + prev.size - 0x100000; + cur.type = prev.type; + prev.size = 0x100000 - prev.addr; + if (mmap) + { + mmap[-1] = prev; + mmap[0] = cur; + mmap++; + } + prev = cur; + count++; + } + + return 0; + } + + grub_mmap_iterate (hook); + + if (len) + *len = count * sizeof (struct grub_e820_mmap); + *cnt = count; + + return; +} + +static grub_err_t +grub_bsd_add_mmap (void) +{ + grub_size_t len, cnt; + void *buf = NULL, *buf0; + + generate_e820_mmap (&len, &cnt, buf); + + if (kernel_type == KERNEL_TYPE_NETBSD) + len += sizeof (grub_uint32_t); + + if (kernel_type == KERNEL_TYPE_OPENBSD) + len += sizeof (struct grub_e820_mmap); + + buf = grub_malloc (len); + if (!buf) + return grub_errno; + + buf0 = buf; + if (kernel_type == KERNEL_TYPE_NETBSD) + { + *(grub_uint32_t *) buf = cnt; + buf = ((grub_uint32_t *) buf + 1); + } + + generate_e820_mmap (NULL, &cnt, buf); + + if (kernel_type == KERNEL_TYPE_OPENBSD) + grub_memset ((grub_uint8_t *) buf + len - sizeof (struct grub_e820_mmap), 0, + sizeof (struct grub_e820_mmap)); + + grub_dprintf ("bsd", "%u entries in smap\n", (unsigned) cnt); + if (kernel_type == KERNEL_TYPE_NETBSD) + grub_bsd_add_meta (NETBSD_BTINFO_MEMMAP, buf0, len); + else if (kernel_type == KERNEL_TYPE_OPENBSD) + grub_bsd_add_meta (OPENBSD_BOOTARG_MMAP, buf0, len); + else + grub_bsd_add_meta (FREEBSD_MODINFO_METADATA | + FREEBSD_MODINFOMD_SMAP, buf0, len); + + grub_free (buf0); + + return grub_errno; +} + +grub_err_t +grub_freebsd_add_meta_module (char *filename, char *type, int argc, char **argv, + grub_addr_t addr, grub_uint32_t size) +{ + char *name; + name = grub_strrchr (filename, '/'); + if (name) + name++; + else + name = filename; + if (grub_strcmp (type, "/boot/zfs/zpool.cache") == 0) + name = "/boot/zfs/zpool.cache"; + + if (grub_bsd_add_meta (FREEBSD_MODINFO_NAME, name, grub_strlen (name) + 1)) + return grub_errno; + + if (is_64bit) + { + grub_uint64_t addr64 = addr, size64 = size; + if (grub_bsd_add_meta (FREEBSD_MODINFO_TYPE, type, grub_strlen (type) + 1) + || grub_bsd_add_meta (FREEBSD_MODINFO_ADDR, &addr64, sizeof (addr64)) + || grub_bsd_add_meta (FREEBSD_MODINFO_SIZE, &size64, sizeof (size64))) + return grub_errno; + } + else + { + if (grub_bsd_add_meta (FREEBSD_MODINFO_TYPE, type, grub_strlen (type) + 1) + || grub_bsd_add_meta (FREEBSD_MODINFO_ADDR, &addr, sizeof (addr)) + || grub_bsd_add_meta (FREEBSD_MODINFO_SIZE, &size, sizeof (size))) + return grub_errno; + } + + if (argc) + { + int i, n; + + n = 0; + for (i = 0; i < argc; i++) + { + n += grub_strlen (argv[i]) + 1; + } + + if (n) + { + char cmdline[n], *p; + + p = cmdline; + for (i = 0; i < argc; i++) + { + grub_strcpy (p, argv[i]); + p += grub_strlen (argv[i]); + *(p++) = ' '; + } + *p = 0; + + if (grub_bsd_add_meta (FREEBSD_MODINFO_ARGS, cmdline, n)) + return grub_errno; + } + } + + return GRUB_ERR_NONE; +} + +static void +grub_freebsd_list_modules (void) +{ + struct bsd_tag *tag; + + grub_printf (" %-18s %-18s%14s%14s\n", "name", "type", "addr", "size"); + + for (tag = tags; tag; tag = tag->next) + { + switch (tag->type) + { + case FREEBSD_MODINFO_NAME: + case FREEBSD_MODINFO_TYPE: + grub_printf (" %-18s", (char *) tag->data); + break; + case FREEBSD_MODINFO_ADDR: + { + grub_uint32_t addr; + + addr = *((grub_uint32_t *) tag->data); + grub_printf (" 0x%08x", addr); + break; + } + case FREEBSD_MODINFO_SIZE: + { + grub_uint32_t len; + + len = *((grub_uint32_t *) tag->data); + grub_printf (" 0x%08x\n", len); + } + } + } +} + +static grub_err_t +grub_netbsd_add_meta_module (char *filename, grub_uint32_t type, + grub_addr_t addr, grub_uint32_t size) +{ + char *name; + struct netbsd_module *mod; + name = grub_strrchr (filename, '/'); + + if (name) + name++; + else + name = filename; + + mod = grub_zalloc (sizeof (*mod)); + if (!mod) + return grub_errno; + + grub_strncpy (mod->mod.name, name, sizeof (mod->mod.name) - 1); + mod->mod.addr = addr; + mod->mod.type = type; + mod->mod.size = size; + + if (netbsd_mods_last) + netbsd_mods_last->next = mod; + else + netbsd_mods = mod; + netbsd_mods_last = mod; + + return GRUB_ERR_NONE; +} + +static void +grub_netbsd_list_modules (void) +{ + struct netbsd_module *mod; + + grub_printf (" %-18s%14s%14s%14s\n", "name", "type", "addr", "size"); + + for (mod = netbsd_mods; mod; mod = mod->next) + grub_printf (" %-18s 0x%08x 0x%08x 0x%08x", mod->mod.name, + mod->mod.type, mod->mod.addr, mod->mod.size); +} + +/* This function would be here but it's under different license. */ +#include "bsd_pagetable.c" + +static grub_uint32_t freebsd_bootdev, freebsd_biosdev; + +static grub_err_t +grub_freebsd_boot (void) +{ + struct grub_freebsd_bootinfo bi; + grub_uint8_t *p, *p0; + grub_addr_t p_target; + grub_size_t p_size = 0; + grub_err_t err; + grub_size_t tag_buf_len = 0; + + auto int iterate_env (struct grub_env_var *var); + int iterate_env (struct grub_env_var *var) + { + if ((!grub_memcmp (var->name, "kFreeBSD.", sizeof("kFreeBSD.") - 1)) && (var->name[sizeof("kFreeBSD.") - 1])) + { + grub_strcpy ((char *) p, &var->name[sizeof("kFreeBSD.") - 1]); + p += grub_strlen ((char *) p); + *(p++) = '='; + grub_strcpy ((char *) p, var->value); + p += grub_strlen ((char *) p) + 1; + } + + return 0; + } + + auto int iterate_env_count (struct grub_env_var *var); + int iterate_env_count (struct grub_env_var *var) + { + if ((!grub_memcmp (var->name, "kFreeBSD.", sizeof("kFreeBSD.") - 1)) && (var->name[sizeof("kFreeBSD.") - 1])) + { + p_size += grub_strlen (&var->name[sizeof("kFreeBSD.") - 1]); + p_size++; + p_size += grub_strlen (var->value) + 1; + } + + return 0; + } + + grub_memset (&bi, 0, sizeof (bi)); + bi.version = FREEBSD_BOOTINFO_VERSION; + bi.length = sizeof (bi); + + bi.boot_device = freebsd_biosdev; + + p_size = 0; + grub_env_iterate (iterate_env_count); + + if (p_size) + p_size = ALIGN_PAGE (kern_end + p_size + 1) - kern_end; + + if (is_elf_kernel) + { + struct bsd_tag *tag; + + err = grub_bsd_add_mmap (); + if (err) + return err; + + err = grub_bsd_add_meta (FREEBSD_MODINFO_END, 0, 0); + if (err) + return err; + + tag_buf_len = 0; + for (tag = tags; tag; tag = tag->next) + tag_buf_len = ALIGN_VAR (tag_buf_len + + sizeof (struct freebsd_tag_header) + + tag->len); + p_size = ALIGN_PAGE (kern_end + p_size + tag_buf_len) - kern_end; + } + + if (is_64bit) + p_size += 4096 * 3; + + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_addr (relocator, &ch, + kern_end, p_size); + if (err) + return err; + p = get_virtual_current_address (ch); + } + p_target = kern_end; + p0 = p; + kern_end += p_size; + + grub_env_iterate (iterate_env); + + if (p != p0) + { + *(p++) = 0; + + bi.environment = p_target; + } + + if (is_elf_kernel) + { + grub_uint8_t *p_tag = p; + struct bsd_tag *tag; + + for (tag = tags; tag; tag = tag->next) + { + struct freebsd_tag_header *head + = (struct freebsd_tag_header *) p_tag; + head->type = tag->type; + head->len = tag->len; + p_tag += sizeof (struct freebsd_tag_header); + switch (tag->type) + { + case FREEBSD_MODINFO_METADATA | FREEBSD_MODINFOMD_HOWTO: + if (is_64bit) + *(grub_uint64_t *) p_tag = bootflags; + else + *(grub_uint32_t *) p_tag = bootflags; + break; + + case FREEBSD_MODINFO_METADATA | FREEBSD_MODINFOMD_ENVP: + if (is_64bit) + *(grub_uint64_t *) p_tag = bi.environment; + else + *(grub_uint32_t *) p_tag = bi.environment; + break; + + case FREEBSD_MODINFO_METADATA | FREEBSD_MODINFOMD_KERNEND: + if (is_64bit) + *(grub_uint64_t *) p_tag = kern_end; + else + *(grub_uint32_t *) p_tag = kern_end; + break; + + default: + grub_memcpy (p_tag, tag->data, tag->len); + break; + } + p_tag += tag->len; + p_tag = ALIGN_VAR (p_tag - p) + p; + } + + bi.tags = (p - p0) + p_target; + + p = (ALIGN_PAGE ((p_tag - p0) + p_target) - p_target) + p0; + } + + bi.kern_end = kern_end; + + grub_video_set_mode ("text", 0, 0); + + if (is_64bit) + { + struct grub_relocator64_state state; + grub_uint8_t *pagetable; + grub_uint32_t *stack; + grub_addr_t stack_target; + + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_align (relocator, &ch, + 0x10000, 0x90000, + 3 * sizeof (grub_uint32_t) + + sizeof (bi), 4, + GRUB_RELOCATOR_PREFERENCE_NONE); + if (err) + return err; + stack = get_virtual_current_address (ch); + stack_target = get_physical_target_address (ch); + } + +#ifdef GRUB_MACHINE_EFI + err = grub_efi_finish_boot_services (NULL, NULL, NULL, NULL, NULL); + if (err) + return err; +#endif + + pagetable = p; + fill_bsd64_pagetable (pagetable, (pagetable - p0) + p_target); + + state.cr3 = (pagetable - p0) + p_target; + state.rsp = stack_target; + state.rip = (((grub_uint64_t) entry_hi) << 32) | entry; + + stack[0] = entry; + stack[1] = bi.tags; + stack[2] = kern_end; + return grub_relocator64_boot (relocator, state, 0, 0x40000000); + } + else + { + struct grub_relocator32_state state; + grub_uint32_t *stack; + grub_addr_t stack_target; + + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_align (relocator, &ch, + 0x10000, 0x90000, + 9 * sizeof (grub_uint32_t) + + sizeof (bi), 4, + GRUB_RELOCATOR_PREFERENCE_NONE); + if (err) + return err; + stack = get_virtual_current_address (ch); + stack_target = get_physical_target_address (ch); + } + +#ifdef GRUB_MACHINE_EFI + err = grub_efi_finish_boot_services (NULL, NULL, NULL, NULL, NULL); + if (err) + return err; +#endif + + grub_memcpy (&stack[9], &bi, sizeof (bi)); + state.eip = entry; + state.esp = stack_target; + state.ebp = stack_target; + stack[0] = entry; /* "Return" address. */ + stack[1] = bootflags | FREEBSD_RB_BOOTINFO; + stack[2] = freebsd_bootdev; + stack[3] = 0; + stack[4] = 0; + stack[5] = 0; + stack[6] = stack_target + 9 * sizeof (grub_uint32_t); + stack[7] = bi.tags; + stack[8] = kern_end; + return grub_relocator32_boot (relocator, state); + } + + /* Not reached. */ + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_openbsd_boot (void) +{ + grub_uint32_t *stack; + struct grub_relocator32_state state; + void *curarg, *buf0, *arg0; + grub_addr_t buf_target; + grub_err_t err; + grub_size_t tag_buf_len; + + err = grub_bsd_add_mmap (); + if (err) + return err; + + { + struct bsd_tag *tag; + tag_buf_len = 0; + for (tag = tags; tag; tag = tag->next) + tag_buf_len = ALIGN_VAR (tag_buf_len + + sizeof (struct grub_openbsd_bootargs) + + tag->len); + } + + buf_target = GRUB_BSD_TEMP_BUFFER - 9 * sizeof (grub_uint32_t); + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_addr (relocator, &ch, buf_target, + tag_buf_len + + sizeof (struct grub_openbsd_bootargs) + + 9 * sizeof (grub_uint32_t)); + if (err) + return err; + buf0 = get_virtual_current_address (ch); + } + + stack = (grub_uint32_t *) buf0; + arg0 = curarg = stack + 9; + + { + struct bsd_tag *tag; + struct grub_openbsd_bootargs *head; + + for (tag = tags; tag; tag = tag->next) + { + head = curarg; + head->ba_type = tag->type; + head->ba_size = tag->len + sizeof (*head); + curarg = head + 1; + grub_memcpy (curarg, tag->data, tag->len); + curarg = (grub_uint8_t *) curarg + tag->len; + head->ba_next = (grub_uint8_t *) curarg - (grub_uint8_t *) buf0 + + buf_target; + } + head = curarg; + head->ba_type = OPENBSD_BOOTARG_END; + head->ba_size = 0; + head->ba_next = 0; + } + + grub_video_set_mode ("text", 0, 0); + +#ifdef GRUB_MACHINE_EFI + err = grub_efi_finish_boot_services (NULL, NULL, NULL, NULL, NULL); + if (err) + return err; +#endif + + state.eip = entry; + state.ebp = state.esp + = ((grub_uint8_t *) stack - (grub_uint8_t *) buf0) + buf_target; + stack[0] = entry; + stack[1] = bootflags; + stack[2] = openbsd_root; + stack[3] = OPENBSD_BOOTARG_APIVER; + stack[4] = 0; + stack[5] = grub_mmap_get_upper () >> 10; + stack[6] = grub_mmap_get_lower () >> 10; + stack[7] = (grub_uint8_t *) curarg - (grub_uint8_t *) arg0; + stack[8] = ((grub_uint8_t *) arg0 - (grub_uint8_t *) buf0) + buf_target; + + return grub_relocator32_boot (relocator, state); +} + +static grub_err_t +grub_netbsd_setup_video (void) +{ + struct grub_video_mode_info mode_info; + void *framebuffer; + const char *modevar; + struct grub_netbsd_btinfo_framebuf params; + grub_err_t err; + grub_video_driver_id_t driv_id; + + modevar = grub_env_get ("gfxpayload"); + + /* Now all graphical modes are acceptable. + May change in future if we have modes without framebuffer. */ + if (modevar && *modevar != 0) + { + char *tmp; + tmp = grub_xasprintf ("%s;" NETBSD_DEFAULT_VIDEO_MODE, modevar); + if (! tmp) + return grub_errno; + err = grub_video_set_mode (tmp, 0, 0); + grub_free (tmp); + } + else + err = grub_video_set_mode (NETBSD_DEFAULT_VIDEO_MODE, 0, 0); + + if (err) + return err; + + driv_id = grub_video_get_driver_id (); + if (driv_id == GRUB_VIDEO_DRIVER_NONE) + return GRUB_ERR_NONE; + + err = grub_video_get_info_and_fini (&mode_info, &framebuffer); + + if (err) + return err; + + params.width = mode_info.width; + params.height = mode_info.height; + params.bpp = mode_info.bpp; + params.pitch = mode_info.pitch; + params.flags = 0; + + params.fbaddr = (grub_addr_t) framebuffer; + + params.red_mask_size = mode_info.red_mask_size; + params.red_field_pos = mode_info.red_field_pos; + params.green_mask_size = mode_info.green_mask_size; + params.green_field_pos = mode_info.green_field_pos; + params.blue_mask_size = mode_info.blue_mask_size; + params.blue_field_pos = mode_info.blue_field_pos; + +#ifdef GRUB_MACHINE_PCBIOS + /* VESA packed modes may come with zeroed mask sizes, which need + to be set here according to DAC Palette width. If we don't, + this results in Linux displaying a black screen. */ + if (mode_info.bpp <= 8 && driv_id == GRUB_VIDEO_DRIVER_VBE) + { + struct grub_vbe_info_block controller_info; + int status; + int width = 8; + + status = grub_vbe_bios_get_controller_info (&controller_info); + + if (status == GRUB_VBE_STATUS_OK && + (controller_info.capabilities & GRUB_VBE_CAPABILITY_DACWIDTH)) + status = grub_vbe_bios_set_dac_palette_width (&width); + + if (status != GRUB_VBE_STATUS_OK) + /* 6 is default after mode reset. */ + width = 6; + + params.red_mask_size = params.green_mask_size + = params.blue_mask_size = width; + } +#endif + + err = grub_bsd_add_meta (NETBSD_BTINFO_FRAMEBUF, ¶ms, sizeof (params)); + return err; +} + +static grub_err_t +grub_netbsd_add_modules (void) +{ + struct netbsd_module *mod; + unsigned modcnt = 0; + struct grub_netbsd_btinfo_modules *mods; + unsigned i; + grub_err_t err; + + for (mod = netbsd_mods; mod; mod = mod->next) + modcnt++; + + mods = grub_malloc (sizeof (*mods) + sizeof (mods->mods[0]) * modcnt); + if (!mods) + return grub_errno; + + mods->num = modcnt; + mods->last_addr = kern_end; + for (mod = netbsd_mods, i = 0; mod; i++, mod = mod->next) + mods->mods[i] = mod->mod; + + err = grub_bsd_add_meta (NETBSD_BTINFO_MODULES, mods, + sizeof (*mods) + sizeof (mods->mods[0]) * modcnt); + grub_free (mods); + return err; +} + +static grub_err_t +grub_netbsd_boot (void) +{ + struct grub_netbsd_bootinfo *bootinfo; + void *curarg, *arg0; + grub_addr_t arg_target, stack_target; + grub_uint32_t *stack; + grub_err_t err; + struct grub_relocator32_state state; + grub_size_t tag_buf_len = 0; + int tag_count = 0; + + err = grub_bsd_add_mmap (); + if (err) + return err; + + err = grub_netbsd_setup_video (); + if (err) + { + grub_print_error (); + grub_printf ("Booting however\n"); + grub_errno = GRUB_ERR_NONE; + } + + err = grub_netbsd_add_modules (); + if (err) + return err; + + { + struct bsd_tag *tag; + tag_buf_len = 0; + for (tag = tags; tag; tag = tag->next) + { + tag_buf_len = ALIGN_VAR (tag_buf_len + + sizeof (struct grub_netbsd_btinfo_common) + + tag->len); + tag_count++; + } + } + + arg_target = kern_end; + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_addr (relocator, &ch, + arg_target, tag_buf_len + + sizeof (struct grub_netbsd_bootinfo) + + tag_count * sizeof (grub_uint32_t)); + if (err) + return err; + curarg = get_virtual_current_address (ch); + } + + arg0 = curarg; + bootinfo = (void *) ((grub_uint8_t *) arg0 + tag_buf_len); + + { + struct bsd_tag *tag; + unsigned i; + + bootinfo->bi_count = tag_count; + for (tag = tags, i = 0; tag; i++, tag = tag->next) + { + struct grub_netbsd_btinfo_common *head = curarg; + bootinfo->bi_data[i] = ((grub_uint8_t *) curarg - (grub_uint8_t *) arg0) + + arg_target; + head->type = tag->type; + head->len = tag->len + sizeof (*head); + curarg = head + 1; + grub_memcpy (curarg, tag->data, tag->len); + curarg = (grub_uint8_t *) curarg + tag->len; + } + } + + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_align (relocator, &ch, 0x10000, 0x90000, + 7 * sizeof (grub_uint32_t), 4, + GRUB_RELOCATOR_PREFERENCE_NONE); + if (err) + return err; + stack = get_virtual_current_address (ch); + stack_target = get_physical_target_address (ch); + } + +#ifdef GRUB_MACHINE_EFI + err = grub_efi_finish_boot_services (NULL, NULL, NULL, NULL, NULL); + if (err) + return err; +#endif + + state.eip = entry; + state.esp = stack_target; + state.ebp = stack_target; + stack[0] = entry; + stack[1] = bootflags; + stack[2] = 0; + stack[3] = ((grub_uint8_t *) bootinfo - (grub_uint8_t *) arg0) + arg_target; + stack[4] = 0; + stack[5] = grub_mmap_get_upper () >> 10; + stack[6] = grub_mmap_get_lower () >> 10; + + return grub_relocator32_boot (relocator, state); +} + +static grub_err_t +grub_bsd_unload (void) +{ + struct bsd_tag *tag, *next; + for (tag = tags; tag; tag = next) + { + next = tag->next; + grub_free (tag); + } + tags = NULL; + tags_last = NULL; + + kernel_type = KERNEL_TYPE_NONE; + grub_dl_unref (my_mod); + + grub_relocator_unload (relocator); + relocator = NULL; + + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_bsd_load_aout (grub_file_t file) +{ + grub_addr_t load_addr, load_end; + int ofs, align_page; + union grub_aout_header ah; + grub_err_t err; + grub_size_t bss_size; + + if ((grub_file_seek (file, 0)) == (grub_off_t) - 1) + return grub_errno; + + if (grub_file_read (file, &ah, sizeof (ah)) != sizeof (ah)) + return grub_error (GRUB_ERR_READ_ERROR, "cannot read the a.out header"); + + if (grub_aout_get_type (&ah) != AOUT_TYPE_AOUT32) + return grub_error (GRUB_ERR_BAD_OS, "invalid a.out header"); + + entry = ah.aout32.a_entry & 0xFFFFFF; + + if (AOUT_GETMAGIC (ah.aout32) == AOUT32_ZMAGIC) + { + load_addr = entry; + ofs = 0x1000; + align_page = 0; + } + else + { + load_addr = entry & 0xF00000; + ofs = sizeof (struct grub_aout32_header); + align_page = 1; + } + + if (load_addr < 0x100000) + return grub_error (GRUB_ERR_BAD_OS, "load address below 1M"); + + kern_start = load_addr; + load_end = kern_end = load_addr + ah.aout32.a_text + ah.aout32.a_data; + if (align_page) + kern_end = ALIGN_PAGE (kern_end); + + if (ah.aout32.a_bss) + { + kern_end += ah.aout32.a_bss; + if (align_page) + kern_end = ALIGN_PAGE (kern_end); + + bss_size = kern_end - load_end; + } + else + bss_size = 0; + + { + grub_relocator_chunk_t ch; + + err = grub_relocator_alloc_chunk_addr (relocator, &ch, + kern_start, kern_end - kern_start); + if (err) + return err; + kern_chunk_src = get_virtual_current_address (ch); + } + + return grub_aout_load (file, ofs, kern_chunk_src, + ah.aout32.a_text + ah.aout32.a_data, + bss_size); +} + +static int NESTED_FUNC_ATTR +grub_bsd_elf32_size_hook (grub_elf_t elf __attribute__ ((unused)), + Elf32_Phdr *phdr, void *arg __attribute__ ((unused))) +{ + Elf32_Addr paddr; + + if (phdr->p_type != PT_LOAD + && phdr->p_type != PT_DYNAMIC) + return 0; + + paddr = phdr->p_paddr & 0xFFFFFF; + + if (paddr < kern_start) + kern_start = paddr; + + if (paddr + phdr->p_memsz > kern_end) + kern_end = paddr + phdr->p_memsz; + + return 0; +} + +static grub_err_t +grub_bsd_elf32_hook (Elf32_Phdr * phdr, grub_addr_t * addr, int *do_load) +{ + Elf32_Addr paddr; + + if (phdr->p_type != PT_LOAD + && phdr->p_type != PT_DYNAMIC) + { + *do_load = 0; + return 0; + } + + *do_load = 1; + phdr->p_paddr &= 0xFFFFFF; + paddr = phdr->p_paddr; + + *addr = (grub_addr_t) (paddr - kern_start + (grub_uint8_t *) kern_chunk_src); + + return GRUB_ERR_NONE; +} + +static int NESTED_FUNC_ATTR +grub_bsd_elf64_size_hook (grub_elf_t elf __attribute__ ((unused)), + Elf64_Phdr *phdr, void *arg __attribute__ ((unused))) +{ + Elf64_Addr paddr; + + if (phdr->p_type != PT_LOAD + && phdr->p_type != PT_DYNAMIC) + return 0; + + paddr = phdr->p_paddr & 0xffffff; + + if (paddr < kern_start) + kern_start = paddr; + + if (paddr + phdr->p_memsz > kern_end) + kern_end = paddr + phdr->p_memsz; + + return 0; +} + +static grub_err_t +grub_bsd_elf64_hook (Elf64_Phdr * phdr, grub_addr_t * addr, int *do_load) +{ + Elf64_Addr paddr; + + if (phdr->p_type != PT_LOAD + && phdr->p_type != PT_DYNAMIC) + { + *do_load = 0; + return 0; + } + + *do_load = 1; + paddr = phdr->p_paddr & 0xffffff; + + *addr = (grub_addr_t) (paddr - kern_start + (grub_uint8_t *) kern_chunk_src); + + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_bsd_load_elf (grub_elf_t elf) +{ + grub_err_t err; + + kern_end = 0; + kern_start = ~0; + + if (grub_elf_is_elf32 (elf)) + { + grub_relocator_chunk_t ch; + + entry = elf->ehdr.ehdr32.e_entry & 0xFFFFFF; + err = grub_elf32_phdr_iterate (elf, grub_bsd_elf32_size_hook, NULL); + if (err) + return err; + err = grub_relocator_alloc_chunk_addr (relocator, &ch, + kern_start, kern_end - kern_start); + if (err) + return err; + + kern_chunk_src = get_virtual_current_address (ch); + + err = grub_elf32_load (elf, grub_bsd_elf32_hook, 0, 0); + if (err) + return err; + if (kernel_type != KERNEL_TYPE_OPENBSD) + return GRUB_ERR_NONE; + return grub_openbsd_find_ramdisk32 (elf->file, kern_start, + kern_chunk_src, &openbsd_ramdisk); + } + else if (grub_elf_is_elf64 (elf)) + { + is_64bit = 1; + + if (! grub_cpuid_has_longmode) + return grub_error (GRUB_ERR_BAD_OS, "your CPU does not implement AMD64 architecture"); + + /* FreeBSD has 64-bit entry point. */ + if (kernel_type == KERNEL_TYPE_FREEBSD) + { + entry = elf->ehdr.ehdr64.e_entry & 0xffffffff; + entry_hi = (elf->ehdr.ehdr64.e_entry >> 32) & 0xffffffff; + } + else + { + entry = elf->ehdr.ehdr64.e_entry & 0x0fffffff; + entry_hi = 0; + } + + err = grub_elf64_phdr_iterate (elf, grub_bsd_elf64_size_hook, NULL); + if (err) + return err; + + grub_dprintf ("bsd", "kern_start = %lx, kern_end = %lx\n", + (unsigned long) kern_start, (unsigned long) kern_end); + { + grub_relocator_chunk_t ch; + + err = grub_relocator_alloc_chunk_addr (relocator, &ch, kern_start, + kern_end - kern_start); + if (err) + return err; + kern_chunk_src = get_virtual_current_address (ch); + } + + err = grub_elf64_load (elf, grub_bsd_elf64_hook, 0, 0); + if (err) + return err; + if (kernel_type != KERNEL_TYPE_OPENBSD) + return GRUB_ERR_NONE; + return grub_openbsd_find_ramdisk64 (elf->file, kern_start, + kern_chunk_src, &openbsd_ramdisk); + } + else + return grub_error (GRUB_ERR_BAD_OS, "invalid ELF"); +} + +static grub_err_t +grub_bsd_load (int argc, char *argv[]) +{ + grub_file_t file; + grub_elf_t elf; + + grub_dl_ref (my_mod); + + grub_loader_unset (); + + grub_memset (&openbsd_ramdisk, 0, sizeof (openbsd_ramdisk)); + + if (argc == 0) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, "no kernel specified"); + goto fail; + } + + file = grub_file_open (argv[0]); + if (!file) + goto fail; + + relocator = grub_relocator_new (); + + elf = grub_elf_file (file); + if (elf) + { + is_elf_kernel = 1; + grub_bsd_load_elf (elf); + grub_elf_close (elf); + } + else + { + is_elf_kernel = 0; + grub_errno = 0; + grub_bsd_load_aout (file); + grub_file_close (file); + } + + kern_end = ALIGN_PAGE (kern_end); + +fail: + + if (grub_errno != GRUB_ERR_NONE) + grub_dl_unref (my_mod); + + return grub_errno; +} + +static grub_uint32_t +grub_bsd_parse_flags (const struct grub_arg_list *state, + const grub_uint32_t * flags) +{ + grub_uint32_t result = 0; + unsigned i; + + for (i = 0; flags[i]; i++) + if (state[i].set) + result |= flags[i]; + + return result; +} + +static grub_err_t +grub_cmd_freebsd (grub_extcmd_context_t ctxt, int argc, char *argv[]) +{ + kernel_type = KERNEL_TYPE_FREEBSD; + bootflags = grub_bsd_parse_flags (ctxt->state, freebsd_flags); + + if (grub_bsd_load (argc, argv) == GRUB_ERR_NONE) + { + grub_uint32_t unit, slice, part; + + kern_end = ALIGN_PAGE (kern_end); + if (is_elf_kernel) + { + grub_err_t err; + grub_uint64_t data = 0; + grub_file_t file; + int len = is_64bit ? 8 : 4; + + err = grub_freebsd_add_meta_module (argv[0], is_64bit + ? FREEBSD_MODTYPE_KERNEL64 + : FREEBSD_MODTYPE_KERNEL, + argc - 1, argv + 1, + kern_start, + kern_end - kern_start); + if (err) + return err; + + file = grub_file_open (argv[0]); + if (! file) + return grub_errno; + + if (is_64bit) + err = grub_freebsd_load_elf_meta64 (relocator, file, &kern_end); + else + err = grub_freebsd_load_elf_meta32 (relocator, file, &kern_end); + if (err) + return err; + + err = grub_bsd_add_meta (FREEBSD_MODINFO_METADATA | + FREEBSD_MODINFOMD_HOWTO, &data, 4); + if (err) + return err; + + err = grub_bsd_add_meta (FREEBSD_MODINFO_METADATA | + FREEBSD_MODINFOMD_ENVP, &data, len); + if (err) + return err; + + err = grub_bsd_add_meta (FREEBSD_MODINFO_METADATA | + FREEBSD_MODINFOMD_KERNEND, &data, len); + if (err) + return err; + } + grub_bsd_get_device (&freebsd_biosdev, &unit, &slice, &part); + freebsd_bootdev = (FREEBSD_B_DEVMAGIC + ((slice + 1) << FREEBSD_B_SLICESHIFT) + + (unit << FREEBSD_B_UNITSHIFT) + (part << FREEBSD_B_PARTSHIFT)); + + grub_loader_set (grub_freebsd_boot, grub_bsd_unload, 0); + } + + return grub_errno; +} + +static grub_err_t +grub_cmd_openbsd (grub_extcmd_context_t ctxt, int argc, char *argv[]) +{ + grub_uint32_t bootdev; + + kernel_type = KERNEL_TYPE_OPENBSD; + bootflags = grub_bsd_parse_flags (ctxt->state, openbsd_flags); + + if (ctxt->state[OPENBSD_ROOT_ARG].set) + { + const char *arg = ctxt->state[OPENBSD_ROOT_ARG].arg; + int unit, part; + if (*(arg++) != 'w' || *(arg++) != 'd') + return grub_error (GRUB_ERR_BAD_ARGUMENT, + "only device specifications of form " + "wd<number><lowercase letter> are supported"); + + unit = grub_strtoul (arg, (char **) &arg, 10); + if (! (arg && *arg >= 'a' && *arg <= 'z')) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + "only device specifications of form " + "wd<number><lowercase letter> are supported"); + + part = *arg - 'a'; + + bootdev = (OPENBSD_B_DEVMAGIC + (unit << OPENBSD_B_UNITSHIFT) + + (part << OPENBSD_B_PARTSHIFT)); + } + else + bootdev = 0; + + if (ctxt->state[OPENBSD_SERIAL_ARG].set) + { + struct grub_openbsd_bootarg_console serial; + char *ptr; + unsigned port = 0; + unsigned speed = 9600; + + grub_memset (&serial, 0, sizeof (serial)); + + if (ctxt->state[OPENBSD_SERIAL_ARG].arg) + { + ptr = ctxt->state[OPENBSD_SERIAL_ARG].arg; + if (grub_memcmp (ptr, "com", sizeof ("com") - 1) != 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + "only com0-com3 are supported"); + ptr += sizeof ("com") - 1; + port = grub_strtoul (ptr, &ptr, 0); + if (port >= 4) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + "only com0-com3 are supported"); + if (*ptr == ',') + { + ptr++; + speed = grub_strtoul (ptr, &ptr, 0); + if (grub_errno) + return grub_errno; + } + } + + serial.device = (GRUB_OPENBSD_COM_MAJOR << 8) | port; + serial.speed = speed; + + grub_bsd_add_meta (OPENBSD_BOOTARG_CONSOLE, &serial, sizeof (serial)); + bootflags |= OPENBSD_RB_SERCONS; + } + else + { + struct grub_openbsd_bootarg_console serial; + + grub_memset (&serial, 0, sizeof (serial)); + serial.device = (GRUB_OPENBSD_VGA_MAJOR << 8); + grub_bsd_add_meta (OPENBSD_BOOTARG_CONSOLE, &serial, sizeof (serial)); + bootflags &= ~OPENBSD_RB_SERCONS; + } + + if (grub_bsd_load (argc, argv) == GRUB_ERR_NONE) + { + grub_loader_set (grub_openbsd_boot, grub_bsd_unload, 0); + openbsd_root = bootdev; + } + + return grub_errno; +} + +static grub_err_t +grub_cmd_netbsd (grub_extcmd_context_t ctxt, int argc, char *argv[]) +{ + grub_err_t err; + kernel_type = KERNEL_TYPE_NETBSD; + bootflags = grub_bsd_parse_flags (ctxt->state, netbsd_flags); + + if (grub_bsd_load (argc, argv) == GRUB_ERR_NONE) + { + if (is_elf_kernel) + { + grub_file_t file; + + file = grub_file_open (argv[0]); + if (! file) + return grub_errno; + + if (is_64bit) + err = grub_netbsd_load_elf_meta64 (relocator, file, &kern_end); + else + err = grub_netbsd_load_elf_meta32 (relocator, file, &kern_end); + if (err) + return err; + } + + { + char bootpath[GRUB_NETBSD_MAX_BOOTPATH_LEN]; + char *name; + name = grub_strrchr (argv[0], '/'); + if (name) + name++; + else + name = argv[0]; + grub_memset (bootpath, 0, sizeof (bootpath)); + grub_strncpy (bootpath, name, sizeof (bootpath) - 1); + grub_bsd_add_meta (NETBSD_BTINFO_BOOTPATH, bootpath, sizeof (bootpath)); + } + + if (ctxt->state[NETBSD_ROOT_ARG].set) + { + char root[GRUB_NETBSD_MAX_ROOTDEVICE_LEN]; + grub_memset (root, 0, sizeof (root)); + grub_strncpy (root, ctxt->state[NETBSD_ROOT_ARG].arg, + sizeof (root) - 1); + grub_bsd_add_meta (NETBSD_BTINFO_ROOTDEVICE, root, sizeof (root)); + } + if (ctxt->state[NETBSD_SERIAL_ARG].set) + { + struct grub_netbsd_btinfo_serial serial; + char *ptr; + + grub_memset (&serial, 0, sizeof (serial)); + grub_strcpy (serial.devname, "com"); + + serial.addr = grub_ns8250_hw_get_port (0); + serial.speed = 9600; + + if (ctxt->state[NETBSD_SERIAL_ARG].arg) + { + ptr = ctxt->state[NETBSD_SERIAL_ARG].arg; + if (grub_memcmp (ptr, "com", sizeof ("com") - 1) == 0) + { + ptr += sizeof ("com") - 1; + serial.addr + = grub_ns8250_hw_get_port (grub_strtoul (ptr, &ptr, 0)); + } + else + serial.addr = grub_strtoul (ptr, &ptr, 0); + if (grub_errno) + return grub_errno; + + if (*ptr == ',') + { + ptr++; + serial.speed = grub_strtoul (ptr, &ptr, 0); + if (grub_errno) + return grub_errno; + } + } + + grub_bsd_add_meta (NETBSD_BTINFO_CONSOLE, &serial, sizeof (serial)); + } + else + { + struct grub_netbsd_btinfo_serial cons; + + grub_memset (&cons, 0, sizeof (cons)); + grub_strcpy (cons.devname, "pc"); + + grub_bsd_add_meta (NETBSD_BTINFO_CONSOLE, &cons, sizeof (cons)); + } + + grub_loader_set (grub_netbsd_boot, grub_bsd_unload, 0); + } + + return grub_errno; +} + +static grub_err_t +grub_cmd_freebsd_loadenv (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_file_t file = 0; + char *buf = 0, *curr, *next; + int len; + + if (! grub_loader_is_loaded ()) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + "you need to load the kernel first"); + + if (kernel_type != KERNEL_TYPE_FREEBSD) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + "only FreeBSD supports environment"); + + if (argc == 0) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, "no filename"); + goto fail; + } + + file = grub_file_open (argv[0]); + if ((!file) || (!file->size)) + goto fail; + + len = file->size; + buf = grub_malloc (len + 1); + if (!buf) + goto fail; + + if (grub_file_read (file, buf, len) != len) + goto fail; + + buf[len] = 0; + + next = buf; + while (next) + { + char *p; + + curr = next; + next = grub_strchr (curr, '\n'); + if (next) + { + + p = next - 1; + while (p > curr) + { + if ((*p != '\r') && (*p != ' ') && (*p != '\t')) + break; + p--; + } + + if ((p > curr) && (*p == '"')) + p--; + + *(p + 1) = 0; + next++; + } + + if (*curr == '#') + continue; + + p = grub_strchr (curr, '='); + if (!p) + continue; + + *(p++) = 0; + + if (*curr) + { + char *name; + + if (*p == '"') + p++; + + name = grub_xasprintf ("kFreeBSD.%s", curr); + if (!name) + goto fail; + if (grub_env_set (name, p)) + { + grub_free (name); + goto fail; + } + grub_free (name); + } + } + +fail: + grub_free (buf); + + if (file) + grub_file_close (file); + + return grub_errno; +} + +static grub_err_t +grub_cmd_freebsd_module (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_file_t file = 0; + int modargc; + char **modargv; + char *type; + grub_err_t err; + void *src; + + if (kernel_type != KERNEL_TYPE_FREEBSD) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "no FreeBSD loaded"); + + if (!is_elf_kernel) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + "only ELF kernel supports module"); + + /* List the current modules if no parameter. */ + if (!argc) + { + grub_freebsd_list_modules (); + return 0; + } + + file = grub_file_open (argv[0]); + if ((!file) || (!file->size)) + goto fail; + + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_addr (relocator, &ch, kern_end, + file->size); + if (err) + goto fail; + src = get_virtual_current_address (ch); + } + + + grub_file_read (file, src, file->size); + if (grub_errno) + goto fail; + + modargc = argc - 1; + modargv = argv + 1; + + if (modargc && (! grub_memcmp (modargv[0], "type=", 5))) + { + type = &modargv[0][5]; + modargc--; + modargv++; + } + else + type = FREEBSD_MODTYPE_RAW; + + err = grub_freebsd_add_meta_module (argv[0], type, modargc, modargv, + kern_end, file->size); + if (err) + goto fail; + + kern_end = ALIGN_PAGE (kern_end + file->size); + +fail: + if (file) + grub_file_close (file); + + return grub_errno; +} + +static grub_err_t +grub_netbsd_module_load (char *filename, grub_uint32_t type) +{ + grub_file_t file = 0; + void *src; + grub_err_t err; + + file = grub_file_open (filename); + if ((!file) || (!file->size)) + goto fail; + + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_addr (relocator, &ch, kern_end, + file->size); + if (err) + goto fail; + + src = get_virtual_current_address (ch); + } + + grub_file_read (file, src, file->size); + if (grub_errno) + goto fail; + + err = grub_netbsd_add_meta_module (filename, type, kern_end, file->size); + + if (err) + goto fail; + + kern_end = ALIGN_PAGE (kern_end + file->size); + +fail: + if (file) + grub_file_close (file); + + return grub_errno; +} + +static grub_err_t +grub_cmd_netbsd_module (grub_command_t cmd, + int argc, char *argv[]) +{ + grub_uint32_t type; + + if (kernel_type != KERNEL_TYPE_NETBSD) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "no NetBSD loaded"); + + if (!is_elf_kernel) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + "only ELF kernel supports module"); + + /* List the current modules if no parameter. */ + if (!argc) + { + grub_netbsd_list_modules (); + return 0; + } + + if (grub_strcmp (cmd->name, "knetbsd_module_elf") == 0) + type = GRUB_NETBSD_MODULE_ELF; + else + type = GRUB_NETBSD_MODULE_RAW; + + return grub_netbsd_module_load (argv[0], type); +} + +static grub_err_t +grub_cmd_freebsd_module_elf (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_file_t file = 0; + grub_err_t err; + + if (! grub_loader_is_loaded ()) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + "you need to load the kernel first"); + + if (kernel_type != KERNEL_TYPE_FREEBSD) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + "only FreeBSD supports module"); + + if (! is_elf_kernel) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + "only ELF kernel supports module"); + + /* List the current modules if no parameter. */ + if (! argc) + { + grub_freebsd_list_modules (); + return 0; + } + + file = grub_file_open (argv[0]); + if (!file) + return grub_errno; + if (!file->size) + { + grub_file_close (file); + return grub_errno; + } + + if (is_64bit) + err = grub_freebsd_load_elfmodule_obj64 (relocator, file, + argc, argv, &kern_end); + else + err = grub_freebsd_load_elfmodule32 (relocator, file, + argc, argv, &kern_end); + grub_file_close (file); + + return err; +} + +static grub_err_t +grub_cmd_openbsd_ramdisk (grub_command_t cmd __attribute__ ((unused)), + int argc, char *args[]) +{ + grub_file_t file; + grub_size_t size; + + if (argc != 1) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "file name required"); + + if (kernel_type != KERNEL_TYPE_OPENBSD) + return grub_error (GRUB_ERR_BAD_OS, "no kOpenBSD loaded"); + + if (!openbsd_ramdisk.max_size) + return grub_error (GRUB_ERR_BAD_OS, "your kOpenBSD doesn't support ramdisk"); + + file = grub_file_open (args[0]); + if (! file) + return grub_error (GRUB_ERR_FILE_NOT_FOUND, + "couldn't load ramdisk"); + + size = grub_file_size (file); + + if (size > openbsd_ramdisk.max_size) + { + grub_file_close (file); + return grub_error (GRUB_ERR_BAD_OS, "your kOpenBSD supports ramdisk only" + " up to %u bytes, however you supplied a %u bytes one", + openbsd_ramdisk.max_size, size); + } + + if (grub_file_read (file, openbsd_ramdisk.target, size) + != (grub_ssize_t) (size)) + { + grub_file_close (file); + grub_error_push (); + return grub_error (GRUB_ERR_BAD_OS, "couldn't read file %s", args[0]); + } + grub_memset (openbsd_ramdisk.target + size, 0, + openbsd_ramdisk.max_size - size); + *openbsd_ramdisk.size = ALIGN_UP (size, 512); + + return GRUB_ERR_NONE; +} + +static grub_extcmd_t cmd_freebsd, cmd_openbsd, cmd_netbsd; +static grub_command_t cmd_freebsd_loadenv, cmd_freebsd_module; +static grub_command_t cmd_netbsd_module, cmd_freebsd_module_elf; +static grub_command_t cmd_netbsd_module_elf, cmd_openbsd_ramdisk; + +GRUB_MOD_INIT (bsd) +{ + /* Net and OpenBSD kernels are often compressed. */ + grub_dl_load ("gzio"); + + cmd_freebsd = grub_register_extcmd ("kfreebsd", grub_cmd_freebsd, 0, + N_("FILE"), N_("Load kernel of FreeBSD."), + freebsd_opts); + cmd_openbsd = grub_register_extcmd ("kopenbsd", grub_cmd_openbsd, 0, + N_("FILE"), N_("Load kernel of OpenBSD."), + openbsd_opts); + cmd_netbsd = grub_register_extcmd ("knetbsd", grub_cmd_netbsd, 0, + N_("FILE"), N_("Load kernel of NetBSD."), + netbsd_opts); + cmd_freebsd_loadenv = + grub_register_command ("kfreebsd_loadenv", grub_cmd_freebsd_loadenv, + 0, N_("Load FreeBSD env.")); + cmd_freebsd_module = + grub_register_command ("kfreebsd_module", grub_cmd_freebsd_module, + 0, N_("Load FreeBSD kernel module.")); + cmd_netbsd_module = + grub_register_command ("knetbsd_module", grub_cmd_netbsd_module, + 0, N_("Load NetBSD kernel module.")); + cmd_netbsd_module_elf = + grub_register_command ("knetbsd_module_elf", grub_cmd_netbsd_module, + 0, N_("Load NetBSD kernel module (ELF).")); + cmd_freebsd_module_elf = + grub_register_command ("kfreebsd_module_elf", grub_cmd_freebsd_module_elf, + 0, N_("Load FreeBSD kernel module (ELF).")); + + cmd_openbsd_ramdisk = grub_register_command ("kopenbsd_ramdisk", + grub_cmd_openbsd_ramdisk, 0, + "Load kOpenBSD ramdisk. "); + + my_mod = mod; +} + +GRUB_MOD_FINI (bsd) +{ + grub_unregister_extcmd (cmd_freebsd); + grub_unregister_extcmd (cmd_openbsd); + grub_unregister_extcmd (cmd_netbsd); + + grub_unregister_command (cmd_freebsd_loadenv); + grub_unregister_command (cmd_freebsd_module); + grub_unregister_command (cmd_netbsd_module); + grub_unregister_command (cmd_freebsd_module_elf); + grub_unregister_command (cmd_netbsd_module_elf); + grub_unregister_command (cmd_openbsd_ramdisk); + + grub_bsd_unload (); +} diff --git a/grub-core/loader/i386/bsd32.c b/grub-core/loader/i386/bsd32.c new file mode 100644 index 0000000..26704c4 --- /dev/null +++ b/grub-core/loader/i386/bsd32.c @@ -0,0 +1,6 @@ +#define SUFFIX(x) x ## 32 +#define GRUB_TARGET_WORDSIZE 32 +#define OBJSYM 0 +#include <grub/types.h> +typedef grub_uint32_t grub_freebsd_addr_t; +#include "bsdXX.c" diff --git a/grub-core/loader/i386/bsd64.c b/grub-core/loader/i386/bsd64.c new file mode 100644 index 0000000..f8aad1c --- /dev/null +++ b/grub-core/loader/i386/bsd64.c @@ -0,0 +1,6 @@ +#define SUFFIX(x) x ## 64 +#define GRUB_TARGET_WORDSIZE 64 +#define OBJSYM 1 +#include <grub/types.h> +typedef grub_uint64_t grub_freebsd_addr_t; +#include "bsdXX.c" diff --git a/grub-core/loader/i386/bsdXX.c b/grub-core/loader/i386/bsdXX.c new file mode 100644 index 0000000..92d2675 --- /dev/null +++ b/grub-core/loader/i386/bsdXX.c @@ -0,0 +1,621 @@ +#include <grub/loader.h> +#include <grub/i386/bsd.h> +#include <grub/mm.h> +#include <grub/elf.h> +#include <grub/misc.h> +#include <grub/i386/relocator.h> + +#define ALIGN_PAGE(a) ALIGN_UP (a, 4096) + +static inline grub_err_t +load (grub_file_t file, void *where, grub_off_t off, grub_size_t size) +{ + if (grub_file_seek (file, off) == (grub_off_t) -1) + return grub_errno; + if (grub_file_read (file, where, size) != (grub_ssize_t) size) + { + if (grub_errno) + return grub_errno; + else + return grub_error (GRUB_ERR_BAD_OS, "file is truncated"); + } + return GRUB_ERR_NONE; +} + +static inline grub_err_t +read_headers (grub_file_t file, Elf_Ehdr *e, char **shdr) +{ + if (grub_file_seek (file, 0) == (grub_off_t) -1) + return grub_errno; + + if (grub_file_read (file, (char *) e, sizeof (*e)) != sizeof (*e)) + { + if (grub_errno) + return grub_errno; + else + return grub_error (GRUB_ERR_BAD_OS, "file is too short"); + } + + if (e->e_ident[EI_MAG0] != ELFMAG0 + || e->e_ident[EI_MAG1] != ELFMAG1 + || e->e_ident[EI_MAG2] != ELFMAG2 + || e->e_ident[EI_MAG3] != ELFMAG3 + || e->e_ident[EI_VERSION] != EV_CURRENT + || e->e_version != EV_CURRENT) + return grub_error (GRUB_ERR_BAD_OS, "invalid arch independent ELF magic"); + + if (e->e_ident[EI_CLASS] != SUFFIX (ELFCLASS)) + return grub_error (GRUB_ERR_BAD_OS, "invalid arch dependent ELF magic"); + + *shdr = grub_malloc (e->e_shnum * e->e_shentsize); + if (! *shdr) + return grub_errno; + + if (grub_file_seek (file, e->e_shoff) == (grub_off_t) -1) + return grub_errno; + + if (grub_file_read (file, *shdr, e->e_shnum * e->e_shentsize) + != e->e_shnum * e->e_shentsize) + { + if (grub_errno) + return grub_errno; + else + return grub_error (GRUB_ERR_BAD_OS, "file is truncated"); + } + + return GRUB_ERR_NONE; +} + +/* On i386 FreeBSD uses "elf module" approarch for 32-bit variant + and "elf obj module" for 64-bit variant. However it may differ on other + platforms. So I keep both versions. */ +#if OBJSYM +grub_err_t +SUFFIX (grub_freebsd_load_elfmodule_obj) (struct grub_relocator *relocator, + grub_file_t file, int argc, + char *argv[], grub_addr_t *kern_end) +{ + Elf_Ehdr e; + Elf_Shdr *s; + char *shdr = 0; + grub_addr_t curload, module; + grub_err_t err; + grub_size_t chunk_size = 0; + void *chunk_src; + + err = read_headers (file, &e, &shdr); + if (err) + return err; + + curload = module = ALIGN_PAGE (*kern_end); + + for (s = (Elf_Shdr *) shdr; s < (Elf_Shdr *) ((char *) shdr + + e.e_shnum * e.e_shentsize); + s = (Elf_Shdr *) ((char *) s + e.e_shentsize)) + { + if (s->sh_size == 0) + continue; + + if (s->sh_addralign) + chunk_size = ALIGN_UP (chunk_size + *kern_end, s->sh_addralign) + - *kern_end; + + chunk_size += s->sh_size; + } + + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_addr (relocator, &ch, + module, chunk_size); + if (err) + return err; + chunk_src = get_virtual_current_address (ch); + } + + for (s = (Elf_Shdr *) shdr; s < (Elf_Shdr *) ((char *) shdr + + e.e_shnum * e.e_shentsize); + s = (Elf_Shdr *) ((char *) s + e.e_shentsize)) + { + if (s->sh_size == 0) + continue; + + if (s->sh_addralign) + curload = ALIGN_UP (curload, s->sh_addralign); + s->sh_addr = curload; + + grub_dprintf ("bsd", "loading section to %x, size %d, align %d\n", + (unsigned) curload, (int) s->sh_size, + (int) s->sh_addralign); + + switch (s->sh_type) + { + default: + case SHT_PROGBITS: + err = load (file, (grub_uint8_t *) chunk_src + curload - *kern_end, + s->sh_offset, s->sh_size); + if (err) + return err; + break; + case SHT_NOBITS: + grub_memset ((grub_uint8_t *) chunk_src + curload - *kern_end, 0, + s->sh_size); + break; + } + curload += s->sh_size; + } + + *kern_end = ALIGN_PAGE (curload); + + err = grub_freebsd_add_meta_module (argv[0], FREEBSD_MODTYPE_ELF_MODULE_OBJ, + argc - 1, argv + 1, module, + curload - module); + if (! err) + err = grub_bsd_add_meta (FREEBSD_MODINFO_METADATA + | FREEBSD_MODINFOMD_ELFHDR, + &e, sizeof (e)); + if (! err) + err = grub_bsd_add_meta (FREEBSD_MODINFO_METADATA + | FREEBSD_MODINFOMD_SHDR, + shdr, e.e_shnum * e.e_shentsize); + + return err; +} + +#else + +grub_err_t +SUFFIX (grub_freebsd_load_elfmodule) (struct grub_relocator *relocator, + grub_file_t file, int argc, char *argv[], + grub_addr_t *kern_end) +{ + Elf_Ehdr e; + Elf_Shdr *s; + char *shdr = 0; + grub_addr_t curload, module; + grub_err_t err; + grub_size_t chunk_size = 0; + void *chunk_src; + + err = read_headers (file, &e, &shdr); + if (err) + return err; + + curload = module = ALIGN_PAGE (*kern_end); + + for (s = (Elf_Shdr *) shdr; s < (Elf_Shdr *) ((char *) shdr + + e.e_shnum * e.e_shentsize); + s = (Elf_Shdr *) ((char *) s + e.e_shentsize)) + { + if (s->sh_size == 0) + continue; + + if (! (s->sh_flags & SHF_ALLOC)) + continue; + if (chunk_size < s->sh_addr + s->sh_size) + chunk_size = s->sh_addr + s->sh_size; + } + + if (chunk_size < sizeof (e)) + chunk_size = sizeof (e); + chunk_size += e.e_phnum * e.e_phentsize; + chunk_size += e.e_shnum * e.e_shentsize; + + { + grub_relocator_chunk_t ch; + + err = grub_relocator_alloc_chunk_addr (relocator, &ch, + module, chunk_size); + if (err) + return err; + + chunk_src = get_virtual_current_address (ch); + } + + for (s = (Elf_Shdr *) shdr; s < (Elf_Shdr *) ((char *) shdr + + e.e_shnum * e.e_shentsize); + s = (Elf_Shdr *) ((char *) s + e.e_shentsize)) + { + if (s->sh_size == 0) + continue; + + if (! (s->sh_flags & SHF_ALLOC)) + continue; + + grub_dprintf ("bsd", "loading section to %x, size %d, align %d\n", + (unsigned) curload, (int) s->sh_size, + (int) s->sh_addralign); + + switch (s->sh_type) + { + default: + case SHT_PROGBITS: + err = load (file, (grub_uint8_t *) chunk_src + module + + s->sh_addr - *kern_end, + s->sh_offset, s->sh_size); + if (err) + return err; + break; + case SHT_NOBITS: + grub_memset ((grub_uint8_t *) chunk_src + module + + s->sh_addr - *kern_end, 0, s->sh_size); + break; + } + if (curload < module + s->sh_addr + s->sh_size) + curload = module + s->sh_addr + s->sh_size; + } + + load (file, UINT_TO_PTR (module), 0, sizeof (e)); + if (curload < module + sizeof (e)) + curload = module + sizeof (e); + + load (file, UINT_TO_PTR (curload), e.e_shoff, + e.e_shnum * e.e_shentsize); + e.e_shoff = curload - module; + curload += e.e_shnum * e.e_shentsize; + + load (file, UINT_TO_PTR (curload), e.e_phoff, + e.e_phnum * e.e_phentsize); + e.e_phoff = curload - module; + curload += e.e_phnum * e.e_phentsize; + + *kern_end = curload; + + grub_freebsd_add_meta_module (argv[0], FREEBSD_MODTYPE_ELF_MODULE, + argc - 1, argv + 1, module, + curload - module); + return SUFFIX (grub_freebsd_load_elf_meta) (relocator, file, kern_end); +} + +#endif + +grub_err_t +SUFFIX (grub_freebsd_load_elf_meta) (struct grub_relocator *relocator, + grub_file_t file, grub_addr_t *kern_end) +{ + grub_err_t err; + Elf_Ehdr e; + Elf_Shdr *s; + char *shdr = 0; + unsigned symoff, stroff, symsize, strsize; + grub_freebsd_addr_t symstart, symend, symentsize, dynamic; + Elf_Sym *sym; + void *sym_chunk; + grub_uint8_t *curload; + grub_freebsd_addr_t symtarget; + const char *str; + unsigned i; + grub_size_t chunk_size; + + err = read_headers (file, &e, &shdr); + if (err) + return err; + + err = grub_bsd_add_meta (FREEBSD_MODINFO_METADATA | + FREEBSD_MODINFOMD_ELFHDR, &e, + sizeof (e)); + if (err) + return err; + + for (s = (Elf_Shdr *) shdr; s < (Elf_Shdr *) (shdr + + e.e_shnum * e.e_shentsize); + s = (Elf_Shdr *) ((char *) s + e.e_shentsize)) + if (s->sh_type == SHT_SYMTAB) + break; + if (s >= (Elf_Shdr *) ((char *) shdr + + e.e_shnum * e.e_shentsize)) + return grub_error (GRUB_ERR_BAD_OS, "no symbol table"); + symoff = s->sh_offset; + symsize = s->sh_size; + symentsize = s->sh_entsize; + s = (Elf_Shdr *) (shdr + e.e_shentsize * s->sh_link); + stroff = s->sh_offset; + strsize = s->sh_size; + + chunk_size = ALIGN_UP (symsize + strsize, sizeof (grub_freebsd_addr_t)) + + 2 * sizeof (grub_freebsd_addr_t); + + symtarget = ALIGN_UP (*kern_end, sizeof (grub_freebsd_addr_t)); + + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_addr (relocator, &ch, + symtarget, chunk_size); + if (err) + return err; + sym_chunk = get_virtual_current_address (ch); + } + + symstart = symtarget; + symend = symstart + chunk_size; + + curload = sym_chunk; + *((grub_freebsd_addr_t *) curload) = symsize; + curload += sizeof (grub_freebsd_addr_t); + + if (grub_file_seek (file, symoff) == (grub_off_t) -1) + return grub_errno; + sym = (Elf_Sym *) curload; + if (grub_file_read (file, curload, symsize) != (grub_ssize_t) symsize) + { + if (! grub_errno) + return grub_error (GRUB_ERR_BAD_OS, "invalid ELF"); + return grub_errno; + } + curload += symsize; + + *((grub_freebsd_addr_t *) curload) = strsize; + curload += sizeof (grub_freebsd_addr_t); + if (grub_file_seek (file, stroff) == (grub_off_t) -1) + return grub_errno; + str = (char *) curload; + if (grub_file_read (file, curload, strsize) != (grub_ssize_t) strsize) + { + if (! grub_errno) + return grub_error (GRUB_ERR_BAD_OS, "invalid ELF"); + return grub_errno; + } + + for (i = 0; + i * symentsize < symsize; + i++, sym = (Elf_Sym *) ((char *) sym + symentsize)) + { + const char *name = str + sym->st_name; + if (grub_strcmp (name, "_DYNAMIC") == 0) + break; + } + + if (i * symentsize < symsize) + { + dynamic = sym->st_value; + grub_dprintf ("bsd", "dynamic = %llx\n", (unsigned long long) dynamic); + err = grub_bsd_add_meta (FREEBSD_MODINFO_METADATA | + FREEBSD_MODINFOMD_DYNAMIC, &dynamic, + sizeof (dynamic)); + if (err) + return err; + } + + err = grub_bsd_add_meta (FREEBSD_MODINFO_METADATA | + FREEBSD_MODINFOMD_SSYM, &symstart, + sizeof (symstart)); + if (err) + return err; + + err = grub_bsd_add_meta (FREEBSD_MODINFO_METADATA | + FREEBSD_MODINFOMD_ESYM, &symend, + sizeof (symend)); + if (err) + return err; + + *kern_end = ALIGN_PAGE (symend); + + return GRUB_ERR_NONE; +} + +grub_err_t +SUFFIX (grub_netbsd_load_elf_meta) (struct grub_relocator *relocator, + grub_file_t file, grub_addr_t *kern_end) +{ + grub_err_t err; + Elf_Ehdr e; + Elf_Shdr *s, *symsh, *strsh; + char *shdr = NULL; + unsigned symsize, strsize; + void *sym_chunk; + grub_uint8_t *curload; + grub_size_t chunk_size; + Elf_Ehdr *e2; + struct grub_netbsd_btinfo_symtab symtab; + grub_addr_t symtarget; + + err = read_headers (file, &e, &shdr); + if (err) + return err; + + for (s = (Elf_Shdr *) shdr; s < (Elf_Shdr *) (shdr + + e.e_shnum * e.e_shentsize); + s = (Elf_Shdr *) ((char *) s + e.e_shentsize)) + if (s->sh_type == SHT_SYMTAB) + break; + if (s >= (Elf_Shdr *) ((char *) shdr + + e.e_shnum * e.e_shentsize)) + return GRUB_ERR_NONE; + symsize = s->sh_size; + symsh = s; + s = (Elf_Shdr *) (shdr + e.e_shentsize * s->sh_link); + strsize = s->sh_size; + strsh = s; + + chunk_size = ALIGN_UP (symsize, sizeof (grub_freebsd_addr_t)) + + ALIGN_UP (strsize, sizeof (grub_freebsd_addr_t)) + + sizeof (e) + e.e_shnum * e.e_shentsize; + + symtarget = ALIGN_UP (*kern_end, sizeof (grub_freebsd_addr_t)); + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_addr (relocator, &ch, + symtarget, chunk_size); + if (err) + return err; + sym_chunk = get_virtual_current_address (ch); + } + + symtab.nsyms = 1; + symtab.ssyms = symtarget; + symtab.esyms = symtarget + chunk_size; + + curload = sym_chunk; + + e2 = (Elf_Ehdr *) curload; + grub_memcpy (curload, &e, sizeof (e)); + e2->e_phoff = 0; + e2->e_phnum = 0; + e2->e_phentsize = 0; + e2->e_shstrndx = 0; + e2->e_shoff = sizeof (e); + + curload += sizeof (e); + + for (s = (Elf_Shdr *) shdr; s < (Elf_Shdr *) (shdr + + e.e_shnum * e.e_shentsize); + s = (Elf_Shdr *) ((char *) s + e.e_shentsize)) + { + Elf_Shdr *s2; + s2 = (Elf_Shdr *) curload; + grub_memcpy (curload, s, e.e_shentsize); + if (s == symsh) + s2->sh_offset = sizeof (e) + e.e_shnum * e.e_shentsize; + else if (s == strsh) + s2->sh_offset = ALIGN_UP (symsize, sizeof (grub_freebsd_addr_t)) + + sizeof (e) + e.e_shnum * e.e_shentsize; + else + s2->sh_offset = 0; + s2->sh_addr = s2->sh_offset; + curload += e.e_shentsize; + } + + if (grub_file_seek (file, symsh->sh_offset) == (grub_off_t) -1) + return grub_errno; + if (grub_file_read (file, curload, symsize) != (grub_ssize_t) symsize) + { + if (! grub_errno) + return grub_error (GRUB_ERR_BAD_OS, "invalid ELF"); + return grub_errno; + } + curload += ALIGN_UP (symsize, sizeof (grub_freebsd_addr_t)); + + if (grub_file_seek (file, strsh->sh_offset) == (grub_off_t) -1) + return grub_errno; + if (grub_file_read (file, curload, strsize) != (grub_ssize_t) strsize) + { + if (! grub_errno) + return grub_error (GRUB_ERR_BAD_OS, "invalid ELF"); + return grub_errno; + } + + err = grub_bsd_add_meta (NETBSD_BTINFO_SYMTAB, + &symtab, + sizeof (symtab)); + if (err) + return err; + + *kern_end = ALIGN_PAGE (symtarget + chunk_size); + + return GRUB_ERR_NONE; +} + +grub_err_t +SUFFIX(grub_openbsd_find_ramdisk) (grub_file_t file, + grub_addr_t kern_start, + void *kern_chunk_src, + struct grub_openbsd_ramdisk_descriptor *desc) +{ + unsigned symoff, stroff, symsize, strsize, symentsize; + + { + grub_err_t err; + Elf_Ehdr e; + Elf_Shdr *s; + char *shdr = NULL; + + err = read_headers (file, &e, &shdr); + if (err) + return err; + + for (s = (Elf_Shdr *) shdr; s < (Elf_Shdr *) (shdr + + e.e_shnum * e.e_shentsize); + s = (Elf_Shdr *) ((char *) s + e.e_shentsize)) + if (s->sh_type == SHT_SYMTAB) + break; + if (s >= (Elf_Shdr *) ((char *) shdr + e.e_shnum * e.e_shentsize)) + { + grub_free (shdr); + return GRUB_ERR_NONE; + } + + symsize = s->sh_size; + symentsize = s->sh_entsize; + symoff = s->sh_offset; + + s = (Elf_Shdr *) (shdr + e.e_shentsize * s->sh_link); + stroff = s->sh_offset; + strsize = s->sh_size; + grub_free (shdr); + } + { + Elf_Sym *syms, *sym, *imagesym = NULL, *sizesym = NULL; + unsigned i; + char *strs; + + syms = grub_malloc (symsize); + if (!syms) + return grub_errno; + + if (grub_file_seek (file, symoff) == (grub_off_t) -1) + { + grub_free (syms); + return grub_errno; + } + if (grub_file_read (file, syms, symsize) != (grub_ssize_t) symsize) + { + grub_free (syms); + if (! grub_errno) + return grub_error (GRUB_ERR_BAD_OS, "invalid ELF"); + return grub_errno; + } + + strs = grub_malloc (strsize); + if (!strs) + { + grub_free (syms); + return grub_errno; + } + + if (grub_file_seek (file, stroff) == (grub_off_t) -1) + return grub_errno; + if (grub_file_read (file, strs, strsize) != (grub_ssize_t) strsize) + { + grub_free (syms); + grub_free (strs); + if (! grub_errno) + return grub_error (GRUB_ERR_BAD_OS, "invalid ELF"); + return grub_errno; + } + + for (i = 0, sym = syms; i < symsize / symentsize; + i++, sym = (Elf_Sym *) ((char *) sym + symentsize)) + { + if (ELF_ST_TYPE (sym->st_info) != STT_OBJECT) + continue; + if (!sym->st_name) + continue; + if (grub_strcmp (strs + sym->st_name, "rd_root_image") == 0) + imagesym = sym; + if (grub_strcmp (strs + sym->st_name, "rd_root_size") == 0) + sizesym = sym; + if (imagesym && sizesym) + break; + } + if (!imagesym || !sizesym) + { + grub_free (syms); + grub_free (strs); + return GRUB_ERR_NONE; + } + if (sizeof (*desc->size) != sizesym->st_size) + { + grub_free (syms); + grub_free (strs); + return grub_error (GRUB_ERR_BAD_OS, "unexpected size of rd_root_size"); + } + desc->max_size = imagesym->st_size; + desc->target = (imagesym->st_value & 0xFFFFFF) - kern_start + + (grub_uint8_t *) kern_chunk_src; + desc->size = (grub_uint32_t *) ((sizesym->st_value & 0xFFFFFF) - kern_start + + (grub_uint8_t *) kern_chunk_src); + grub_free (syms); + grub_free (strs); + + return GRUB_ERR_NONE; + } +} diff --git a/grub-core/loader/i386/bsd_pagetable.c b/grub-core/loader/i386/bsd_pagetable.c new file mode 100644 index 0000000..9ec5abf --- /dev/null +++ b/grub-core/loader/i386/bsd_pagetable.c @@ -0,0 +1,92 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (c) 1998 Michael Smith <msmith@freebsd.org> + * Copyright (C) 2009 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see <http://www.gnu.org/licenses/>. + */ + +/* Based on the code from FreeBSD originally distributed under the + following terms: */ + +/*- + * Copyright (c) 1998 Michael Smith <msmith@freebsd.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + + +static void +fill_bsd64_pagetable (grub_uint8_t *src, grub_addr_t target) +{ + grub_uint64_t *pt2, *pt3, *pt4; + grub_addr_t pt2t, pt3t; + int i; + +#define PG_V 0x001 +#define PG_RW 0x002 +#define PG_U 0x004 +#define PG_PS 0x080 + + pt4 = (grub_uint64_t *) src; + pt3 = (grub_uint64_t *) (src + 4096); + pt2 = (grub_uint64_t *) (src + 8192); + + pt3t = target + 4096; + pt2t = target + 8192; + + grub_memset (src, 0, 4096 * 3); + + /* + * This is kinda brutal, but every single 1GB VM memory segment points to + * the same first 1GB of physical memory. But it is how BSD expects + * it to be. + */ + for (i = 0; i < 512; i++) + { + /* Each slot of the level 4 pages points to the same level 3 page */ + pt4[i] = (grub_addr_t) pt3t; + pt4[i] |= PG_V | PG_RW | PG_U; + + /* Each slot of the level 3 pages points to the same level 2 page */ + pt3[i] = (grub_addr_t) pt2t; + pt3[i] |= PG_V | PG_RW | PG_U; + + /* The level 2 page slots are mapped with 2MB pages for 1GB. */ + pt2[i] = i * (2 * 1024 * 1024); + pt2[i] |= PG_V | PG_RW | PG_PS | PG_U; + } +} diff --git a/grub-core/loader/i386/linux.c b/grub-core/loader/i386/linux.c new file mode 100644 index 0000000..f19f471 --- /dev/null +++ b/grub-core/loader/i386/linux.c @@ -0,0 +1,1016 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2006,2007,2008,2009,2010 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <grub/loader.h> +#include <grub/memory.h> +#include <grub/normal.h> +#include <grub/file.h> +#include <grub/disk.h> +#include <grub/err.h> +#include <grub/misc.h> +#include <grub/types.h> +#include <grub/dl.h> +#include <grub/mm.h> +#include <grub/term.h> +#include <grub/cpu/linux.h> +#include <grub/video.h> +#include <grub/video_fb.h> +#include <grub/command.h> +#include <grub/i386/relocator.h> +#include <grub/i18n.h> +#include <grub/lib/cmdline.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +#ifdef GRUB_MACHINE_PCBIOS +#include <grub/i386/pc/vesa_modes_table.h> +#endif + +#ifdef GRUB_MACHINE_EFI +#include <grub/efi/efi.h> +#define HAS_VGA_TEXT 0 +#define DEFAULT_VIDEO_MODE "auto" +#elif defined (GRUB_MACHINE_IEEE1275) +#include <grub/ieee1275/ieee1275.h> +#define HAS_VGA_TEXT 0 +#define DEFAULT_VIDEO_MODE "text" +#else +#include <grub/i386/pc/vbe.h> +#include <grub/i386/pc/console.h> +#define HAS_VGA_TEXT 1 +#define DEFAULT_VIDEO_MODE "text" +#endif + +#define GRUB_LINUX_CL_OFFSET 0x1000 +#define GRUB_LINUX_CL_END_OFFSET 0x2000 + +static grub_dl_t my_mod; + +static grub_size_t linux_mem_size; +static int loaded; +static void *real_mode_mem; +static grub_addr_t real_mode_target; +static void *prot_mode_mem; +static grub_addr_t prot_mode_target; +static void *initrd_mem; +static grub_addr_t initrd_mem_target; +static grub_uint32_t real_mode_pages; +static grub_uint32_t prot_mode_pages; +static grub_uint32_t initrd_pages; +static struct grub_relocator *relocator = NULL; +static void *efi_mmap_buf; +#ifdef GRUB_MACHINE_EFI +static grub_efi_uintn_t efi_mmap_size; +#else +static const grub_size_t efi_mmap_size = 0; +#endif + +/* FIXME */ +#if 0 +struct idt_descriptor +{ + grub_uint16_t limit; + void *base; +} __attribute__ ((packed)); + +static struct idt_descriptor idt_desc = + { + 0, + 0 + }; +#endif + +static inline grub_size_t +page_align (grub_size_t size) +{ + return (size + (1 << 12) - 1) & (~((1 << 12) - 1)); +} + +#ifdef GRUB_MACHINE_EFI +/* Find the optimal number of pages for the memory map. Is it better to + move this code to efi/mm.c? */ +static grub_efi_uintn_t +find_efi_mmap_size (void) +{ + static grub_efi_uintn_t mmap_size = 0; + + if (mmap_size != 0) + return mmap_size; + + mmap_size = (1 << 12); + while (1) + { + int ret; + grub_efi_memory_descriptor_t *mmap; + grub_efi_uintn_t desc_size; + + mmap = grub_malloc (mmap_size); + if (! mmap) + return 0; + + ret = grub_efi_get_memory_map (&mmap_size, mmap, 0, &desc_size, 0); + grub_free (mmap); + + if (ret < 0) + grub_fatal ("cannot get memory map"); + else if (ret > 0) + break; + + mmap_size += (1 << 12); + } + + /* Increase the size a bit for safety, because GRUB allocates more on + later, and EFI itself may allocate more. */ + mmap_size += (1 << 12); + + mmap_size = page_align (mmap_size); + return mmap_size; +} + +#endif + +/* Find the optimal number of pages for the memory map. */ +static grub_size_t +find_mmap_size (void) +{ + grub_size_t count = 0, mmap_size; + + auto int NESTED_FUNC_ATTR hook (grub_uint64_t, grub_uint64_t, + grub_memory_type_t); + int NESTED_FUNC_ATTR hook (grub_uint64_t addr __attribute__ ((unused)), + grub_uint64_t size __attribute__ ((unused)), + grub_memory_type_t type __attribute__ ((unused))) + { + count++; + return 0; + } + + grub_mmap_iterate (hook); + + mmap_size = count * sizeof (struct grub_e820_mmap); + + /* Increase the size a bit for safety, because GRUB allocates more on + later. */ + mmap_size += (1 << 12); + + return page_align (mmap_size); +} + +static void +free_pages (void) +{ + grub_relocator_unload (relocator); + relocator = NULL; + real_mode_mem = prot_mode_mem = initrd_mem = 0; + real_mode_target = prot_mode_target = initrd_mem_target = 0; +} + +/* Allocate pages for the real mode code and the protected mode code + for linux as well as a memory map buffer. */ +static grub_err_t +allocate_pages (grub_size_t prot_size) +{ + grub_size_t real_size, mmap_size; + grub_err_t err; + + /* Make sure that each size is aligned to a page boundary. */ + real_size = GRUB_LINUX_CL_END_OFFSET; + prot_size = page_align (prot_size); + mmap_size = find_mmap_size (); + +#ifdef GRUB_MACHINE_EFI + efi_mmap_size = find_efi_mmap_size (); +#endif + + grub_dprintf ("linux", "real_size = %x, prot_size = %x, mmap_size = %x\n", + (unsigned) real_size, (unsigned) prot_size, (unsigned) mmap_size); + + /* Calculate the number of pages; Combine the real mode code with + the memory map buffer for simplicity. */ + real_mode_pages = ((real_size + mmap_size + efi_mmap_size) >> 12); + prot_mode_pages = (prot_size >> 12); + + /* Initialize the memory pointers with NULL for convenience. */ + free_pages (); + + relocator = grub_relocator_new (); + if (!relocator) + { + err = grub_errno; + goto fail; + } + + /* FIXME: Should request low memory from the heap when this feature is + implemented. */ + + auto int NESTED_FUNC_ATTR hook (grub_uint64_t, grub_uint64_t, + grub_memory_type_t); + int NESTED_FUNC_ATTR hook (grub_uint64_t addr, grub_uint64_t size, + grub_memory_type_t type) + { + /* We must put real mode code in the traditional space. */ + + if (type == GRUB_MEMORY_AVAILABLE + && addr <= 0x90000) + { + if (addr < 0x10000) + { + size += addr - 0x10000; + addr = 0x10000; + } + + if (addr + size > 0x90000) + size = 0x90000 - addr; + + if (real_size + mmap_size + efi_mmap_size > size) + return 0; + + real_mode_target = ((addr + size) - (real_size + mmap_size + efi_mmap_size)); + return 1; + } + + return 0; + } + grub_mmap_iterate (hook); + if (! real_mode_target) + { + err = grub_error (GRUB_ERR_OUT_OF_MEMORY, "cannot allocate real mode pages"); + goto fail; + } + + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_addr (relocator, &ch, + real_mode_target, + (real_size + mmap_size + + efi_mmap_size)); + if (err) + goto fail; + real_mode_mem = get_virtual_current_address (ch); + } + efi_mmap_buf = (grub_uint8_t *) real_mode_mem + real_size + mmap_size; + + prot_mode_target = GRUB_LINUX_BZIMAGE_ADDR; + + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_addr (relocator, &ch, + prot_mode_target, prot_size); + if (err) + goto fail; + prot_mode_mem = get_virtual_current_address (ch); + } + + grub_dprintf ("linux", "real_mode_mem = %lx, real_mode_pages = %x, " + "prot_mode_mem = %lx, prot_mode_pages = %x\n", + (unsigned long) real_mode_mem, (unsigned) real_mode_pages, + (unsigned long) prot_mode_mem, (unsigned) prot_mode_pages); + + return GRUB_ERR_NONE; + + fail: + free_pages (); + return err; +} + +static void +grub_e820_add_region (struct grub_e820_mmap *e820_map, int *e820_num, + grub_uint64_t start, grub_uint64_t size, + grub_uint32_t type) +{ + int n = *e820_num; + + if (n >= GRUB_E820_MAX_ENTRY) + grub_fatal ("Too many e820 memory map entries"); + + if ((n > 0) && (e820_map[n - 1].addr + e820_map[n - 1].size == start) && + (e820_map[n - 1].type == type)) + e820_map[n - 1].size += size; + else + { + e820_map[n].addr = start; + e820_map[n].size = size; + e820_map[n].type = type; + (*e820_num)++; + } +} + +static grub_err_t +grub_linux_setup_video (struct linux_kernel_params *params) +{ + struct grub_video_mode_info mode_info; + void *framebuffer; + grub_err_t err; + grub_video_driver_id_t driver_id; + char *gfxlfbvar = grub_env_get ("gfxpayloadforcelfb"); + + driver_id = grub_video_get_driver_id (); + + if (driver_id == GRUB_VIDEO_DRIVER_NONE) + return 1; + + err = grub_video_get_info_and_fini (&mode_info, &framebuffer); + + if (err) + { + grub_errno = GRUB_ERR_NONE; + return 1; + } + + params->lfb_width = mode_info.width; + params->lfb_height = mode_info.height; + params->lfb_depth = mode_info.bpp; + params->lfb_line_len = mode_info.pitch; + + params->lfb_base = (grub_size_t) framebuffer; + params->lfb_size = ALIGN_UP (params->lfb_line_len * params->lfb_height, 65536); + + params->red_mask_size = mode_info.red_mask_size; + params->red_field_pos = mode_info.red_field_pos; + params->green_mask_size = mode_info.green_mask_size; + params->green_field_pos = mode_info.green_field_pos; + params->blue_mask_size = mode_info.blue_mask_size; + params->blue_field_pos = mode_info.blue_field_pos; + params->reserved_mask_size = mode_info.reserved_mask_size; + params->reserved_field_pos = mode_info.reserved_field_pos; + + if (gfxlfbvar && (gfxlfbvar[0] == '1' || gfxlfbvar[0] == 'y')) + params->have_vga = GRUB_VIDEO_LINUX_TYPE_SIMPLE; + else + { + switch (driver_id) + { + case GRUB_VIDEO_DRIVER_VBE: + params->lfb_size >>= 16; + params->have_vga = GRUB_VIDEO_LINUX_TYPE_VESA; + break; + + case GRUB_VIDEO_DRIVER_EFI_UGA: + case GRUB_VIDEO_DRIVER_EFI_GOP: + params->have_vga = GRUB_VIDEO_LINUX_TYPE_EFIFB; + break; + + /* FIXME: check if better id is available. */ + case GRUB_VIDEO_DRIVER_SM712: + case GRUB_VIDEO_DRIVER_VGA: + case GRUB_VIDEO_DRIVER_CIRRUS: + case GRUB_VIDEO_DRIVER_BOCHS: + /* Make gcc happy. */ + case GRUB_VIDEO_DRIVER_SDL: + case GRUB_VIDEO_DRIVER_NONE: + params->have_vga = GRUB_VIDEO_LINUX_TYPE_SIMPLE; + break; + } + } + +#ifdef GRUB_MACHINE_PCBIOS + /* VESA packed modes may come with zeroed mask sizes, which need + to be set here according to DAC Palette width. If we don't, + this results in Linux displaying a black screen. */ + if (driver_id == GRUB_VIDEO_DRIVER_VBE && mode_info.bpp <= 8) + { + struct grub_vbe_info_block controller_info; + int status; + int width = 8; + + status = grub_vbe_bios_get_controller_info (&controller_info); + + if (status == GRUB_VBE_STATUS_OK && + (controller_info.capabilities & GRUB_VBE_CAPABILITY_DACWIDTH)) + status = grub_vbe_bios_set_dac_palette_width (&width); + + if (status != GRUB_VBE_STATUS_OK) + /* 6 is default after mode reset. */ + width = 6; + + params->red_mask_size = params->green_mask_size + = params->blue_mask_size = width; + params->reserved_mask_size = 0; + } +#endif + + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_linux_boot (void) +{ + struct linux_kernel_params *params; + int e820_num; + grub_err_t err = 0; + char *modevar, *tmp; + struct grub_relocator32_state state; + + params = real_mode_mem; + +#ifdef GRUB_MACHINE_IEEE1275 + { + char *bootpath; + grub_ssize_t len; + + bootpath = grub_env_get ("root"); + if (bootpath) + grub_ieee1275_set_property (grub_ieee1275_chosen, + "bootpath", bootpath, + grub_strlen (bootpath) + 1, + &len); + } +#endif + + grub_dprintf ("linux", "code32_start = %x\n", + (unsigned) params->code32_start); + + auto int NESTED_FUNC_ATTR hook (grub_uint64_t, grub_uint64_t, + grub_memory_type_t); + int NESTED_FUNC_ATTR hook (grub_uint64_t addr, grub_uint64_t size, + grub_memory_type_t type) + { + switch (type) + { + case GRUB_MEMORY_AVAILABLE: + grub_e820_add_region (params->e820_map, &e820_num, + addr, size, GRUB_E820_RAM); + break; + + case GRUB_MEMORY_ACPI: + grub_e820_add_region (params->e820_map, &e820_num, + addr, size, GRUB_E820_ACPI); + break; + + case GRUB_MEMORY_NVS: + grub_e820_add_region (params->e820_map, &e820_num, + addr, size, GRUB_E820_NVS); + break; + + case GRUB_MEMORY_BADRAM: + grub_e820_add_region (params->e820_map, &e820_num, + addr, size, GRUB_E820_BADRAM); + break; + + default: + grub_e820_add_region (params->e820_map, &e820_num, + addr, size, GRUB_E820_RESERVED); + } + return 0; + } + + e820_num = 0; + grub_mmap_iterate (hook); + params->mmap_size = e820_num; + + modevar = grub_env_get ("gfxpayload"); + + /* Now all graphical modes are acceptable. + May change in future if we have modes without framebuffer. */ + if (modevar && *modevar != 0) + { + tmp = grub_xasprintf ("%s;" DEFAULT_VIDEO_MODE, modevar); + if (! tmp) + return grub_errno; + err = grub_video_set_mode (tmp, 0, 0); + grub_free (tmp); + } + else + err = grub_video_set_mode (DEFAULT_VIDEO_MODE, 0, 0); + + if (err) + { + grub_print_error (); + grub_printf ("Booting however\n"); + grub_errno = GRUB_ERR_NONE; + } + + if (grub_linux_setup_video (params)) + { +#if defined (GRUB_MACHINE_PCBIOS) || defined (GRUB_MACHINE_COREBOOT) || defined (GRUB_MACHINE_QEMU) + params->have_vga = GRUB_VIDEO_LINUX_TYPE_TEXT; + params->video_mode = 0x3; +#else + params->have_vga = 0; + params->video_mode = 0; + params->video_width = 0; + params->video_height = 0; +#endif + } + + /* Initialize these last, because terminal position could be affected by printfs above. */ +#ifndef GRUB_MACHINE_IEEE1275 + if (params->have_vga == GRUB_VIDEO_LINUX_TYPE_TEXT) +#endif + { + grub_term_output_t term; + int found = 0; + FOR_ACTIVE_TERM_OUTPUTS(term) + if (grub_strcmp (term->name, "vga_text") == 0 + || grub_strcmp (term->name, "console") == 0 + || grub_strcmp (term->name, "ofconsole") == 0) + { + grub_uint16_t pos = grub_term_getxy (term); + params->video_cursor_x = pos >> 8; + params->video_cursor_y = pos & 0xff; + params->video_width = grub_term_width (term); + params->video_height = grub_term_height (term); + found = 1; + break; + } + if (!found) + { + params->video_cursor_x = 0; + params->video_cursor_y = 0; + params->video_width = 80; + params->video_height = 25; + } + } + +#ifdef GRUB_MACHINE_IEEE1275 + { + params->ofw_signature = GRUB_LINUX_OFW_SIGNATURE; + params->ofw_num_items = 1; + params->ofw_cif_handler = (grub_uint32_t) grub_ieee1275_entry_fn; + params->ofw_idt = 0; + } +#endif + +#ifdef GRUB_MACHINE_EFI + { + grub_efi_uintn_t efi_desc_size; + grub_size_t efi_mmap_target; + grub_efi_uint32_t efi_desc_version; + err = grub_efi_finish_boot_services (&efi_mmap_size, efi_mmap_buf, NULL, + &efi_desc_size, &efi_desc_version); + if (err) + return err; + + /* Note that no boot services are available from here. */ + efi_mmap_target = real_mode_target + + ((grub_uint8_t *) efi_mmap_buf - (grub_uint8_t *) real_mode_mem); + /* Pass EFI parameters. */ + if (grub_le_to_cpu16 (params->version) >= 0x0206) + { + params->v0206.efi_mem_desc_size = efi_desc_size; + params->v0206.efi_mem_desc_version = efi_desc_version; + params->v0206.efi_mmap = efi_mmap_target; + params->v0206.efi_mmap_size = efi_mmap_size; +#ifdef __x86_64__ + params->v0206.efi_mmap_hi = (efi_mmap_target >> 32); +#endif + } + else if (grub_le_to_cpu16 (params->version) >= 0x0204) + { + params->v0204.efi_mem_desc_size = efi_desc_size; + params->v0204.efi_mem_desc_version = efi_desc_version; + params->v0204.efi_mmap = efi_mmap_target; + params->v0204.efi_mmap_size = efi_mmap_size; + } + } +#endif + + /* FIXME. */ + /* asm volatile ("lidt %0" : : "m" (idt_desc)); */ + state.ebp = state.edi = state.ebx = 0; + state.esi = real_mode_target; + state.esp = real_mode_target; + state.eip = params->code32_start; + return grub_relocator32_boot (relocator, state); +} + +static grub_err_t +grub_linux_unload (void) +{ + grub_dl_unref (my_mod); + loaded = 0; + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_file_t file = 0; + struct linux_kernel_header lh; + struct linux_kernel_params *params; + grub_uint8_t setup_sects; + grub_size_t real_size, prot_size; + grub_ssize_t len; + int i; + + grub_dl_ref (my_mod); + + if (argc == 0) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, "no kernel specified"); + goto fail; + } + + file = grub_file_open (argv[0]); + if (! file) + goto fail; + + if (grub_file_read (file, &lh, sizeof (lh)) != sizeof (lh)) + { + grub_error (GRUB_ERR_READ_ERROR, "cannot read the Linux header"); + goto fail; + } + + if (lh.boot_flag != grub_cpu_to_le16 (0xaa55)) + { + grub_error (GRUB_ERR_BAD_OS, "invalid magic number"); + goto fail; + } + + if (lh.setup_sects > GRUB_LINUX_MAX_SETUP_SECTS) + { + grub_error (GRUB_ERR_BAD_OS, "too many setup sectors"); + goto fail; + } + + if (! (lh.loadflags & GRUB_LINUX_FLAG_BIG_KERNEL)) + { + grub_error (GRUB_ERR_BAD_OS, "zImage doesn't support 32-bit boot" +#ifdef GRUB_MACHINE_PCBIOS + " (try with `linux16')" +#endif + ); + goto fail; + } + + /* FIXME: 2.03 is not always good enough (Linux 2.4 can be 2.03 and + still not support 32-bit boot. */ + if (lh.header != grub_cpu_to_le32 (GRUB_LINUX_MAGIC_SIGNATURE) + || grub_le_to_cpu16 (lh.version) < 0x0203) + { + grub_error (GRUB_ERR_BAD_OS, "version too old for 32-bit boot" +#ifdef GRUB_MACHINE_PCBIOS + " (try with `linux16')" +#endif + ); + goto fail; + } + + setup_sects = lh.setup_sects; + + /* If SETUP_SECTS is not set, set it to the default (4). */ + if (! setup_sects) + setup_sects = GRUB_LINUX_DEFAULT_SETUP_SECTS; + + real_size = setup_sects << GRUB_DISK_SECTOR_BITS; + prot_size = grub_file_size (file) - real_size - GRUB_DISK_SECTOR_SIZE; + + if (allocate_pages (prot_size)) + goto fail; + + params = (struct linux_kernel_params *) real_mode_mem; + grub_memset (params, 0, GRUB_LINUX_CL_END_OFFSET); + grub_memcpy (¶ms->setup_sects, &lh.setup_sects, sizeof (lh) - 0x1F1); + + params->ps_mouse = params->padding10 = 0; + + len = 0x400 - sizeof (lh); + if (grub_file_read (file, (char *) real_mode_mem + sizeof (lh), len) != len) + { + grub_error (GRUB_ERR_FILE_READ_ERROR, "couldn't read file"); + goto fail; + } + + params->type_of_loader = GRUB_LINUX_BOOT_LOADER_TYPE; + + /* These two are used (instead of cmd_line_ptr) by older versions of Linux, + and otherwise ignored. */ + params->cl_magic = GRUB_LINUX_CL_MAGIC; + params->cl_offset = 0x1000; + + params->cmd_line_ptr = real_mode_target + 0x1000; + params->ramdisk_image = 0; + params->ramdisk_size = 0; + + params->heap_end_ptr = GRUB_LINUX_HEAP_END_OFFSET; + params->loadflags |= GRUB_LINUX_FLAG_CAN_USE_HEAP; + + /* These are not needed to be precise, because Linux uses these values + only to raise an error when the decompression code cannot find good + space. */ + params->ext_mem = ((32 * 0x100000) >> 10); + params->alt_mem = ((32 * 0x100000) >> 10); + + /* Ignored by Linux. */ + params->video_page = 0; + + /* Only used when `video_mode == 0x7', otherwise ignored. */ + params->video_ega_bx = 0; + + params->font_size = 16; /* XXX */ + +#ifdef GRUB_MACHINE_EFI + if (grub_le_to_cpu16 (params->version) >= 0x0206) + { + params->v0206.efi_signature = GRUB_LINUX_EFI_SIGNATURE; + params->v0206.efi_system_table = (grub_uint32_t) (unsigned long) grub_efi_system_table; +#ifdef __x86_64__ + params->v0206.efi_system_table_hi = (grub_uint32_t) ((grub_uint64_t) grub_efi_system_table >> 32); +#endif + } + else if (grub_le_to_cpu16 (params->version) >= 0x0204) + { + params->v0204.efi_signature = GRUB_LINUX_EFI_SIGNATURE_0204; + params->v0204.efi_system_table = (grub_uint32_t) (unsigned long) grub_efi_system_table; + } +#endif + + /* The other parameters are filled when booting. */ + + grub_file_seek (file, real_size + GRUB_DISK_SECTOR_SIZE); + + grub_dprintf ("linux", "bzImage, setup=0x%x, size=0x%x\n", + (unsigned) real_size, (unsigned) prot_size); + + /* Look for memory size and video mode specified on the command line. */ + linux_mem_size = 0; + for (i = 1; i < argc; i++) +#ifdef GRUB_MACHINE_PCBIOS + if (grub_memcmp (argv[i], "vga=", 4) == 0) + { + /* Video mode selection support. */ + char *val = argv[i] + 4; + unsigned vid_mode = GRUB_LINUX_VID_MODE_NORMAL; + struct grub_vesa_mode_table_entry *linux_mode; + grub_err_t err; + char *buf; + + grub_dl_load ("vbe"); + + if (grub_strcmp (val, "normal") == 0) + vid_mode = GRUB_LINUX_VID_MODE_NORMAL; + else if (grub_strcmp (val, "ext") == 0) + vid_mode = GRUB_LINUX_VID_MODE_EXTENDED; + else if (grub_strcmp (val, "ask") == 0) + { + grub_printf ("Legacy `ask' parameter no longer supported.\n"); + + /* We usually would never do this in a loader, but "vga=ask" means user + requested interaction, so it can't hurt to request keyboard input. */ + grub_wait_after_message (); + + goto fail; + } + else + vid_mode = (grub_uint16_t) grub_strtoul (val, 0, 0); + + switch (vid_mode) + { + case 0: + case GRUB_LINUX_VID_MODE_NORMAL: + grub_env_set ("gfxpayload", "text"); + grub_printf ("%s is deprecated. " + "Use set gfxpayload=text before " + "linux command instead.\n", + argv[i]); + break; + + case 1: + case GRUB_LINUX_VID_MODE_EXTENDED: + /* FIXME: support 80x50 text. */ + grub_env_set ("gfxpayload", "text"); + grub_printf ("%s is deprecated. " + "Use set gfxpayload=text before " + "linux command instead.\n", + argv[i]); + break; + default: + /* Ignore invalid values. */ + if (vid_mode < GRUB_VESA_MODE_TABLE_START || + vid_mode > GRUB_VESA_MODE_TABLE_END) + { + grub_env_set ("gfxpayload", "text"); + grub_printf ("%s is deprecated. Mode %d isn't recognized. " + "Use set gfxpayload=WIDTHxHEIGHT[xDEPTH] before " + "linux command instead.\n", + argv[i], vid_mode); + break; + } + + linux_mode = &grub_vesa_mode_table[vid_mode + - GRUB_VESA_MODE_TABLE_START]; + + buf = grub_xasprintf ("%ux%ux%u,%ux%u", + linux_mode->width, linux_mode->height, + linux_mode->depth, + linux_mode->width, linux_mode->height); + if (! buf) + goto fail; + + grub_printf ("%s is deprecated. " + "Use set gfxpayload=%s before " + "linux command instead.\n", + argv[i], buf); + err = grub_env_set ("gfxpayload", buf); + grub_free (buf); + if (err) + goto fail; + } + } + else +#endif /* GRUB_MACHINE_PCBIOS */ + if (grub_memcmp (argv[i], "mem=", 4) == 0) + { + char *val = argv[i] + 4; + + linux_mem_size = grub_strtoul (val, &val, 0); + + if (grub_errno) + { + grub_errno = GRUB_ERR_NONE; + linux_mem_size = 0; + } + else + { + int shift = 0; + + switch (grub_tolower (val[0])) + { + case 'g': + shift += 10; + case 'm': + shift += 10; + case 'k': + shift += 10; + default: + break; + } + + /* Check an overflow. */ + if (linux_mem_size > (~0UL >> shift)) + linux_mem_size = 0; + else + linux_mem_size <<= shift; + } + } + else if (grub_memcmp (argv[i], "quiet", sizeof ("quiet") - 1) == 0) + { + params->loadflags |= GRUB_LINUX_FLAG_QUIET; + } + + /* Create kernel command line. */ + grub_memcpy ((char *)real_mode_mem + GRUB_LINUX_CL_OFFSET, LINUX_IMAGE, + sizeof (LINUX_IMAGE)); + grub_create_loader_cmdline (argc, argv, + (char *)real_mode_mem + GRUB_LINUX_CL_OFFSET + + sizeof (LINUX_IMAGE) - 1, + GRUB_LINUX_CL_END_OFFSET - GRUB_LINUX_CL_OFFSET + - (sizeof (LINUX_IMAGE) - 1)); + + len = prot_size; + if (grub_file_read (file, prot_mode_mem, len) != len) + grub_error (GRUB_ERR_FILE_READ_ERROR, "couldn't read file"); + + if (grub_errno == GRUB_ERR_NONE) + { + grub_loader_set (grub_linux_boot, grub_linux_unload, + 0 /* set noreturn=0 in order to avoid grub_console_fini() */); + loaded = 1; + } + + fail: + + if (file) + grub_file_close (file); + + if (grub_errno != GRUB_ERR_NONE) + { + grub_dl_unref (my_mod); + loaded = 0; + } + + return grub_errno; +} + +static grub_err_t +grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_file_t file = 0; + grub_ssize_t size; + grub_addr_t addr_min, addr_max; + grub_addr_t addr; + grub_err_t err; + struct linux_kernel_header *lh; + + if (argc == 0) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, "no module specified"); + goto fail; + } + + if (! loaded) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, "you need to load the kernel first"); + goto fail; + } + + grub_file_filter_disable_compression (); + file = grub_file_open (argv[0]); + if (! file) + goto fail; + + size = grub_file_size (file); + initrd_pages = (page_align (size) >> 12); + + lh = (struct linux_kernel_header *) real_mode_mem; + + /* Get the highest address available for the initrd. */ + if (grub_le_to_cpu16 (lh->version) >= 0x0203) + { + addr_max = grub_cpu_to_le32 (lh->initrd_addr_max); + + /* XXX in reality, Linux specifies a bogus value, so + it is necessary to make sure that ADDR_MAX does not exceed + 0x3fffffff. */ + if (addr_max > GRUB_LINUX_INITRD_MAX_ADDRESS) + addr_max = GRUB_LINUX_INITRD_MAX_ADDRESS; + } + else + addr_max = GRUB_LINUX_INITRD_MAX_ADDRESS; + + if (linux_mem_size != 0 && linux_mem_size < addr_max) + addr_max = linux_mem_size; + + /* Linux 2.3.xx has a bug in the memory range check, so avoid + the last page. + Linux 2.2.xx has a bug in the memory range check, which is + worse than that of Linux 2.3.xx, so avoid the last 64kb. */ + addr_max -= 0x10000; + + /* Usually, the compression ratio is about 50%. */ + addr_min = (grub_addr_t) prot_mode_target + ((prot_mode_pages * 3) << 12) + + page_align (size); + + /* Put the initrd as high as possible, 4KiB aligned. */ + addr = (addr_max - size) & ~0xFFF; + + if (addr < addr_min) + { + grub_error (GRUB_ERR_OUT_OF_RANGE, "the initrd is too big"); + goto fail; + } + + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_align (relocator, &ch, + addr_min, addr, size, 0x1000, + GRUB_RELOCATOR_PREFERENCE_HIGH); + if (err) + return err; + initrd_mem = get_virtual_current_address (ch); + initrd_mem_target = get_physical_target_address (ch); + } + + if (grub_file_read (file, initrd_mem, size) != size) + { + grub_error (GRUB_ERR_FILE_READ_ERROR, "couldn't read file"); + goto fail; + } + + grub_dprintf ("linux", "Initrd, addr=0x%x, size=0x%x\n", + (unsigned) addr, (unsigned) size); + + lh->ramdisk_image = initrd_mem_target; + lh->ramdisk_size = size; + lh->root_dev = 0x0100; /* XXX */ + + fail: + if (file) + grub_file_close (file); + + return grub_errno; +} + +static grub_command_t cmd_linux, cmd_initrd; + +GRUB_MOD_INIT(linux) +{ + cmd_linux = grub_register_command ("linux", grub_cmd_linux, + 0, N_("Load Linux.")); + cmd_initrd = grub_register_command ("initrd", grub_cmd_initrd, + 0, N_("Load initrd.")); + my_mod = mod; +} + +GRUB_MOD_FINI(linux) +{ + grub_unregister_command (cmd_linux); + grub_unregister_command (cmd_initrd); +} diff --git a/grub-core/loader/i386/multiboot_mbi.c b/grub-core/loader/i386/multiboot_mbi.c new file mode 100644 index 0000000..bef5342 --- /dev/null +++ b/grub-core/loader/i386/multiboot_mbi.c @@ -0,0 +1,731 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 1999,2000,2001,2002,2003,2004,2005,2007,2008,2009 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <grub/memory.h> +#ifdef GRUB_MACHINE_PCBIOS +#include <grub/machine/biosnum.h> +#include <grub/machine/apm.h> +#include <grub/machine/memory.h> +#endif +#include <grub/multiboot.h> +#include <grub/cpu/relocator.h> +#include <grub/disk.h> +#include <grub/device.h> +#include <grub/partition.h> +#include <grub/mm.h> +#include <grub/misc.h> +#include <grub/env.h> +#include <grub/relocator.h> +#include <grub/video.h> +#include <grub/file.h> + +/* The bits in the required part of flags field we don't support. */ +#define UNSUPPORTED_FLAGS 0x0000fff8 + +struct module +{ + struct module *next; + grub_addr_t start; + grub_size_t size; + char *cmdline; + int cmdline_size; +}; + +static struct module *modules, *modules_last; +static grub_size_t cmdline_size; +static grub_size_t total_modcmd; +static unsigned modcnt; +static char *cmdline = NULL; +static grub_uint32_t bootdev; +static int bootdev_set; +static grub_size_t elf_sec_num, elf_sec_entsize; +static unsigned elf_sec_shstrndx; +static void *elf_sections; + + +grub_err_t +grub_multiboot_load (grub_file_t file) +{ + char *buffer; + grub_ssize_t len; + struct multiboot_header *header; + grub_err_t err; + + buffer = grub_malloc (MULTIBOOT_SEARCH); + if (!buffer) + return grub_errno; + + len = grub_file_read (file, buffer, MULTIBOOT_SEARCH); + if (len < 32) + { + grub_free (buffer); + return grub_error (GRUB_ERR_BAD_OS, "file too small"); + } + + /* Look for the multiboot header in the buffer. The header should + be at least 12 bytes and aligned on a 4-byte boundary. */ + for (header = (struct multiboot_header *) buffer; + ((char *) header <= buffer + len - 12) || (header = 0); + header = (struct multiboot_header *) ((char *) header + MULTIBOOT_HEADER_ALIGN)) + { + if (header->magic == MULTIBOOT_HEADER_MAGIC + && !(header->magic + header->flags + header->checksum)) + break; + } + + if (header == 0) + { + grub_free (buffer); + return grub_error (GRUB_ERR_BAD_ARGUMENT, "no multiboot header found"); + } + + if (header->flags & UNSUPPORTED_FLAGS) + { + grub_free (buffer); + return grub_error (GRUB_ERR_UNKNOWN_OS, + "unsupported flag: 0x%x", header->flags); + } + + if (header->flags & MULTIBOOT_AOUT_KLUDGE) + { + int offset = ((char *) header - buffer - + (header->header_addr - header->load_addr)); + int load_size = ((header->load_end_addr == 0) ? file->size - offset : + header->load_end_addr - header->load_addr); + grub_size_t code_size; + void *source; + grub_relocator_chunk_t ch; + + if (header->bss_end_addr) + code_size = (header->bss_end_addr - header->load_addr); + else + code_size = load_size; + + err = grub_relocator_alloc_chunk_addr (grub_multiboot_relocator, + &ch, header->load_addr, + code_size); + if (err) + { + grub_dprintf ("multiboot_loader", "Error loading aout kludge\n"); + grub_free (buffer); + return err; + } + source = get_virtual_current_address (ch); + + if ((grub_file_seek (file, offset)) == (grub_off_t) -1) + { + grub_free (buffer); + return grub_errno; + } + + grub_file_read (file, source, load_size); + if (grub_errno) + { + grub_free (buffer); + return grub_errno; + } + + if (header->bss_end_addr) + grub_memset ((grub_uint8_t *) source + load_size, 0, + header->bss_end_addr - header->load_addr - load_size); + + grub_multiboot_payload_eip = header->entry_addr; + } + else + { + err = grub_multiboot_load_elf (file, buffer); + if (err) + { + grub_free (buffer); + return err; + } + } + + if (header->flags & MULTIBOOT_VIDEO_MODE) + { + switch (header->mode_type) + { + case 1: + err = grub_multiboot_set_console (GRUB_MULTIBOOT_CONSOLE_EGA_TEXT, + GRUB_MULTIBOOT_CONSOLE_EGA_TEXT + | GRUB_MULTIBOOT_CONSOLE_FRAMEBUFFER, + 0, 0, 0, 0); + break; + case 0: + err = grub_multiboot_set_console (GRUB_MULTIBOOT_CONSOLE_FRAMEBUFFER, + GRUB_MULTIBOOT_CONSOLE_EGA_TEXT + | GRUB_MULTIBOOT_CONSOLE_FRAMEBUFFER, + header->width, header->height, + header->depth, 0); + break; + default: + err = grub_error (GRUB_ERR_BAD_OS, + "unsupported graphical mode type %d", + header->mode_type); + break; + } + } + else + err = grub_multiboot_set_console (GRUB_MULTIBOOT_CONSOLE_EGA_TEXT, + GRUB_MULTIBOOT_CONSOLE_EGA_TEXT, + 0, 0, 0, 0); + return err; +} + +#if GRUB_MACHINE_HAS_VBE || GRUB_MACHINE_HAS_VGA_TEXT +#include <grub/i386/pc/vbe.h> +#endif + +static grub_size_t +grub_multiboot_get_mbi_size (void) +{ + return sizeof (struct multiboot_info) + ALIGN_UP (cmdline_size, 4) + + modcnt * sizeof (struct multiboot_mod_list) + total_modcmd + + ALIGN_UP (sizeof(PACKAGE_STRING), 4) + + grub_get_multiboot_mmap_count () * sizeof (struct multiboot_mmap_entry) + + elf_sec_entsize * elf_sec_num + + 256 * sizeof (struct multiboot_color) +#if GRUB_MACHINE_HAS_VBE || GRUB_MACHINE_HAS_VGA_TEXT + + sizeof (struct grub_vbe_info_block) + + sizeof (struct grub_vbe_mode_info_block) +#endif + + ALIGN_UP (sizeof (struct multiboot_apm_info), 4); +} + +/* Fill previously allocated Multiboot mmap. */ +static void +grub_fill_multiboot_mmap (struct multiboot_mmap_entry *first_entry) +{ + struct multiboot_mmap_entry *mmap_entry = (struct multiboot_mmap_entry *) first_entry; + + auto int NESTED_FUNC_ATTR hook (grub_uint64_t, grub_uint64_t, + grub_memory_type_t); + int NESTED_FUNC_ATTR hook (grub_uint64_t addr, grub_uint64_t size, + grub_memory_type_t type) + { + mmap_entry->addr = addr; + mmap_entry->len = size; + switch (type) + { + case GRUB_MEMORY_AVAILABLE: + mmap_entry->type = MULTIBOOT_MEMORY_AVAILABLE; + break; + + case GRUB_MEMORY_ACPI: + mmap_entry->type = MULTIBOOT_MEMORY_ACPI_RECLAIMABLE; + break; + + case GRUB_MEMORY_NVS: + mmap_entry->type = MULTIBOOT_MEMORY_NVS; + break; + + case GRUB_MEMORY_BADRAM: + mmap_entry->type = MULTIBOOT_MEMORY_BADRAM; + break; + + default: + mmap_entry->type = MULTIBOOT_MEMORY_RESERVED; + break; + } + mmap_entry->size = sizeof (struct multiboot_mmap_entry) - sizeof (mmap_entry->size); + mmap_entry++; + + return 0; + } + + grub_mmap_iterate (hook); +} + +#if GRUB_MACHINE_HAS_VBE || GRUB_MACHINE_HAS_VGA_TEXT + +static grub_err_t +fill_vbe_info (struct multiboot_info *mbi, grub_uint8_t *ptrorig, + grub_uint32_t ptrdest, int fill_generic) +{ + grub_uint32_t vbe_mode; + struct grub_vbe_mode_info_block *mode_info; +#if GRUB_MACHINE_HAS_VBE + grub_vbe_status_t status; + void *scratch = (void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR; + + status = grub_vbe_bios_get_controller_info (scratch); + if (status != GRUB_VBE_STATUS_OK) + return grub_error (GRUB_ERR_IO, "Can't get controller info."); + + mbi->vbe_control_info = ptrdest; + grub_memcpy (ptrorig, scratch, sizeof (struct grub_vbe_info_block)); + ptrorig += sizeof (struct grub_vbe_info_block); + ptrdest += sizeof (struct grub_vbe_info_block); +#else + mbi->vbe_control_info = 0; +#endif + +#if GRUB_MACHINE_HAS_VBE + status = grub_vbe_bios_get_mode (scratch); + vbe_mode = *(grub_uint32_t *) scratch; + if (status != GRUB_VBE_STATUS_OK) + return grub_error (GRUB_ERR_IO, "can't get VBE mode"); +#else + vbe_mode = 3; +#endif + mbi->vbe_mode = vbe_mode; + + mode_info = (struct grub_vbe_mode_info_block *) ptrorig; + mbi->vbe_mode_info = ptrdest; + /* get_mode_info isn't available for mode 3. */ + if (vbe_mode == 3) + { + grub_memset (mode_info, 0, sizeof (struct grub_vbe_mode_info_block)); + mode_info->memory_model = GRUB_VBE_MEMORY_MODEL_TEXT; + mode_info->x_resolution = 80; + mode_info->y_resolution = 25; + } + else + { +#if GRUB_MACHINE_HAS_VBE + status = grub_vbe_bios_get_mode_info (vbe_mode, scratch); + if (status != GRUB_VBE_STATUS_OK) + return grub_error (GRUB_ERR_IO, "can't get mode info"); + grub_memcpy (mode_info, scratch, + sizeof (struct grub_vbe_mode_info_block)); +#endif + } + ptrorig += sizeof (struct grub_vbe_mode_info_block); + ptrdest += sizeof (struct grub_vbe_mode_info_block); + +#if GRUB_MACHINE_HAS_VBE + grub_vbe_bios_get_pm_interface (&mbi->vbe_interface_seg, + &mbi->vbe_interface_off, + &mbi->vbe_interface_len); +#endif + + mbi->flags |= MULTIBOOT_INFO_VBE_INFO; + + if (fill_generic && mode_info->memory_model == GRUB_VBE_MEMORY_MODEL_TEXT) + { + mbi->framebuffer_addr = 0xb8000; + + mbi->framebuffer_pitch = 2 * mode_info->x_resolution; + mbi->framebuffer_width = mode_info->x_resolution; + mbi->framebuffer_height = mode_info->y_resolution; + + mbi->framebuffer_bpp = 16; + + mbi->framebuffer_type = MULTIBOOT_FRAMEBUFFER_TYPE_EGA_TEXT; + + mbi->flags |= MULTIBOOT_INFO_FRAMEBUFFER_INFO; + } + + return GRUB_ERR_NONE; +} +#endif + +static grub_err_t +retrieve_video_parameters (struct multiboot_info *mbi, + grub_uint8_t *ptrorig, grub_uint32_t ptrdest) +{ + grub_err_t err; + struct grub_video_mode_info mode_info; + void *framebuffer; + grub_video_driver_id_t driv_id; + struct grub_video_palette_data palette[256]; + + err = grub_multiboot_set_video_mode (); + if (err) + { + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + } + + grub_video_get_palette (0, ARRAY_SIZE (palette), palette); + + driv_id = grub_video_get_driver_id (); +#if GRUB_MACHINE_HAS_VGA_TEXT + if (driv_id == GRUB_VIDEO_DRIVER_NONE) + return fill_vbe_info (mbi, ptrorig, ptrdest, 1); +#else + if (driv_id == GRUB_VIDEO_DRIVER_NONE) + return GRUB_ERR_NONE; +#endif + + err = grub_video_get_info_and_fini (&mode_info, &framebuffer); + if (err) + return err; + + mbi->framebuffer_addr = (grub_addr_t) framebuffer; + mbi->framebuffer_pitch = mode_info.pitch; + + mbi->framebuffer_width = mode_info.width; + mbi->framebuffer_height = mode_info.height; + + mbi->framebuffer_bpp = mode_info.bpp; + + if (mode_info.mode_type & GRUB_VIDEO_MODE_TYPE_INDEX_COLOR) + { + struct multiboot_color *mb_palette; + unsigned i; + mbi->framebuffer_type = MULTIBOOT_FRAMEBUFFER_TYPE_INDEXED; + mbi->framebuffer_palette_addr = ptrdest; + mbi->framebuffer_palette_num_colors = mode_info.number_of_colors; + if (mbi->framebuffer_palette_num_colors > ARRAY_SIZE (palette)) + mbi->framebuffer_palette_num_colors = ARRAY_SIZE (palette); + mb_palette = (struct multiboot_color *) ptrorig; + for (i = 0; i < mbi->framebuffer_palette_num_colors; i++) + { + mb_palette[i].red = palette[i].r; + mb_palette[i].green = palette[i].g; + mb_palette[i].blue = palette[i].b; + } + ptrorig += mbi->framebuffer_palette_num_colors + * sizeof (struct multiboot_color); + ptrdest += mbi->framebuffer_palette_num_colors + * sizeof (struct multiboot_color); + } + else + { + mbi->framebuffer_type = MULTIBOOT_FRAMEBUFFER_TYPE_RGB; + mbi->framebuffer_red_field_position = mode_info.red_field_pos; + mbi->framebuffer_red_mask_size = mode_info.red_mask_size; + mbi->framebuffer_green_field_position = mode_info.green_field_pos; + mbi->framebuffer_green_mask_size = mode_info.green_mask_size; + mbi->framebuffer_blue_field_position = mode_info.blue_field_pos; + mbi->framebuffer_blue_mask_size = mode_info.blue_mask_size; + } + + mbi->flags |= MULTIBOOT_INFO_FRAMEBUFFER_INFO; + +#if GRUB_MACHINE_HAS_VBE + if (driv_id == GRUB_VIDEO_DRIVER_VBE) + return fill_vbe_info (mbi, ptrorig, ptrdest, 0); +#endif + + return GRUB_ERR_NONE; +} + +grub_err_t +grub_multiboot_make_mbi (grub_uint32_t *target) +{ + struct multiboot_info *mbi; + struct multiboot_mod_list *modlist; + unsigned i; + struct module *cur; + grub_size_t mmap_size; + grub_uint8_t *ptrorig; + grub_addr_t ptrdest; + + grub_err_t err; + grub_size_t bufsize; + grub_relocator_chunk_t ch; + + bufsize = grub_multiboot_get_mbi_size (); + + err = grub_relocator_alloc_chunk_align (grub_multiboot_relocator, &ch, + 0x10000, 0x100000 - bufsize, + bufsize, 4, + GRUB_RELOCATOR_PREFERENCE_NONE); + if (err) + return err; + ptrorig = get_virtual_current_address (ch); + ptrdest = get_physical_target_address (ch); + + *target = ptrdest; + + mbi = (struct multiboot_info *) ptrorig; + ptrorig += sizeof (*mbi); + ptrdest += sizeof (*mbi); + grub_memset (mbi, 0, sizeof (*mbi)); + + grub_memcpy (ptrorig, cmdline, cmdline_size); + mbi->flags |= MULTIBOOT_INFO_CMDLINE; + mbi->cmdline = ptrdest; + ptrorig += ALIGN_UP (cmdline_size, 4); + ptrdest += ALIGN_UP (cmdline_size, 4); + + grub_memcpy (ptrorig, PACKAGE_STRING, sizeof(PACKAGE_STRING)); + mbi->flags |= MULTIBOOT_INFO_BOOT_LOADER_NAME; + mbi->boot_loader_name = ptrdest; + ptrorig += ALIGN_UP (sizeof(PACKAGE_STRING), 4); + ptrdest += ALIGN_UP (sizeof(PACKAGE_STRING), 4); + +#ifdef GRUB_MACHINE_PCBIOS + { + struct grub_apm_info info; + if (grub_apm_get_info (&info)) + { + struct multiboot_apm_info *mbinfo = (void *) ptrorig; + + mbinfo->cseg = info.cseg; + mbinfo->offset = info.offset; + mbinfo->cseg_16 = info.cseg_16; + mbinfo->dseg = info.dseg; + mbinfo->flags = info.flags; + mbinfo->cseg_len = info.cseg_len; + mbinfo->dseg_len = info.dseg_len; + mbinfo->cseg_16_len = info.cseg_16_len; + mbinfo->version = info.version; + + ptrorig += ALIGN_UP (sizeof (struct multiboot_apm_info), 4); + ptrdest += ALIGN_UP (sizeof (struct multiboot_apm_info), 4); + } + } +#endif + + if (modcnt) + { + mbi->flags |= MULTIBOOT_INFO_MODS; + mbi->mods_addr = ptrdest; + mbi->mods_count = modcnt; + modlist = (struct multiboot_mod_list *) ptrorig; + ptrorig += modcnt * sizeof (struct multiboot_mod_list); + ptrdest += modcnt * sizeof (struct multiboot_mod_list); + + for (i = 0, cur = modules; i < modcnt; i++, cur = cur->next) + { + modlist[i].mod_start = cur->start; + modlist[i].mod_end = modlist[i].mod_start + cur->size; + modlist[i].cmdline = ptrdest; + grub_memcpy (ptrorig, cur->cmdline, cur->cmdline_size); + ptrorig += ALIGN_UP (cur->cmdline_size, 4); + ptrdest += ALIGN_UP (cur->cmdline_size, 4); + } + } + else + { + mbi->mods_addr = 0; + mbi->mods_count = 0; + } + + mmap_size = grub_get_multiboot_mmap_count () + * sizeof (struct multiboot_mmap_entry); + grub_fill_multiboot_mmap ((struct multiboot_mmap_entry *) ptrorig); + mbi->mmap_length = mmap_size; + mbi->mmap_addr = ptrdest; + mbi->flags |= MULTIBOOT_INFO_MEM_MAP; + ptrorig += mmap_size; + ptrdest += mmap_size; + + /* Convert from bytes to kilobytes. */ + mbi->mem_lower = grub_mmap_get_lower () / 1024; + mbi->mem_upper = grub_mmap_get_upper () / 1024; + mbi->flags |= MULTIBOOT_INFO_MEMORY; + + if (bootdev_set) + { + mbi->boot_device = bootdev; + mbi->flags |= MULTIBOOT_INFO_BOOTDEV; + } + + if (elf_sec_num) + { + mbi->u.elf_sec.addr = ptrdest; + grub_memcpy (ptrorig, elf_sections, elf_sec_entsize * elf_sec_num); + mbi->u.elf_sec.num = elf_sec_num; + mbi->u.elf_sec.size = elf_sec_entsize; + mbi->u.elf_sec.shndx = elf_sec_shstrndx; + + mbi->flags |= MULTIBOOT_INFO_ELF_SHDR; + + ptrorig += elf_sec_entsize * elf_sec_num; + ptrdest += elf_sec_entsize * elf_sec_num; + } + + err = retrieve_video_parameters (mbi, ptrorig, ptrdest); + if (err) + { + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + } + + if ((mbi->flags & MULTIBOOT_INFO_FRAMEBUFFER_INFO) + && mbi->framebuffer_type == MULTIBOOT_FRAMEBUFFER_TYPE_INDEXED) + { + ptrorig += mbi->framebuffer_palette_num_colors + * sizeof (struct multiboot_color); + ptrdest += mbi->framebuffer_palette_num_colors + * sizeof (struct multiboot_color); + } + +#if GRUB_MACHINE_HAS_VBE + ptrorig += sizeof (struct grub_vbe_info_block); + ptrdest += sizeof (struct grub_vbe_info_block); + ptrorig += sizeof (struct grub_vbe_mode_info_block); + ptrdest += sizeof (struct grub_vbe_mode_info_block); +#endif + + return GRUB_ERR_NONE; +} + +void +grub_multiboot_add_elfsyms (grub_size_t num, grub_size_t entsize, + unsigned shndx, void *data) +{ + elf_sec_num = num; + elf_sec_shstrndx = shndx; + elf_sec_entsize = entsize; + elf_sections = data; +} + +void +grub_multiboot_free_mbi (void) +{ + struct module *cur, *next; + + cmdline_size = 0; + total_modcmd = 0; + modcnt = 0; + grub_free (cmdline); + cmdline = NULL; + bootdev_set = 0; + + for (cur = modules; cur; cur = next) + { + next = cur->next; + grub_free (cur->cmdline); + grub_free (cur); + } + modules = NULL; + modules_last = NULL; + + grub_free (elf_sections); + elf_sections = NULL; + elf_sec_entsize = 0; + elf_sec_num = 0; +} + +grub_err_t +grub_multiboot_init_mbi (int argc, char *argv[]) +{ + grub_ssize_t len = 0; + char *p; + int i; + + grub_multiboot_free_mbi (); + + for (i = 0; i < argc; i++) + len += grub_strlen (argv[i]) + 1; + if (len == 0) + len = 1; + + cmdline = p = grub_malloc (len); + if (! cmdline) + return grub_errno; + cmdline_size = len; + + for (i = 0; i < argc; i++) + { + p = grub_stpcpy (p, argv[i]); + *(p++) = ' '; + } + + /* Remove the space after the last word. */ + if (p != cmdline) + p--; + *p = '\0'; + + return GRUB_ERR_NONE; +} + +grub_err_t +grub_multiboot_add_module (grub_addr_t start, grub_size_t size, + int argc, char *argv[]) +{ + struct module *newmod; + char *p; + grub_ssize_t len = 0; + int i; + + newmod = grub_malloc (sizeof (*newmod)); + if (!newmod) + return grub_errno; + newmod->start = start; + newmod->size = size; + newmod->next = 0; + + for (i = 0; i < argc; i++) + len += grub_strlen (argv[i]) + 1; + + if (len == 0) + len = 1; + + newmod->cmdline = p = grub_malloc (len); + if (! newmod->cmdline) + { + grub_free (newmod); + return grub_errno; + } + newmod->cmdline_size = len; + total_modcmd += ALIGN_UP (len, 4); + + for (i = 0; i < argc; i++) + { + p = grub_stpcpy (p, argv[i]); + *(p++) = ' '; + } + + /* Remove the space after the last word. */ + if (p != newmod->cmdline) + p--; + *p = '\0'; + + if (modules_last) + modules_last->next = newmod; + else + { + modules = newmod; + modules_last->next = NULL; + } + modules_last = newmod; + + modcnt++; + + return GRUB_ERR_NONE; +} + +void +grub_multiboot_set_bootdev (void) +{ + grub_uint32_t biosdev, slice = ~0, part = ~0; + grub_device_t dev; + +#ifdef GRUB_MACHINE_PCBIOS + biosdev = grub_get_root_biosnumber (); +#else + biosdev = 0xffffffff; +#endif + + if (biosdev == 0xffffffff) + return; + + dev = grub_device_open (0); + if (dev && dev->disk && dev->disk->partition) + { + if (dev->disk->partition->parent) + { + part = dev->disk->partition->number; + slice = dev->disk->partition->parent->number; + } + else + slice = dev->disk->partition->number; + } + if (dev) + grub_device_close (dev); + + bootdev = ((biosdev & 0xff) << 24) | ((slice & 0xff) << 16) + | ((part & 0xff) << 8) | 0xff; + bootdev_set = 1; +} diff --git a/grub-core/loader/i386/pc/chainloader.c b/grub-core/loader/i386/pc/chainloader.c new file mode 100644 index 0000000..794316b --- /dev/null +++ b/grub-core/loader/i386/pc/chainloader.c @@ -0,0 +1,177 @@ +/* chainloader.c - boot another boot loader */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2002,2004,2007,2009,2010 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <grub/loader.h> +#include <grub/machine/loader.h> +#include <grub/machine/chainloader.h> +#include <grub/machine/memory.h> +#include <grub/file.h> +#include <grub/err.h> +#include <grub/device.h> +#include <grub/disk.h> +#include <grub/misc.h> +#include <grub/types.h> +#include <grub/partition.h> +#include <grub/memory.h> +#include <grub/dl.h> +#include <grub/command.h> +#include <grub/msdos_partition.h> +#include <grub/machine/biosnum.h> +#include <grub/cpu/floppy.h> +#include <grub/i18n.h> +#include <grub/video.h> +#include <grub/mm.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +static grub_dl_t my_mod; +static int boot_drive; +static void *boot_part_addr; + +static grub_err_t +grub_chainloader_boot (void) +{ + grub_video_set_mode ("text", 0, 0); + grub_chainloader_real_boot (boot_drive, boot_part_addr); + + /* Never reach here. */ + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_chainloader_unload (void) +{ + grub_dl_unref (my_mod); + return GRUB_ERR_NONE; +} + +static void +grub_chainloader_cmd (const char *filename, grub_chainloader_flags_t flags) +{ + grub_file_t file = 0; + grub_uint16_t signature; + grub_device_t dev; + int drive = -1; + void *part_addr = 0; + + grub_dl_ref (my_mod); + + grub_file_filter_disable_compression (); + file = grub_file_open (filename); + if (! file) + goto fail; + + /* Read the first block. */ + if (grub_file_read (file, (void *) 0x7C00, GRUB_DISK_SECTOR_SIZE) + != GRUB_DISK_SECTOR_SIZE) + { + if (grub_errno == GRUB_ERR_NONE) + grub_error (GRUB_ERR_BAD_OS, "too small"); + + goto fail; + } + + /* Check the signature. */ + signature = *((grub_uint16_t *) (0x7C00 + GRUB_DISK_SECTOR_SIZE - 2)); + if (signature != grub_le_to_cpu16 (0xaa55) + && ! (flags & GRUB_CHAINLOADER_FORCE)) + { + grub_error (GRUB_ERR_BAD_OS, "invalid signature"); + goto fail; + } + + grub_file_close (file); + + /* Obtain the partition table from the root device. */ + drive = grub_get_root_biosnumber (); + dev = grub_device_open (0); + if (dev && dev->disk && dev->disk->partition) + { + grub_disk_t disk = dev->disk; + + if (disk) + { + grub_partition_t p = disk->partition; + + if (p && grub_strcmp (p->partmap->name, "msdos") == 0) + { + disk->partition = p->parent; + grub_disk_read (disk, p->offset, 446, 64, + (void *) GRUB_MEMORY_MACHINE_PART_TABLE_ADDR); + part_addr = (void *) (GRUB_MEMORY_MACHINE_PART_TABLE_ADDR + + (p->index << 4)); + disk->partition = p; + } + } + } + + if (dev) + grub_device_close (dev); + + /* Ignore errors. Perhaps it's not fatal. */ + grub_errno = GRUB_ERR_NONE; + + boot_drive = drive; + boot_part_addr = part_addr; + + grub_loader_set (grub_chainloader_boot, grub_chainloader_unload, 1); + return; + + fail: + + if (file) + grub_file_close (file); + + grub_dl_unref (my_mod); +} + +static grub_err_t +grub_cmd_chainloader (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_chainloader_flags_t flags = 0; + + if (argc > 0 && grub_strcmp (argv[0], "--force") == 0) + { + flags |= GRUB_CHAINLOADER_FORCE; + argc--; + argv++; + } + + if (argc == 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "no file specified"); + else + grub_chainloader_cmd (argv[0], flags); + + return grub_errno; +} + +static grub_command_t cmd; + +GRUB_MOD_INIT(chainloader) +{ + cmd = grub_register_command ("chainloader", grub_cmd_chainloader, + 0, N_("Load another boot loader.")); + my_mod = mod; +} + +GRUB_MOD_FINI(chainloader) +{ + grub_unregister_command (cmd); +} diff --git a/grub-core/loader/i386/pc/linux.c b/grub-core/loader/i386/pc/linux.c new file mode 100644 index 0000000..c6e6b67 --- /dev/null +++ b/grub-core/loader/i386/pc/linux.c @@ -0,0 +1,450 @@ +/* linux.c - boot Linux zImage or bzImage */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 1999,2000,2001,2002,2003,2004,2005,2007,2008,2009,2010 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <grub/loader.h> +#include <grub/machine/loader.h> +#include <grub/file.h> +#include <grub/err.h> +#include <grub/device.h> +#include <grub/disk.h> +#include <grub/misc.h> +#include <grub/types.h> +#include <grub/memory.h> +#include <grub/dl.h> +#include <grub/cpu/linux.h> +#include <grub/command.h> +#include <grub/i18n.h> +#include <grub/mm.h> +#include <grub/cpu/relocator.h> +#include <grub/video.h> +#include <grub/i386/floppy.h> +#include <grub/lib/cmdline.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +#define GRUB_LINUX_CL_OFFSET 0x9000 +#define GRUB_LINUX_CL_END_OFFSET 0x90FF + +static grub_dl_t my_mod; + +static grub_size_t linux_mem_size; +static int loaded; +static struct grub_relocator *relocator = NULL; +static grub_addr_t grub_linux_real_target; +static char *grub_linux_real_chunk; +static grub_size_t grub_linux16_prot_size; + +static grub_err_t +grub_linux16_boot (void) +{ + grub_uint16_t segment; + struct grub_relocator16_state state; + + segment = grub_linux_real_target >> 4; + state.gs = state.fs = state.es = state.ds = state.ss = segment; + state.sp = GRUB_LINUX_SETUP_STACK; + state.cs = segment + 0x20; + state.ip = 0; + + grub_video_set_mode ("text", 0, 0); + + grub_stop_floppy (); + + return grub_relocator16_boot (relocator, state); +} + +static grub_err_t +grub_linux_unload (void) +{ + grub_dl_unref (my_mod); + loaded = 0; + grub_relocator_unload (relocator); + relocator = NULL; + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_file_t file = 0; + struct linux_kernel_header lh; + grub_uint8_t setup_sects; + grub_size_t real_size; + grub_ssize_t len; + int i; + char *grub_linux_prot_chunk; + int grub_linux_is_bzimage; + grub_addr_t grub_linux_prot_target; + grub_err_t err; + + grub_dl_ref (my_mod); + + if (argc == 0) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, "no kernel specified"); + goto fail; + } + + file = grub_file_open (argv[0]); + if (! file) + goto fail; + + if (grub_file_read (file, &lh, sizeof (lh)) != sizeof (lh)) + { + grub_error (GRUB_ERR_READ_ERROR, "cannot read the Linux header"); + goto fail; + } + + if (lh.boot_flag != grub_cpu_to_le16 (0xaa55)) + { + grub_error (GRUB_ERR_BAD_OS, "invalid magic number"); + goto fail; + } + + if (lh.setup_sects > GRUB_LINUX_MAX_SETUP_SECTS) + { + grub_error (GRUB_ERR_BAD_OS, "too many setup sectors"); + goto fail; + } + + grub_linux_is_bzimage = 0; + setup_sects = lh.setup_sects; + linux_mem_size = 0; + + if (lh.header == grub_cpu_to_le32 (GRUB_LINUX_MAGIC_SIGNATURE) + && grub_le_to_cpu16 (lh.version) >= 0x0200) + { + grub_linux_is_bzimage = (lh.loadflags & GRUB_LINUX_FLAG_BIG_KERNEL); + lh.type_of_loader = GRUB_LINUX_BOOT_LOADER_TYPE; + + /* Put the real mode part at as a high location as possible. */ + grub_linux_real_target = grub_mmap_get_lower () + - GRUB_LINUX_SETUP_MOVE_SIZE; + /* But it must not exceed the traditional area. */ + if (grub_linux_real_target > GRUB_LINUX_OLD_REAL_MODE_ADDR) + grub_linux_real_target = GRUB_LINUX_OLD_REAL_MODE_ADDR; + + if (grub_le_to_cpu16 (lh.version) >= 0x0201) + { + lh.heap_end_ptr = grub_cpu_to_le16 (GRUB_LINUX_HEAP_END_OFFSET); + lh.loadflags |= GRUB_LINUX_FLAG_CAN_USE_HEAP; + } + + if (grub_le_to_cpu16 (lh.version) >= 0x0202) + lh.cmd_line_ptr = grub_linux_real_target + GRUB_LINUX_CL_OFFSET; + else + { + lh.cl_magic = grub_cpu_to_le16 (GRUB_LINUX_CL_MAGIC); + lh.cl_offset = grub_cpu_to_le16 (GRUB_LINUX_CL_OFFSET); + lh.setup_move_size = grub_cpu_to_le16 (GRUB_LINUX_SETUP_MOVE_SIZE); + } + } + else + { + /* Your kernel is quite old... */ + lh.cl_magic = grub_cpu_to_le16 (GRUB_LINUX_CL_MAGIC); + lh.cl_offset = grub_cpu_to_le16 (GRUB_LINUX_CL_OFFSET); + + setup_sects = GRUB_LINUX_DEFAULT_SETUP_SECTS; + + grub_linux_real_target = GRUB_LINUX_OLD_REAL_MODE_ADDR; + } + + /* If SETUP_SECTS is not set, set it to the default (4). */ + if (! setup_sects) + setup_sects = GRUB_LINUX_DEFAULT_SETUP_SECTS; + + real_size = setup_sects << GRUB_DISK_SECTOR_BITS; + grub_linux16_prot_size = grub_file_size (file) + - real_size - GRUB_DISK_SECTOR_SIZE; + + if (! grub_linux_is_bzimage + && GRUB_LINUX_ZIMAGE_ADDR + grub_linux16_prot_size + > grub_linux_real_target) + { + grub_error (GRUB_ERR_BAD_OS, "too big zImage (0x%x > 0x%x), use bzImage instead", + (char *) GRUB_LINUX_ZIMAGE_ADDR + grub_linux16_prot_size, + (grub_size_t) grub_linux_real_target); + goto fail; + } + + if (grub_linux_real_target + GRUB_LINUX_SETUP_MOVE_SIZE + > grub_mmap_get_lower ()) + { + grub_error (GRUB_ERR_OUT_OF_RANGE, + "too small lower memory (0x%x > 0x%x)", + grub_linux_real_target + GRUB_LINUX_SETUP_MOVE_SIZE, + (int) grub_mmap_get_lower ()); + goto fail; + } + + grub_printf (" [Linux-%s, setup=0x%x, size=0x%x]\n", + grub_linux_is_bzimage ? "bzImage" : "zImage", real_size, + grub_linux16_prot_size); + + relocator = grub_relocator_new (); + if (!relocator) + goto fail; + + for (i = 1; i < argc; i++) + if (grub_memcmp (argv[i], "vga=", 4) == 0) + { + /* Video mode selection support. */ + grub_uint16_t vid_mode; + char *val = argv[i] + 4; + + if (grub_strcmp (val, "normal") == 0) + vid_mode = GRUB_LINUX_VID_MODE_NORMAL; + else if (grub_strcmp (val, "ext") == 0) + vid_mode = GRUB_LINUX_VID_MODE_EXTENDED; + else if (grub_strcmp (val, "ask") == 0) + vid_mode = GRUB_LINUX_VID_MODE_ASK; + else + vid_mode = (grub_uint16_t) grub_strtoul (val, 0, 0); + + if (grub_errno) + goto fail; + + lh.vid_mode = grub_cpu_to_le16 (vid_mode); + } + else if (grub_memcmp (argv[i], "mem=", 4) == 0) + { + char *val = argv[i] + 4; + + linux_mem_size = grub_strtoul (val, &val, 0); + + if (grub_errno) + { + grub_errno = GRUB_ERR_NONE; + linux_mem_size = 0; + } + else + { + int shift = 0; + + switch (grub_tolower (val[0])) + { + case 'g': + shift += 10; + case 'm': + shift += 10; + case 'k': + shift += 10; + default: + break; + } + + /* Check an overflow. */ + if (linux_mem_size > (~0UL >> shift)) + linux_mem_size = 0; + else + linux_mem_size <<= shift; + } + } + + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_addr (relocator, &ch, + grub_linux_real_target, + GRUB_LINUX_SETUP_MOVE_SIZE); + if (err) + return err; + grub_linux_real_chunk = get_virtual_current_address (ch); + } + + /* Put the real mode code at the temporary address. */ + grub_memmove (grub_linux_real_chunk, &lh, sizeof (lh)); + + len = real_size + GRUB_DISK_SECTOR_SIZE - sizeof (lh); + if (grub_file_read (file, grub_linux_real_chunk + sizeof (lh), len) != len) + { + grub_error (GRUB_ERR_FILE_READ_ERROR, "couldn't read file"); + goto fail; + } + + if (lh.header != grub_cpu_to_le32 (GRUB_LINUX_MAGIC_SIGNATURE) + || grub_le_to_cpu16 (lh.version) < 0x0200) + /* Clear the heap space. */ + grub_memset (grub_linux_real_chunk + + ((setup_sects + 1) << GRUB_DISK_SECTOR_BITS), + 0, + ((GRUB_LINUX_MAX_SETUP_SECTS - setup_sects - 1) + << GRUB_DISK_SECTOR_BITS)); + + /* Create kernel command line. */ + grub_memcpy ((char *)grub_linux_real_chunk + GRUB_LINUX_CL_OFFSET, + LINUX_IMAGE, sizeof (LINUX_IMAGE)); + grub_create_loader_cmdline (argc, argv, + (char *)grub_linux_real_chunk + + GRUB_LINUX_CL_OFFSET + sizeof (LINUX_IMAGE) - 1, + GRUB_LINUX_CL_END_OFFSET - GRUB_LINUX_CL_OFFSET + - (sizeof (LINUX_IMAGE) - 1)); + + if (grub_linux_is_bzimage) + grub_linux_prot_target = GRUB_LINUX_BZIMAGE_ADDR; + else + grub_linux_prot_target = GRUB_LINUX_ZIMAGE_ADDR; + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_addr (relocator, &ch, + grub_linux_prot_target, + grub_linux16_prot_size); + if (err) + return err; + grub_linux_prot_chunk = get_virtual_current_address (ch); + } + + len = grub_linux16_prot_size; + if (grub_file_read (file, grub_linux_prot_chunk, grub_linux16_prot_size) + != (grub_ssize_t) grub_linux16_prot_size) + grub_error (GRUB_ERR_FILE_READ_ERROR, "couldn't read file"); + + if (grub_errno == GRUB_ERR_NONE) + { + grub_loader_set (grub_linux16_boot, grub_linux_unload, 0); + loaded = 1; + } + + fail: + + if (file) + grub_file_close (file); + + if (grub_errno != GRUB_ERR_NONE) + { + grub_dl_unref (my_mod); + loaded = 0; + grub_relocator_unload (relocator); + } + + return grub_errno; +} + +static grub_err_t +grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_file_t file = 0; + grub_ssize_t size; + grub_addr_t addr_max, addr_min; + struct linux_kernel_header *lh; + grub_uint8_t *initrd_chunk; + grub_addr_t initrd_addr; + grub_err_t err; + + if (argc == 0) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, "no module specified"); + goto fail; + } + + if (!loaded) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, "you need to load the kernel first"); + goto fail; + } + + lh = (struct linux_kernel_header *) grub_linux_real_chunk; + + if (!(lh->header == grub_cpu_to_le32 (GRUB_LINUX_MAGIC_SIGNATURE) + && grub_le_to_cpu16 (lh->version) >= 0x0200)) + { + grub_error (GRUB_ERR_BAD_OS, "the kernel is too old for initrd"); + goto fail; + } + + /* Get the highest address available for the initrd. */ + if (grub_le_to_cpu16 (lh->version) >= 0x0203) + { + addr_max = grub_cpu_to_le32 (lh->initrd_addr_max); + + /* XXX in reality, Linux specifies a bogus value, so + it is necessary to make sure that ADDR_MAX does not exceed + 0x3fffffff. */ + if (addr_max > GRUB_LINUX_INITRD_MAX_ADDRESS) + addr_max = GRUB_LINUX_INITRD_MAX_ADDRESS; + } + else + addr_max = GRUB_LINUX_INITRD_MAX_ADDRESS; + + if (linux_mem_size != 0 && linux_mem_size < addr_max) + addr_max = linux_mem_size; + + /* Linux 2.3.xx has a bug in the memory range check, so avoid + the last page. + Linux 2.2.xx has a bug in the memory range check, which is + worse than that of Linux 2.3.xx, so avoid the last 64kb. */ + addr_max -= 0x10000; + + addr_min = GRUB_LINUX_BZIMAGE_ADDR + grub_linux16_prot_size; + + grub_file_filter_disable_compression (); + file = grub_file_open (argv[0]); + if (!file) + goto fail; + + size = grub_file_size (file); + + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_align (relocator, &ch, + addr_min, addr_max - size, + size, 0x1000, + GRUB_RELOCATOR_PREFERENCE_HIGH); + if (err) + return err; + initrd_chunk = get_virtual_current_address (ch); + initrd_addr = get_physical_target_address (ch); + } + + if (grub_file_read (file, initrd_chunk, size) != size) + { + grub_error (GRUB_ERR_FILE_READ_ERROR, "couldn't read file"); + goto fail; + } + + lh->ramdisk_image = initrd_addr; + lh->ramdisk_size = size; + + fail: + if (file) + grub_file_close (file); + + return grub_errno; +} + +static grub_command_t cmd_linux, cmd_initrd; + +GRUB_MOD_INIT(linux16) +{ + cmd_linux = + grub_register_command ("linux16", grub_cmd_linux, + 0, N_("Load Linux.")); + cmd_initrd = + grub_register_command ("initrd16", grub_cmd_initrd, + 0, N_("Load initrd.")); + my_mod = mod; +} + +GRUB_MOD_FINI(linux16) +{ + grub_unregister_command (cmd_linux); + grub_unregister_command (cmd_initrd); +} diff --git a/grub-core/loader/i386/pc/ntldr.c b/grub-core/loader/i386/pc/ntldr.c new file mode 100644 index 0000000..4a08b54 --- /dev/null +++ b/grub-core/loader/i386/pc/ntldr.c @@ -0,0 +1,159 @@ +/* chainloader.c - boot another boot loader */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2002,2004,2007,2009,2010 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <grub/loader.h> +#include <grub/file.h> +#include <grub/err.h> +#include <grub/device.h> +#include <grub/disk.h> +#include <grub/misc.h> +#include <grub/types.h> +#include <grub/partition.h> +#include <grub/dl.h> +#include <grub/command.h> +#include <grub/machine/biosnum.h> +#include <grub/i18n.h> +#include <grub/video.h> +#include <grub/mm.h> +#include <grub/cpu/relocator.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +static grub_dl_t my_mod; +static struct grub_relocator *rel; +static grub_uint32_t edx = 0xffffffff; + +#define GRUB_NTLDR_SEGMENT 0x2000 + +static grub_err_t +grub_ntldr_boot (void) +{ + struct grub_relocator16_state state = { + .cs = GRUB_NTLDR_SEGMENT, + .ip = 0, + .ds = 0, + .es = 0, + .fs = 0, + .gs = 0, + .ss = 0, + .sp = 0x7c00, + .edx = edx + }; + grub_video_set_mode ("text", 0, 0); + + return grub_relocator16_boot (rel, state); +} + +static grub_err_t +grub_ntldr_unload (void) +{ + grub_relocator_unload (rel); + rel = NULL; + grub_dl_unref (my_mod); + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_cmd_ntldr (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_file_t file = 0; + grub_err_t err; + void *bs, *ntldr; + grub_size_t ntldrsize; + grub_device_t dev; + + if (argc == 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "no file specified"); + + grub_dl_ref (my_mod); + + rel = grub_relocator_new (); + if (!rel) + goto fail; + + file = grub_file_open (argv[0]); + if (! file) + goto fail; + + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_addr (rel, &ch, 0x7C00, + GRUB_DISK_SECTOR_SIZE); + if (err) + goto fail; + bs = get_virtual_current_address (ch); + } + + edx = grub_get_root_biosnumber (); + dev = grub_device_open (0); + + if (dev && dev->disk) + { + err = grub_disk_read (dev->disk, 0, 0, GRUB_DISK_SECTOR_SIZE, bs); + if (err) + { + grub_device_close (dev); + goto fail; + } + } + + if (dev) + grub_device_close (dev); + + ntldrsize = grub_file_size (file); + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_addr (rel, &ch, GRUB_NTLDR_SEGMENT << 4, + ntldrsize); + if (err) + goto fail; + ntldr = get_virtual_current_address (ch); + } + + if (grub_file_read (file, ntldr, ntldrsize) + != (grub_ssize_t) ntldrsize) + goto fail; + + grub_loader_set (grub_ntldr_boot, grub_ntldr_unload, 1); + return GRUB_ERR_NONE; + + fail: + + if (file) + grub_file_close (file); + + grub_ntldr_unload (); + + return grub_errno; +} + +static grub_command_t cmd; + +GRUB_MOD_INIT(ntldr) +{ + cmd = grub_register_command ("ntldr", grub_cmd_ntldr, + 0, N_("Load NTLDR or BootMGR.")); + my_mod = mod; +} + +GRUB_MOD_FINI(ntldr) +{ + grub_unregister_command (cmd); +} diff --git a/grub-core/loader/i386/xnu.c b/grub-core/loader/i386/xnu.c new file mode 100644 index 0000000..b877b0e --- /dev/null +++ b/grub-core/loader/i386/xnu.c @@ -0,0 +1,1137 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2009 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <grub/env.h> +#include <grub/file.h> +#include <grub/disk.h> +#include <grub/xnu.h> +#include <grub/cpu/xnu.h> +#include <grub/mm.h> +#include <grub/loader.h> +#include <grub/autoefi.h> +#include <grub/i386/tsc.h> +#include <grub/efi/api.h> +#include <grub/i386/pit.h> +#include <grub/misc.h> +#include <grub/charset.h> +#include <grub/term.h> +#include <grub/command.h> +#include <grub/i18n.h> +#include <grub/bitmap_scale.h> + +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#define max(a,b) (((a) > (b)) ? (a) : (b)) + +#define DEFAULT_VIDEO_MODE "auto" + +char grub_xnu_cmdline[1024]; +grub_uint32_t grub_xnu_entry_point, grub_xnu_arg1, grub_xnu_stack; + +/* Aliases set for some tables. */ +struct tbl_alias +{ + grub_efi_guid_t guid; + char *name; +}; + +static struct tbl_alias table_aliases[] = + { + {GRUB_EFI_ACPI_20_TABLE_GUID, "ACPI_20"}, + {GRUB_EFI_ACPI_TABLE_GUID, "ACPI"}, + }; + +struct grub_xnu_devprop_device_descriptor +{ + struct grub_xnu_devprop_device_descriptor *next; + struct property_descriptor *properties; + struct grub_efi_device_path *path; + int pathlen; +}; + +static int +utf16_strlen (grub_uint16_t *in) +{ + int i; + for (i = 0; in[i]; i++); + return i; +} + +/* Read frequency from a string in MHz and return it in Hz. */ +static grub_uint64_t +readfrequency (const char *str) +{ + grub_uint64_t num = 0; + int mul = 1000000; + int found = 0; + + while (*str) + { + unsigned long digit; + + digit = grub_tolower (*str) - '0'; + if (digit > 9) + break; + + found = 1; + + num = num * 10 + digit; + str++; + } + num *= 1000000; + if (*str == '.') + { + str++; + while (*str) + { + unsigned long digit; + + digit = grub_tolower (*str) - '0'; + if (digit > 9) + break; + + found = 1; + + mul /= 10; + num = num + mul * digit; + str++; + } + } + if (! found) + return 0; + + return num; +} + +/* Thanks to Kabyl for precious information about Intel architecture. */ +static grub_uint64_t +guessfsb (void) +{ + const grub_uint64_t sane_value = 100000000; + grub_uint32_t manufacturer[3], max_cpuid, capabilities, msrlow; + grub_uint64_t start_tsc; + grub_uint64_t end_tsc; + grub_uint64_t tsc_ticks_per_ms; + + if (! grub_cpu_is_cpuid_supported ()) + return sane_value; + +#ifdef APPLE_CC + asm volatile ("movl $0, %%eax\n" +#ifdef __x86_64__ + "push %%rbx\n" +#else + "push %%ebx\n" +#endif + "cpuid\n" +#ifdef __x86_64__ + "pop %%rbx\n" +#else + "pop %%ebx\n" +#endif + : "=a" (max_cpuid), + "=d" (manufacturer[1]), "=c" (manufacturer[2])); + + /* Only Intel for now is done. */ + if (grub_memcmp (manufacturer + 1, "ineIntel", 12) != 0) + return sane_value; + +#else + asm volatile ("movl $0, %%eax\n" + "cpuid" + : "=a" (max_cpuid), "=b" (manufacturer[0]), + "=d" (manufacturer[1]), "=c" (manufacturer[2])); + + /* Only Intel for now is done. */ + if (grub_memcmp (manufacturer, "GenuineIntel", 12) != 0) + return sane_value; +#endif + + /* Check Speedstep. */ + if (max_cpuid < 1) + return sane_value; + +#ifdef APPLE_CC + asm volatile ("movl $1, %%eax\n" +#ifdef __x86_64__ + "push %%rbx\n" +#else + "push %%ebx\n" +#endif + "cpuid\n" +#ifdef __x86_64__ + "pop %%rbx\n" +#else + "pop %%ebx\n" +#endif + : "=c" (capabilities): + : "%rax", "%rdx"); +#else + asm volatile ("movl $1, %%eax\n" + "cpuid" + : "=c" (capabilities): + : "%rax", "%rbx", "%rdx"); +#endif + + if (! (capabilities & (1 << 7))) + return sane_value; + + /* Calibrate the TSC rate. */ + + start_tsc = grub_get_tsc (); + grub_pit_wait (0xffff); + end_tsc = grub_get_tsc (); + + tsc_ticks_per_ms = grub_divmod64 (end_tsc - start_tsc, 55, 0); + + /* Read the multiplier. */ + asm volatile ("movl $0x198, %%ecx\n" + "rdmsr" + : "=d" (msrlow) + : + : "%ecx", "%eax"); + + return grub_divmod64 (2000 * tsc_ticks_per_ms, + ((msrlow >> 7) & 0x3e) + ((msrlow >> 14) & 1), 0); +} + +struct property_descriptor +{ + struct property_descriptor *next; + grub_uint8_t *name; + grub_uint16_t *name16; + int name16len; + int length; + void *data; +}; + +static struct grub_xnu_devprop_device_descriptor *devices = 0; + +grub_err_t +grub_xnu_devprop_remove_property (struct grub_xnu_devprop_device_descriptor *dev, + char *name) +{ + struct property_descriptor *prop; + prop = grub_named_list_find (GRUB_AS_NAMED_LIST_P (&dev->properties), name); + if (!prop) + return GRUB_ERR_NONE; + + grub_free (prop->name); + grub_free (prop->name16); + grub_free (prop->data); + + grub_list_remove (GRUB_AS_LIST_P (&dev->properties), GRUB_AS_LIST (prop)); + + return GRUB_ERR_NONE; +} + +grub_err_t +grub_xnu_devprop_remove_device (struct grub_xnu_devprop_device_descriptor *dev) +{ + void *t; + struct property_descriptor *prop; + + grub_list_remove (GRUB_AS_LIST_P (&devices), GRUB_AS_LIST (dev)); + + for (prop = dev->properties; prop; ) + { + grub_free (prop->name); + grub_free (prop->name16); + grub_free (prop->data); + t = prop; + prop = prop->next; + grub_free (t); + } + + grub_free (dev->path); + grub_free (dev); + + return GRUB_ERR_NONE; +} + +struct grub_xnu_devprop_device_descriptor * +grub_xnu_devprop_add_device (struct grub_efi_device_path *path, int length) +{ + struct grub_xnu_devprop_device_descriptor *ret; + + ret = grub_zalloc (sizeof (*ret)); + if (!ret) + return 0; + + ret->path = grub_malloc (length); + if (!ret->path) + { + grub_free (ret); + return 0; + } + ret->pathlen = length; + grub_memcpy (ret->path, path, length); + + grub_list_push (GRUB_AS_LIST_P (&devices), GRUB_AS_LIST (ret)); + + return ret; +} + +static grub_err_t +grub_xnu_devprop_add_property (struct grub_xnu_devprop_device_descriptor *dev, + grub_uint8_t *utf8, grub_uint16_t *utf16, + int utf16len, void *data, int datalen) +{ + struct property_descriptor *prop; + + prop = grub_malloc (sizeof (*prop)); + if (!prop) + return grub_errno; + + prop->name = utf8; + prop->name16 = utf16; + prop->name16len = utf16len; + + prop->length = datalen; + prop->data = grub_malloc (prop->length); + if (!prop->data) + { + grub_free (prop); + grub_free (prop->name); + grub_free (prop->name16); + return grub_errno; + } + grub_memcpy (prop->data, data, prop->length); + grub_list_push (GRUB_AS_LIST_P (&dev->properties), + GRUB_AS_LIST (prop)); + return GRUB_ERR_NONE; +} + +grub_err_t +grub_xnu_devprop_add_property_utf8 (struct grub_xnu_devprop_device_descriptor *dev, + char *name, void *data, int datalen) +{ + grub_uint8_t *utf8; + grub_uint16_t *utf16; + int len, utf16len; + grub_err_t err; + + utf8 = (grub_uint8_t *) grub_strdup (name); + if (!utf8) + return grub_errno; + + len = grub_strlen (name); + utf16 = grub_malloc (sizeof (grub_uint16_t) * len); + if (!utf16) + { + grub_free (utf8); + return grub_errno; + } + + utf16len = grub_utf8_to_utf16 (utf16, len, utf8, len, NULL); + if (utf16len < 0) + { + grub_free (utf8); + grub_free (utf16); + return grub_errno; + } + + err = grub_xnu_devprop_add_property (dev, utf8, utf16, + utf16len, data, datalen); + if (err) + { + grub_free (utf8); + grub_free (utf16); + return err; + } + + return GRUB_ERR_NONE; +} + +grub_err_t +grub_xnu_devprop_add_property_utf16 (struct grub_xnu_devprop_device_descriptor *dev, + grub_uint16_t *name, int namelen, + void *data, int datalen) +{ + grub_uint8_t *utf8; + grub_uint16_t *utf16; + grub_err_t err; + + utf16 = grub_malloc (sizeof (grub_uint16_t) * namelen); + if (!utf16) + return grub_errno; + grub_memcpy (utf16, name, sizeof (grub_uint16_t) * namelen); + + utf8 = grub_malloc (namelen * 4 + 1); + if (!utf8) + { + grub_free (utf8); + return grub_errno; + } + + *grub_utf16_to_utf8 ((grub_uint8_t *) utf8, name, namelen) = '\0'; + + err = grub_xnu_devprop_add_property (dev, utf8, utf16, + namelen, data, datalen); + if (err) + { + grub_free (utf8); + grub_free (utf16); + return err; + } + + return GRUB_ERR_NONE; +} + +static inline int +hextoval (char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'a' && c <= 'z') + return c - 'a' + 10; + if (c >= 'A' && c <= 'Z') + return c - 'A' + 10; + return 0; +} + +void +grub_cpu_xnu_unload (void) +{ + struct grub_xnu_devprop_device_descriptor *dev1, *dev2; + + for (dev1 = devices; dev1; ) + { + dev2 = dev1->next; + grub_xnu_devprop_remove_device (dev1); + dev1 = dev2; + } +} + +static grub_err_t +grub_cpu_xnu_fill_devprop (void) +{ + struct grub_xnu_devtree_key *efikey; + int total_length = sizeof (struct grub_xnu_devprop_header); + struct grub_xnu_devtree_key *devprop; + struct grub_xnu_devprop_device_descriptor *device; + void *ptr; + struct grub_xnu_devprop_header *head; + void *t; + int numdevs = 0; + + /* The key "efi". */ + efikey = grub_xnu_create_key (&grub_xnu_devtree_root, "efi"); + if (! efikey) + return grub_errno; + + for (device = devices; device; device = device->next) + { + struct property_descriptor *propdesc; + total_length += sizeof (struct grub_xnu_devprop_device_header); + total_length += device->pathlen; + + for (propdesc = device->properties; propdesc; propdesc = propdesc->next) + { + total_length += sizeof (grub_uint32_t); + total_length += sizeof (grub_uint16_t) + * (propdesc->name16len + 1); + total_length += sizeof (grub_uint32_t); + total_length += propdesc->length; + } + numdevs++; + } + + devprop = grub_xnu_create_value (&(efikey->first_child), "device-properties"); + if (devprop) + { + devprop->data = grub_malloc (total_length); + devprop->datasize = total_length; + } + + ptr = devprop->data; + head = ptr; + ptr = head + 1; + head->length = total_length; + head->alwaysone = 1; + head->num_devices = numdevs; + for (device = devices; device; ) + { + struct grub_xnu_devprop_device_header *devhead; + struct property_descriptor *propdesc; + devhead = ptr; + devhead->num_values = 0; + ptr = devhead + 1; + + grub_memcpy (ptr, device->path, device->pathlen); + ptr = (char *) ptr + device->pathlen; + + for (propdesc = device->properties; propdesc; ) + { + grub_uint32_t *len; + grub_uint16_t *name; + void *data; + + len = ptr; + *len = 2 * propdesc->name16len + sizeof (grub_uint16_t) + + sizeof (grub_uint32_t); + ptr = len + 1; + + name = ptr; + grub_memcpy (name, propdesc->name16, 2 * propdesc->name16len); + name += propdesc->name16len; + + /* NUL terminator. */ + *name = 0; + ptr = name + 1; + + len = ptr; + *len = propdesc->length + sizeof (grub_uint32_t); + data = len + 1; + ptr = data; + grub_memcpy (ptr, propdesc->data, propdesc->length); + ptr = (char *) ptr + propdesc->length; + + grub_free (propdesc->name); + grub_free (propdesc->name16); + grub_free (propdesc->data); + t = propdesc; + propdesc = propdesc->next; + grub_free (t); + devhead->num_values++; + } + + devhead->length = (char *) ptr - (char *) devhead; + t = device; + device = device->next; + grub_free (t); + } + + devices = 0; + + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_cmd_devprop_load (grub_command_t cmd __attribute__ ((unused)), + int argc, char *args[]) +{ + grub_file_t file; + void *buf, *bufstart, *bufend; + struct grub_xnu_devprop_header *head; + grub_size_t size; + unsigned i, j; + + if (argc != 1) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "file name required"); + + file = grub_file_open (args[0]); + if (! file) + return grub_error (GRUB_ERR_FILE_NOT_FOUND, + "couldn't load device-propertie dump"); + size = grub_file_size (file); + buf = grub_malloc (size); + if (!buf) + { + grub_file_close (file); + return grub_errno; + } + if (grub_file_read (file, buf, size) != (grub_ssize_t) size) + { + grub_file_close (file); + return grub_errno; + } + grub_file_close (file); + + bufstart = buf; + bufend = (char *) buf + size; + head = buf; + buf = head + 1; + for (i = 0; i < grub_le_to_cpu32 (head->num_devices) && buf < bufend; i++) + { + struct grub_efi_device_path *dp, *dpstart; + struct grub_xnu_devprop_device_descriptor *dev; + struct grub_xnu_devprop_device_header *devhead; + + devhead = buf; + buf = devhead + 1; + dpstart = buf; + + do + { + dp = buf; + buf = (char *) buf + GRUB_EFI_DEVICE_PATH_LENGTH (dp); + } + while (!GRUB_EFI_END_ENTIRE_DEVICE_PATH (dp) && buf < bufend); + + dev = grub_xnu_devprop_add_device (dpstart, (char *) buf + - (char *) dpstart); + + for (j = 0; j < grub_le_to_cpu32 (devhead->num_values) && buf < bufend; + j++) + { + grub_uint32_t *namelen; + grub_uint32_t *datalen; + grub_uint16_t *utf16; + void *data; + grub_err_t err; + + namelen = buf; + buf = namelen + 1; + if (buf >= bufend) + break; + + utf16 = buf; + buf = (char *) buf + *namelen - sizeof (grub_uint32_t); + if (buf >= bufend) + break; + + datalen = buf; + buf = datalen + 1; + if (buf >= bufend) + break; + + data = buf; + buf = (char *) buf + *datalen - sizeof (grub_uint32_t); + if (buf >= bufend) + break; + err = grub_xnu_devprop_add_property_utf16 + (dev, utf16, (*namelen - sizeof (grub_uint32_t) + - sizeof (grub_uint16_t)) / sizeof (grub_uint16_t), + data, *datalen - sizeof (grub_uint32_t)); + if (err) + { + grub_free (bufstart); + return err; + } + } + } + + grub_free (bufstart); + return GRUB_ERR_NONE; +} + +/* Fill device tree. */ +/* FIXME: some entries may be platform-agnostic. Move them to loader/xnu.c. */ +grub_err_t +grub_cpu_xnu_fill_devicetree (void) +{ + struct grub_xnu_devtree_key *efikey; + struct grub_xnu_devtree_key *cfgtablekey; + struct grub_xnu_devtree_key *curval; + struct grub_xnu_devtree_key *runtimesrvkey; + struct grub_xnu_devtree_key *platformkey; + unsigned i, j; + + /* The value "model". */ + /* FIXME: may this value be sometimes different? */ + curval = grub_xnu_create_value (&grub_xnu_devtree_root, "model"); + if (! curval) + return grub_errno; + curval->datasize = sizeof ("ACPI"); + curval->data = grub_strdup ("ACPI"); + curval = grub_xnu_create_value (&grub_xnu_devtree_root, "compatible"); + if (! curval) + return grub_errno; + curval->datasize = sizeof ("ACPI"); + curval->data = grub_strdup ("ACPI"); + + /* The key "efi". */ + efikey = grub_xnu_create_key (&grub_xnu_devtree_root, "efi"); + if (! efikey) + return grub_errno; + + /* Information about firmware. */ + curval = grub_xnu_create_value (&(efikey->first_child), "firmware-revision"); + if (! curval) + return grub_errno; + curval->datasize = (SYSTEM_TABLE_SIZEOF (firmware_revision)); + curval->data = grub_malloc (curval->datasize); + if (! curval->data) + return grub_errno; + grub_memcpy (curval->data, (SYSTEM_TABLE_VAR(firmware_revision)), + curval->datasize); + + curval = grub_xnu_create_value (&(efikey->first_child), "firmware-vendor"); + if (! curval) + return grub_errno; + curval->datasize = + 2 * (utf16_strlen (SYSTEM_TABLE_PTR (firmware_vendor)) + 1); + curval->data = grub_malloc (curval->datasize); + if (! curval->data) + return grub_errno; + grub_memcpy (curval->data, SYSTEM_TABLE_PTR (firmware_vendor), + curval->datasize); + + curval = grub_xnu_create_value (&(efikey->first_child), "firmware-abi"); + if (! curval) + return grub_errno; + curval->datasize = sizeof ("EFI32"); + curval->data = grub_malloc (curval->datasize); + if (! curval->data) + return grub_errno; + if (SIZEOF_OF_UINTN == 4) + grub_memcpy (curval->data, "EFI32", curval->datasize); + else + grub_memcpy (curval->data, "EFI64", curval->datasize); + + /* The key "platform". */ + platformkey = grub_xnu_create_key (&(efikey->first_child), + "platform"); + if (! platformkey) + return grub_errno; + + /* Pass FSB frequency to the kernel. */ + curval = grub_xnu_create_value (&(platformkey->first_child), "FSBFrequency"); + if (! curval) + return grub_errno; + curval->datasize = sizeof (grub_uint64_t); + curval->data = grub_malloc (curval->datasize); + if (!curval->data) + return grub_errno; + + /* First see if user supplies the value. */ + char *fsbvar = grub_env_get ("fsb"); + if (! fsbvar) + *((grub_uint64_t *) curval->data) = 0; + else + *((grub_uint64_t *) curval->data) = readfrequency (fsbvar); + /* Try autodetect. */ + if (! *((grub_uint64_t *) curval->data)) + *((grub_uint64_t *) curval->data) = guessfsb (); + grub_dprintf ("xnu", "fsb autodetected as %llu\n", + (unsigned long long) *((grub_uint64_t *) curval->data)); + + cfgtablekey = grub_xnu_create_key (&(efikey->first_child), + "configuration-table"); + if (!cfgtablekey) + return grub_errno; + + /* Fill "configuration-table" key. */ + for (i = 0; i < SYSTEM_TABLE (num_table_entries); i++) + { + void *ptr; + struct grub_xnu_devtree_key *curkey; + grub_efi_guid_t guid; + char guidbuf[64]; + + /* Retrieve current key. */ +#ifdef GRUB_MACHINE_EFI + { + ptr = (void *) + grub_efi_system_table->configuration_table[i].vendor_table; + guid = grub_efi_system_table->configuration_table[i].vendor_guid; + } +#else + if (SIZEOF_OF_UINTN == 4) + { + ptr = UINT_TO_PTR (((grub_efiemu_configuration_table32_t *) + SYSTEM_TABLE_PTR (configuration_table))[i] + .vendor_table); + guid = + ((grub_efiemu_configuration_table32_t *) + SYSTEM_TABLE_PTR (configuration_table))[i].vendor_guid; + } + else + { + ptr = UINT_TO_PTR (((grub_efiemu_configuration_table64_t *) + SYSTEM_TABLE_PTR (configuration_table))[i] + .vendor_table); + guid = + ((grub_efiemu_configuration_table64_t *) + SYSTEM_TABLE_PTR (configuration_table))[i].vendor_guid; + } +#endif + + /* The name of key for new table. */ + grub_snprintf (guidbuf, sizeof (guidbuf), "%08x-%04x-%04x-%02x%02x-", + guid.data1, guid.data2, guid.data3, guid.data4[0], + guid.data4[1]); + for (j = 2; j < 8; j++) + grub_snprintf (guidbuf + grub_strlen (guidbuf), + sizeof (guidbuf) - grub_strlen (guidbuf), + "%02x", guid.data4[j]); + /* For some reason GUID has to be in uppercase. */ + for (j = 0; guidbuf[j] ; j++) + if (guidbuf[j] >= 'a' && guidbuf[j] <= 'f') + guidbuf[j] += 'A' - 'a'; + curkey = grub_xnu_create_key (&(cfgtablekey->first_child), guidbuf); + if (! curkey) + return grub_errno; + + curval = grub_xnu_create_value (&(curkey->first_child), "guid"); + if (! curval) + return grub_errno; + curval->datasize = sizeof (guid); + curval->data = grub_malloc (curval->datasize); + if (! curval->data) + return grub_errno; + grub_memcpy (curval->data, &guid, curval->datasize); + + /* The value "table". */ + curval = grub_xnu_create_value (&(curkey->first_child), "table"); + if (! curval) + return grub_errno; + curval->datasize = SIZEOF_OF_UINTN; + curval->data = grub_malloc (curval->datasize); + if (! curval->data) + return grub_errno; + if (SIZEOF_OF_UINTN == 4) + *((grub_uint32_t *)curval->data) = PTR_TO_UINT32 (ptr); + else + *((grub_uint64_t *)curval->data) = PTR_TO_UINT64 (ptr); + + /* Create alias. */ + for (j = 0; j < sizeof (table_aliases) / sizeof (table_aliases[0]); j++) + if (grub_memcmp (&table_aliases[j].guid, &guid, sizeof (guid)) == 0) + break; + if (j != sizeof (table_aliases) / sizeof (table_aliases[0])) + { + curval = grub_xnu_create_value (&(curkey->first_child), "alias"); + if (!curval) + return grub_errno; + curval->datasize = grub_strlen (table_aliases[j].name) + 1; + curval->data = grub_malloc (curval->datasize); + if (!curval->data) + return grub_errno; + grub_memcpy (curval->data, table_aliases[j].name, curval->datasize); + } + } + + /* Create and fill "runtime-services" key. */ + runtimesrvkey = grub_xnu_create_key (&(efikey->first_child), + "runtime-services"); + if (! runtimesrvkey) + return grub_errno; + curval = grub_xnu_create_value (&(runtimesrvkey->first_child), "table"); + if (! curval) + return grub_errno; + curval->datasize = SIZEOF_OF_UINTN; + curval->data = grub_malloc (curval->datasize); + if (! curval->data) + return grub_errno; + if (SIZEOF_OF_UINTN == 4) + *((grub_uint32_t *) curval->data) + = PTR_TO_UINT32 (SYSTEM_TABLE_PTR (runtime_services)); + else + *((grub_uint64_t *) curval->data) + = PTR_TO_UINT64 (SYSTEM_TABLE_PTR (runtime_services)); + + return GRUB_ERR_NONE; +} + +grub_err_t +grub_xnu_boot_resume (void) +{ + struct grub_relocator32_state state; + + state.esp = grub_xnu_stack; + state.ebp = grub_xnu_stack; + state.eip = grub_xnu_entry_point; + state.eax = grub_xnu_arg1; + + return grub_relocator32_boot (grub_xnu_relocator, state); +} + +/* Setup video for xnu. */ +static grub_err_t +grub_xnu_set_video (struct grub_xnu_boot_params *params) +{ + struct grub_video_mode_info mode_info; + int ret; + char *tmp; + const char *modevar; + void *framebuffer; + grub_err_t err; + struct grub_video_bitmap *bitmap = NULL; + + modevar = grub_env_get ("gfxpayload"); + /* Consider only graphical 32-bit deep modes. */ + if (! modevar || *modevar == 0) + err = grub_video_set_mode (DEFAULT_VIDEO_MODE, + GRUB_VIDEO_MODE_TYPE_PURE_TEXT + | GRUB_VIDEO_MODE_TYPE_DEPTH_MASK, + 32 << GRUB_VIDEO_MODE_TYPE_DEPTH_POS); + else + { + tmp = grub_xasprintf ("%s;" DEFAULT_VIDEO_MODE, modevar); + if (! tmp) + return grub_errno; + err = grub_video_set_mode (tmp, + GRUB_VIDEO_MODE_TYPE_PURE_TEXT + | GRUB_VIDEO_MODE_TYPE_DEPTH_MASK, + 32 << GRUB_VIDEO_MODE_TYPE_DEPTH_POS); + grub_free (tmp); + } + + if (err) + return err; + + ret = grub_video_get_info (&mode_info); + if (ret) + return grub_error (GRUB_ERR_IO, "couldn't retrieve video parameters"); + + if (grub_xnu_bitmap) + { + if (grub_xnu_bitmap_mode == GRUB_XNU_BITMAP_STRETCH) + err = grub_video_bitmap_create_scaled (&bitmap, + mode_info.width, + mode_info.height, + grub_xnu_bitmap, + GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST); + else + bitmap = grub_xnu_bitmap; + } + + if (bitmap) + { + if (grub_xnu_bitmap_mode == GRUB_XNU_BITMAP_STRETCH) + err = grub_video_bitmap_create_scaled (&bitmap, + mode_info.width, + mode_info.height, + grub_xnu_bitmap, + GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST); + else + bitmap = grub_xnu_bitmap; + } + + if (bitmap) + { + int x, y; + + x = mode_info.width - bitmap->mode_info.width; + x /= 2; + y = mode_info.height - bitmap->mode_info.height; + y /= 2; + err = grub_video_blit_bitmap (bitmap, + GRUB_VIDEO_BLIT_REPLACE, + x > 0 ? x : 0, + y > 0 ? y : 0, + x < 0 ? -x : 0, + y < 0 ? -y : 0, + min (bitmap->mode_info.width, + mode_info.width), + min (bitmap->mode_info.height, + mode_info.height)); + } + if (err) + { + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + bitmap = 0; + } + + ret = grub_video_get_info_and_fini (&mode_info, &framebuffer); + if (ret) + return grub_error (GRUB_ERR_IO, "couldn't retrieve video parameters"); + + params->lfb_width = mode_info.width; + params->lfb_height = mode_info.height; + params->lfb_depth = mode_info.bpp; + params->lfb_line_len = mode_info.pitch; + + params->lfb_base = PTR_TO_UINT32 (framebuffer); + params->lfb_mode = bitmap ? GRUB_XNU_VIDEO_SPLASH + : GRUB_XNU_VIDEO_TEXT_IN_VIDEO; + + return GRUB_ERR_NONE; +} + +/* Boot xnu. */ +grub_err_t +grub_xnu_boot (void) +{ + struct grub_xnu_boot_params *bootparams; + void *bp_in; + grub_addr_t bootparams_target; + grub_err_t err; + grub_efi_uintn_t memory_map_size = 0; + void *memory_map; + grub_addr_t memory_map_target; + grub_efi_uintn_t map_key = 0; + grub_efi_uintn_t descriptor_size = 0; + grub_efi_uint32_t descriptor_version = 0; + grub_uint64_t firstruntimepage, lastruntimepage; + grub_uint64_t curruntimepage; + grub_addr_t devtree_target; + grub_size_t devtreelen; + int i; + struct grub_relocator32_state state; + + err = grub_autoefi_prepare (); + if (err) + return err; + + err = grub_cpu_xnu_fill_devprop (); + if (err) + return err; + + err = grub_cpu_xnu_fill_devicetree (); + if (err) + return err; + + err = grub_xnu_fill_devicetree (); + if (err) + return err; + + /* Page-align to avoid following parts to be inadvertently freed. */ + err = grub_xnu_align_heap (GRUB_XNU_PAGESIZE); + if (err) + return err; + + /* Pass memory map to kernel. */ + memory_map_size = 0; + memory_map = 0; + map_key = 0; + descriptor_size = 0; + descriptor_version = 0; + + grub_dprintf ("xnu", "eip=%x\n", grub_xnu_entry_point); + + const char *debug = grub_env_get ("debug"); + + if (debug && (grub_strword (debug, "all") || grub_strword (debug, "xnu"))) + { + grub_printf ("Press any key to launch xnu\n"); + grub_getkey (); + } + + /* Relocate the boot parameters to heap. */ + err = grub_xnu_heap_malloc (sizeof (*bootparams), + &bp_in, &bootparams_target); + if (err) + return err; + bootparams = bp_in; + + /* Set video. */ + err = grub_xnu_set_video (bootparams); + if (err != GRUB_ERR_NONE) + { + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + grub_printf ("Booting in blind mode\n"); + + bootparams->lfb_mode = 0; + bootparams->lfb_width = 0; + bootparams->lfb_height = 0; + bootparams->lfb_depth = 0; + bootparams->lfb_line_len = 0; + bootparams->lfb_base = 0; + } + + if (grub_autoefi_get_memory_map (&memory_map_size, memory_map, + &map_key, &descriptor_size, + &descriptor_version) < 0) + return grub_errno; + + /* We will do few allocations later. Reserve some space for possible + memory map growth. */ + memory_map_size += 20 * descriptor_size; + err = grub_xnu_heap_malloc (memory_map_size, + &memory_map, &memory_map_target); + if (err) + return err; + + err = grub_xnu_writetree_toheap (&devtree_target, &devtreelen); + if (err) + return err; + + grub_memcpy (bootparams->cmdline, grub_xnu_cmdline, + sizeof (bootparams->cmdline)); + + bootparams->devtree = devtree_target; + bootparams->devtreelen = devtreelen; + + err = grub_autoefi_finish_boot_services (&memory_map_size, memory_map, + &map_key, &descriptor_size, + &descriptor_version); + if (err) + return err; + + bootparams->efi_system_table = PTR_TO_UINT32 (grub_autoefi_system_table); + + firstruntimepage = (((grub_addr_t) grub_xnu_heap_target_start + + grub_xnu_heap_size + GRUB_XNU_PAGESIZE - 1) + / GRUB_XNU_PAGESIZE) + 20; + curruntimepage = firstruntimepage; + + for (i = 0; (unsigned) i < memory_map_size / descriptor_size; i++) + { + grub_efi_memory_descriptor_t *curdesc = (grub_efi_memory_descriptor_t *) + ((char *) memory_map + descriptor_size * i); + + curdesc->virtual_start = curdesc->physical_start; + + if (curdesc->type == GRUB_EFI_RUNTIME_SERVICES_DATA + || curdesc->type == GRUB_EFI_RUNTIME_SERVICES_CODE) + { + curdesc->virtual_start = curruntimepage << 12; + curruntimepage += curdesc->num_pages; + if (curdesc->physical_start + <= PTR_TO_UINT64 (grub_autoefi_system_table) + && curdesc->physical_start + (curdesc->num_pages << 12) + > PTR_TO_UINT64 (grub_autoefi_system_table)) + bootparams->efi_system_table + = PTR_TO_UINT64 (grub_autoefi_system_table) + - curdesc->physical_start + curdesc->virtual_start; + if (SIZEOF_OF_UINTN == 8 && grub_xnu_is_64bit) + curdesc->virtual_start |= 0xffffff8000000000ULL; + } + } + + lastruntimepage = curruntimepage; + + bootparams->efi_mmap = memory_map_target; + bootparams->efi_mmap_size = memory_map_size; + bootparams->efi_mem_desc_size = descriptor_size; + bootparams->efi_mem_desc_version = descriptor_version; + + bootparams->heap_start = grub_xnu_heap_target_start; + bootparams->heap_size = grub_xnu_heap_size; + bootparams->efi_runtime_first_page = firstruntimepage; + + bootparams->efi_runtime_npages = lastruntimepage - firstruntimepage; + bootparams->efi_uintnbits = SIZEOF_OF_UINTN * 8; + + bootparams->verminor = GRUB_XNU_BOOTARGS_VERMINOR; + bootparams->vermajor = GRUB_XNU_BOOTARGS_VERMAJOR; + + /* Parameters for asm helper. */ + grub_xnu_stack = bootparams->heap_start + + bootparams->heap_size + GRUB_XNU_PAGESIZE; + grub_xnu_arg1 = bootparams_target; + + grub_autoefi_set_virtual_address_map (memory_map_size, descriptor_size, + descriptor_version, memory_map); + + state.eip = grub_xnu_entry_point; + state.eax = grub_xnu_arg1; + state.esp = grub_xnu_stack; + state.ebp = grub_xnu_stack; + return grub_relocator32_boot (grub_xnu_relocator, state); +} + +static grub_command_t cmd_devprop_load; + +void +grub_cpu_xnu_init (void) +{ + cmd_devprop_load = grub_register_command ("xnu_devprop_load", + grub_cmd_devprop_load, + 0, N_("Load device-properties dump.")); +} + +void +grub_cpu_xnu_fini (void) +{ + grub_unregister_command (cmd_devprop_load); +} diff --git a/grub-core/loader/macho.c b/grub-core/loader/macho.c new file mode 100644 index 0000000..ecf0d8c --- /dev/null +++ b/grub-core/loader/macho.c @@ -0,0 +1,160 @@ +/* macho.c - load Mach-O files. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2009 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see <http://www.gnu.org/licenses/>. + */ + +/* This Mach-O loader is incomplete and can load only non-relocatable segments. + This is however enough to boot xnu (otool -l and Mach-O specs for more info). +*/ + +#include <grub/err.h> +#include <grub/macho.h> +#include <grub/cpu/macho.h> +#include <grub/machoload.h> +#include <grub/file.h> +#include <grub/misc.h> +#include <grub/mm.h> + +grub_err_t +grub_macho_close (grub_macho_t macho) +{ + grub_file_t file = macho->file; + + grub_free (macho->cmds32); + grub_free (macho->cmds64); + + grub_free (macho); + + if (file) + grub_file_close (file); + + return grub_errno; +} + +grub_macho_t +grub_macho_file (grub_file_t file) +{ + grub_macho_t macho; + union grub_macho_filestart filestart; + + macho = grub_malloc (sizeof (*macho)); + if (! macho) + return 0; + + macho->file = file; + macho->offset32 = -1; + macho->offset64 = -1; + macho->end32 = -1; + macho->end64 = -1; + macho->cmds32 = 0; + macho->cmds64 = 0; + + if (grub_file_seek (macho->file, 0) == (grub_off_t) -1) + goto fail; + + if (grub_file_read (macho->file, &filestart, sizeof (filestart)) + != sizeof (filestart)) + { + grub_error_push (); + grub_error (GRUB_ERR_READ_ERROR, "cannot read Mach-O header"); + goto fail; + } + + /* Is it a fat file? */ + if (filestart.fat.magic == grub_be_to_cpu32 (GRUB_MACHO_FAT_MAGIC)) + { + struct grub_macho_fat_arch *archs; + int i, narchs; + + /* Load architecture description. */ + narchs = grub_be_to_cpu32 (filestart.fat.nfat_arch); + if (grub_file_seek (macho->file, sizeof (struct grub_macho_fat_header)) + == (grub_off_t) -1) + goto fail; + archs = grub_malloc (sizeof (struct grub_macho_fat_arch) * narchs); + if (!archs) + goto fail; + if (grub_file_read (macho->file, archs, + sizeof (struct grub_macho_fat_arch) * narchs) + != (grub_ssize_t)sizeof(struct grub_macho_fat_arch) * narchs) + { + grub_free (archs); + grub_error_push (); + grub_error (GRUB_ERR_READ_ERROR, "cannot read Mach-O header"); + goto fail; + } + + for (i = 0; i < narchs; i++) + { + if (GRUB_MACHO_CPUTYPE_IS_HOST32 + (grub_be_to_cpu32 (archs[i].cputype))) + { + macho->offset32 = grub_be_to_cpu32 (archs[i].offset); + macho->end32 = grub_be_to_cpu32 (archs[i].offset) + + grub_be_to_cpu32 (archs[i].size); + } + if (GRUB_MACHO_CPUTYPE_IS_HOST64 + (grub_be_to_cpu32 (archs[i].cputype))) + { + macho->offset64 = grub_be_to_cpu32 (archs[i].offset); + macho->end64 = grub_be_to_cpu32 (archs[i].offset) + + grub_be_to_cpu32 (archs[i].size); + } + } + grub_free (archs); + } + + /* Is it a thin 32-bit file? */ + if (filestart.thin32.magic == GRUB_MACHO_MAGIC32) + { + macho->offset32 = 0; + macho->end32 = grub_file_size (file); + } + + /* Is it a thin 64-bit file? */ + if (filestart.thin64.magic == GRUB_MACHO_MAGIC64) + { + macho->offset64 = 0; + macho->end64 = grub_file_size (file); + } + + grub_macho_parse32 (macho); + grub_macho_parse64 (macho); + + return macho; + +fail: + grub_macho_close (macho); + return 0; +} + +grub_macho_t +grub_macho_open (const char *name) +{ + grub_file_t file; + grub_macho_t macho; + + file = grub_file_open (name); + if (! file) + return 0; + + macho = grub_macho_file (file); + if (! macho) + grub_file_close (file); + + return macho; +} diff --git a/grub-core/loader/macho32.c b/grub-core/loader/macho32.c new file mode 100644 index 0000000..0d740ed --- /dev/null +++ b/grub-core/loader/macho32.c @@ -0,0 +1,18 @@ +#include <grub/cpu/macho.h> +#include <grub/machoload.h> + +#define SUFFIX(x) x ## 32 +typedef struct grub_macho_header32 grub_macho_header_t; +typedef struct grub_macho_segment32 grub_macho_segment_t; +typedef grub_uint32_t grub_macho_addr_t; +typedef struct grub_macho_thread32 grub_macho_thread_t; +#define offsetXX offset32 +#define ncmdsXX ncmds32 +#define cmdsizeXX cmdsize32 +#define cmdsXX cmds32 +#define endXX end32 +#define XX "32" +#define GRUB_MACHO_MAGIC GRUB_MACHO_MAGIC32 +#define GRUB_MACHO_CMD_SEGMENT GRUB_MACHO_CMD_SEGMENT32 +#include "machoXX.c" + diff --git a/grub-core/loader/macho64.c b/grub-core/loader/macho64.c new file mode 100644 index 0000000..17a8021 --- /dev/null +++ b/grub-core/loader/macho64.c @@ -0,0 +1,18 @@ +#include <grub/cpu/macho.h> +#include <grub/machoload.h> + +#define SUFFIX(x) x ## 64 +typedef struct grub_macho_header64 grub_macho_header_t; +typedef struct grub_macho_segment64 grub_macho_segment_t; +typedef grub_uint64_t grub_macho_addr_t; +typedef struct grub_macho_thread64 grub_macho_thread_t; +#define offsetXX offset64 +#define ncmdsXX ncmds64 +#define cmdsizeXX cmdsize64 +#define cmdsXX cmds64 +#define endXX end64 +#define XX "64" +#define GRUB_MACHO_MAGIC GRUB_MACHO_MAGIC64 +#define GRUB_MACHO_CMD_SEGMENT GRUB_MACHO_CMD_SEGMENT64 +#include "machoXX.c" + diff --git a/grub-core/loader/machoXX.c b/grub-core/loader/machoXX.c new file mode 100644 index 0000000..918ddbb --- /dev/null +++ b/grub-core/loader/machoXX.c @@ -0,0 +1,239 @@ + +#include <grub/file.h> +#include <grub/mm.h> +#include <grub/misc.h> + +#define min(a,b) (((a) < (b)) ? (a) : (b)) + +int +SUFFIX (grub_macho_contains_macho) (grub_macho_t macho) +{ + return macho->offsetXX != -1; +} + +void +SUFFIX (grub_macho_parse) (grub_macho_t macho) +{ + grub_macho_header_t head; + + /* Is there any candidate at all? */ + if (macho->offsetXX == -1) + return; + + /* Read header and check magic*/ + if (grub_file_seek (macho->file, macho->offsetXX) == (grub_off_t) -1 + || grub_file_read (macho->file, &head, sizeof (head)) + != sizeof(head)) + { + grub_error (GRUB_ERR_READ_ERROR, "cannot read Mach-O header"); + macho->offsetXX = -1; + return; + } + if (head.magic != GRUB_MACHO_MAGIC) + { + grub_error (GRUB_ERR_BAD_OS, "invalid Mach-O " XX "-bit header"); + macho->offsetXX = -1; + return; + } + + /* Read commands. */ + macho->ncmdsXX = head.ncmds; + macho->cmdsizeXX = head.sizeofcmds; + macho->cmdsXX = grub_malloc(macho->cmdsizeXX); + if (! macho->cmdsXX) + { + grub_error (GRUB_ERR_OUT_OF_MEMORY, "not enough memory to read commands"); + return; + } + if (grub_file_read (macho->file, macho->cmdsXX, + (grub_size_t) macho->cmdsizeXX) + != (grub_ssize_t) macho->cmdsizeXX) + { + grub_error (GRUB_ERR_READ_ERROR, "cannot read Mach-O header"); + macho->offsetXX = -1; + } +} + +typedef int NESTED_FUNC_ATTR (*grub_macho_iter_hook_t) +(grub_macho_t , struct grub_macho_cmd *, + void *); + +static grub_err_t +grub_macho_cmds_iterate (grub_macho_t macho, + grub_macho_iter_hook_t hook, + void *hook_arg) +{ + grub_uint8_t *hdrs = macho->cmdsXX; + int i; + if (! macho->cmdsXX) + return grub_error (GRUB_ERR_BAD_OS, "couldn't find " XX "-bit Mach-O"); + for (i = 0; i < macho->ncmdsXX; i++) + { + struct grub_macho_cmd *hdr = (struct grub_macho_cmd *) hdrs; + if (hook (macho, hdr, hook_arg)) + break; + hdrs += hdr->cmdsize; + } + + return grub_errno; +} + +grub_size_t +SUFFIX (grub_macho_filesize) (grub_macho_t macho) +{ + if (SUFFIX (grub_macho_contains_macho) (macho)) + return macho->endXX - macho->offsetXX; + return 0; +} + +grub_err_t +SUFFIX (grub_macho_readfile) (grub_macho_t macho, void *dest) +{ + grub_ssize_t read; + if (! SUFFIX (grub_macho_contains_macho) (macho)) + return grub_error (GRUB_ERR_BAD_OS, + "couldn't read architecture-specific part"); + + if (grub_file_seek (macho->file, macho->offsetXX) == (grub_off_t) -1) + { + grub_error_push (); + return grub_error (GRUB_ERR_BAD_OS, + "invalid offset in program header"); + } + + read = grub_file_read (macho->file, dest, + macho->endXX - macho->offsetXX); + if (read != (grub_ssize_t) (macho->endXX - macho->offsetXX)) + { + grub_error_push (); + return grub_error (GRUB_ERR_BAD_OS, + "couldn't read architecture-specific part"); + } + return GRUB_ERR_NONE; +} + +/* Calculate the amount of memory spanned by the segments. */ +grub_err_t +SUFFIX (grub_macho_size) (grub_macho_t macho, grub_macho_addr_t *segments_start, + grub_macho_addr_t *segments_end, int flags) +{ + int nr_phdrs = 0; + + /* Run through the program headers to calculate the total memory size we + should claim. */ + auto int NESTED_FUNC_ATTR calcsize (grub_macho_t _macho, + struct grub_macho_cmd *phdr, void *_arg); + int NESTED_FUNC_ATTR calcsize (grub_macho_t _macho __attribute__ ((unused)), + struct grub_macho_cmd *hdr0, + void *_arg __attribute__ ((unused))) + { + grub_macho_segment_t *hdr = (grub_macho_segment_t *) hdr0; + if (hdr->cmd != GRUB_MACHO_CMD_SEGMENT) + return 0; + + if (! hdr->vmsize) + return 0; + + if (! hdr->filesize && (flags & GRUB_MACHO_NOBSS)) + return 0; + + nr_phdrs++; + if (hdr->vmaddr < *segments_start) + *segments_start = hdr->vmaddr; + if (hdr->vmaddr + hdr->vmsize > *segments_end) + *segments_end = hdr->vmaddr + hdr->vmsize; + return 0; + } + + *segments_start = (grub_macho_addr_t) -1; + *segments_end = 0; + + grub_macho_cmds_iterate (macho, calcsize, 0); + + if (nr_phdrs == 0) + return grub_error (GRUB_ERR_BAD_OS, "no program headers present"); + + if (*segments_end < *segments_start) + /* Very bad addresses. */ + return grub_error (GRUB_ERR_BAD_OS, "bad program header load addresses"); + + return GRUB_ERR_NONE; +} + +/* Load every loadable segment into memory specified by `_load_hook'. */ +grub_err_t +SUFFIX (grub_macho_load) (grub_macho_t macho, char *offset, int flags) +{ + grub_err_t err = 0; + auto int NESTED_FUNC_ATTR do_load(grub_macho_t _macho, + struct grub_macho_cmd *hdr0, + void *_arg __attribute__ ((unused))); + int NESTED_FUNC_ATTR do_load(grub_macho_t _macho, + struct grub_macho_cmd *hdr0, + void *_arg __attribute__ ((unused))) + { + grub_macho_segment_t *hdr = (grub_macho_segment_t *) hdr0; + + if (hdr->cmd != GRUB_MACHO_CMD_SEGMENT) + return 0; + + if (! hdr->filesize && (flags & GRUB_MACHO_NOBSS)) + return 0; + if (! hdr->vmsize) + return 0; + + if (grub_file_seek (_macho->file, hdr->fileoff + + _macho->offsetXX) == (grub_off_t) -1) + { + grub_error_push (); + grub_error (GRUB_ERR_BAD_OS, + "invalid offset in program header"); + return 1; + } + + if (hdr->filesize) + { + grub_ssize_t read; + read = grub_file_read (_macho->file, offset + hdr->vmaddr, + min (hdr->filesize, hdr->vmsize)); + if (read != (grub_ssize_t) min (hdr->filesize, hdr->vmsize)) + { + /* XXX How can we free memory from `load_hook'? */ + grub_error_push (); + err=grub_error (GRUB_ERR_BAD_OS, + "couldn't read segment from file: " + "wanted 0x%lx bytes; read 0x%lx bytes", + hdr->filesize, read); + return 1; + } + } + + if (hdr->filesize < hdr->vmsize) + grub_memset (offset + hdr->vmaddr + hdr->filesize, + 0, hdr->vmsize - hdr->filesize); + return 0; + } + + grub_macho_cmds_iterate (macho, do_load, 0); + + return err; +} + +grub_macho_addr_t +SUFFIX (grub_macho_get_entry_point) (grub_macho_t macho) +{ + grub_macho_addr_t entry_point = 0; + auto int NESTED_FUNC_ATTR hook(grub_macho_t _macho, + struct grub_macho_cmd *hdr, + void *_arg __attribute__ ((unused))); + int NESTED_FUNC_ATTR hook(grub_macho_t _macho __attribute__ ((unused)), + struct grub_macho_cmd *hdr, + void *_arg __attribute__ ((unused))) + { + if (hdr->cmd == GRUB_MACHO_CMD_THREAD) + entry_point = ((grub_macho_thread_t *) hdr)->entry_point; + return 0; + } + grub_macho_cmds_iterate (macho, hook, 0); + return entry_point; +} diff --git a/grub-core/loader/mips/linux.c b/grub-core/loader/mips/linux.c new file mode 100644 index 0000000..0bf7b1f --- /dev/null +++ b/grub-core/loader/mips/linux.c @@ -0,0 +1,443 @@ +/* linux.c - boot Linux */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2003,2004,2005,2007,2009,2010 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <grub/elf.h> +#include <grub/elfload.h> +#include <grub/loader.h> +#include <grub/dl.h> +#include <grub/mm.h> +#include <grub/misc.h> +#include <grub/command.h> +#include <grub/mips/relocator.h> +#include <grub/memory.h> +#include <grub/i18n.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +/* For frequencies. */ +#include <grub/pci.h> +#include <grub/machine/time.h> + +#ifdef GRUB_MACHINE_MIPS_YEELOONG +/* This can be detected on runtime from PMON, but: + a) it wouldn't work when GRUB is the firmware + and + b) for now we only support Yeeloong anyway. */ +#define LOONGSON_MACHTYPE "machtype=lemote-yeeloong-2f-8.9inches" +#endif + +static grub_dl_t my_mod; + +static int loaded; + +static grub_size_t linux_size; + +static struct grub_relocator *relocator; +static grub_uint8_t *playground; +static grub_addr_t target_addr, entry_addr; +static int linux_argc; +static grub_off_t argv_off, envp_off; +static grub_off_t rd_addr_arg_off, rd_size_arg_off; +static int initrd_loaded = 0; + +static grub_err_t +grub_linux_boot (void) +{ + struct grub_relocator32_state state; + + /* Boot the kernel. */ + state.gpr[1] = entry_addr; + state.gpr[4] = linux_argc; + state.gpr[5] = target_addr + argv_off; + state.gpr[6] = target_addr + envp_off; + state.jumpreg = 1; + grub_relocator32_boot (relocator, state); + + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_linux_unload (void) +{ + grub_relocator_unload (relocator); + grub_dl_unref (my_mod); + + loaded = 0; + + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_linux_load32 (grub_elf_t elf, void **extra_mem, grub_size_t extra_size) +{ + Elf32_Addr base; + int extraoff; + grub_err_t err; + + /* Linux's entry point incorrectly contains a virtual address. */ + entry_addr = elf->ehdr.ehdr32.e_entry; + + linux_size = grub_elf32_size (elf, &base, 0); + if (linux_size == 0) + return grub_errno; + target_addr = base; + /* Pad it; the kernel scribbles over memory beyond its load address. */ + linux_size += 0x100000; + linux_size = ALIGN_UP (base + linux_size, 4) - base; + extraoff = linux_size; + linux_size += extra_size; + + relocator = grub_relocator_new (); + if (!relocator) + return grub_errno; + + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_addr (relocator, &ch, + target_addr & 0x1fffffff, + linux_size); + if (err) + return err; + playground = get_virtual_current_address (ch); + } + + *extra_mem = playground + extraoff; + + /* Now load the segments into the area we claimed. */ + auto grub_err_t offset_phdr (Elf32_Phdr *phdr, grub_addr_t *addr, int *do_load); + grub_err_t offset_phdr (Elf32_Phdr *phdr, grub_addr_t *addr, int *do_load) + { + if (phdr->p_type != PT_LOAD) + { + *do_load = 0; + return 0; + } + *do_load = 1; + + /* Linux's program headers incorrectly contain virtual addresses. + * Translate those to physical, and offset to the area we claimed. */ + *addr = (grub_addr_t) (phdr->p_paddr - base + playground); + return 0; + } + return grub_elf32_load (elf, offset_phdr, 0, 0); +} + +static grub_err_t +grub_linux_load64 (grub_elf_t elf, void **extra_mem, grub_size_t extra_size) +{ + Elf64_Addr base; + int extraoff; + grub_err_t err; + + /* Linux's entry point incorrectly contains a virtual address. */ + entry_addr = elf->ehdr.ehdr64.e_entry; + + linux_size = grub_elf64_size (elf, &base, 0); + if (linux_size == 0) + return grub_errno; + target_addr = base; + /* Pad it; the kernel scribbles over memory beyond its load address. */ + linux_size += 0x100000; + linux_size = ALIGN_UP (base + linux_size, 4) - base; + extraoff = linux_size; + linux_size += extra_size; + + relocator = grub_relocator_new (); + if (!relocator) + return grub_errno; + + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_addr (relocator, &ch, + target_addr & 0x1fffffff, + linux_size); + if (err) + return err; + playground = get_virtual_current_address (ch); + } + + *extra_mem = playground + extraoff; + + /* Now load the segments into the area we claimed. */ + auto grub_err_t offset_phdr (Elf64_Phdr *phdr, grub_addr_t *addr, int *do_load); + grub_err_t offset_phdr (Elf64_Phdr *phdr, grub_addr_t *addr, int *do_load) + { + if (phdr->p_type != PT_LOAD) + { + *do_load = 0; + return 0; + } + *do_load = 1; + /* Linux's program headers incorrectly contain virtual addresses. + * Translate those to physical, and offset to the area we claimed. */ + *addr = (grub_addr_t) (phdr->p_paddr - base + playground); + return 0; + } + return grub_elf64_load (elf, offset_phdr, 0, 0); +} + +static grub_err_t +grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_elf_t elf = 0; + int i; + int size; + void *extra = NULL; + grub_uint32_t *linux_argv, *linux_envp; + char *linux_args, *linux_envs; + grub_err_t err; + + if (argc == 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "no kernel specified"); + + elf = grub_elf_open (argv[0]); + if (! elf) + return grub_errno; + + if (elf->ehdr.ehdr32.e_type != ET_EXEC) + { + grub_elf_close (elf); + return grub_error (GRUB_ERR_UNKNOWN_OS, + "this ELF file is not of the right type\n"); + } + + /* Release the previously used memory. */ + grub_loader_unset (); + loaded = 0; + + /* For arguments. */ + linux_argc = argc; +#ifdef LOONGSON_MACHTYPE + linux_argc++; +#endif + /* Main arguments. */ + size = (linux_argc) * sizeof (grub_uint32_t); + /* Initrd address and size. */ + size += 2 * sizeof (grub_uint32_t); + /* NULL terminator. */ + size += sizeof (grub_uint32_t); + + /* First argument is always "a0". */ + size += ALIGN_UP (sizeof ("a0"), 4); + /* Normal arguments. */ + for (i = 1; i < argc; i++) + size += ALIGN_UP (grub_strlen (argv[i]) + 1, 4); +#ifdef LOONGSON_MACHTYPE + size += ALIGN_UP (sizeof (LOONGSON_MACHTYPE), 4); +#endif + + /* rd arguments. */ + size += ALIGN_UP (sizeof ("rd_start=0xXXXXXXXXXXXXXXXX"), 4); + size += ALIGN_UP (sizeof ("rd_size=0xXXXXXXXXXXXXXXXX"), 4); + + /* For the environment. */ + size += sizeof (grub_uint32_t); + size += 4 * sizeof (grub_uint32_t); + size += ALIGN_UP (sizeof ("memsize=XXXXXXXXXXXXXXXXXXXX"), 4) + + ALIGN_UP (sizeof ("highmemsize=XXXXXXXXXXXXXXXXXXXX"), 4) + + ALIGN_UP (sizeof ("busclock=XXXXXXXXXX"), 4) + + ALIGN_UP (sizeof ("cpuclock=XXXXXXXXXX"), 4); + + if (grub_elf_is_elf32 (elf)) + err = grub_linux_load32 (elf, &extra, size); + else + if (grub_elf_is_elf64 (elf)) + err = grub_linux_load64 (elf, &extra, size); + else + err = grub_error (GRUB_ERR_BAD_FILE_TYPE, "unknown ELF class"); + + grub_elf_close (elf); + + if (err) + return err; + + linux_argv = extra; + argv_off = (grub_uint8_t *) linux_argv - (grub_uint8_t *) playground; + extra = linux_argv + (linux_argc + 1 + 2); + linux_args = extra; + + grub_memcpy (linux_args, "a0", sizeof ("a0")); + *linux_argv = (grub_uint8_t *) linux_args - (grub_uint8_t *) playground + + target_addr; + linux_argv++; + linux_args += ALIGN_UP (sizeof ("a0"), 4); + +#ifdef LOONGSON_MACHTYPE + /* In Loongson platform, it is the responsibility of the bootloader/firmware + to supply the OS kernel with machine type information. */ + grub_memcpy (linux_args, LOONGSON_MACHTYPE, sizeof (LOONGSON_MACHTYPE)); + *linux_argv = (grub_uint8_t *) linux_args - (grub_uint8_t *) playground + + target_addr; + linux_argv++; + linux_args += ALIGN_UP (sizeof (LOONGSON_MACHTYPE), 4); +#endif + + for (i = 1; i < argc; i++) + { + grub_memcpy (linux_args, argv[i], grub_strlen (argv[i]) + 1); + *linux_argv = (grub_uint8_t *) linux_args - (grub_uint8_t *) playground + + target_addr; + linux_argv++; + linux_args += ALIGN_UP (grub_strlen (argv[i]) + 1, 4); + } + + /* Reserve space for rd arguments. */ + rd_addr_arg_off = (grub_uint8_t *) linux_args - (grub_uint8_t *) playground; + linux_args += ALIGN_UP (sizeof ("rd_start=0xXXXXXXXXXXXXXXXX"), 4); + *linux_argv = 0; + linux_argv++; + + rd_size_arg_off = (grub_uint8_t *) linux_args - (grub_uint8_t *) playground; + linux_args += ALIGN_UP (sizeof ("rd_size=0xXXXXXXXXXXXXXXXX"), 4); + *linux_argv = 0; + linux_argv++; + + *linux_argv = 0; + + extra = linux_args; + + linux_envp = extra; + envp_off = (grub_uint8_t *) linux_envp - (grub_uint8_t *) playground; + linux_envs = (char *) (linux_envp + 5); + grub_snprintf (linux_envs, sizeof ("memsize=XXXXXXXXXXXXXXXXXXXX"), + "memsize=%lld", + (unsigned long long) grub_mmap_get_lower () >> 20); + linux_envp[0] = (grub_uint8_t *) linux_envs - (grub_uint8_t *) playground + + target_addr; + linux_envs += ALIGN_UP (grub_strlen (linux_envs) + 1, 4); + grub_snprintf (linux_envs, sizeof ("highmemsize=XXXXXXXXXXXXXXXXXXXX"), + "highmemsize=%lld", + (unsigned long long) grub_mmap_get_upper () >> 20); + linux_envp[1] = (grub_uint8_t *) linux_envs - (grub_uint8_t *) playground + + target_addr; + linux_envs += ALIGN_UP (grub_strlen (linux_envs) + 1, 4); + + grub_snprintf (linux_envs, sizeof ("busclock=XXXXXXXXXX"), + "busclock=%d", grub_arch_busclock); + linux_envp[2] = (grub_uint8_t *) linux_envs - (grub_uint8_t *) playground + + target_addr; + linux_envs += ALIGN_UP (grub_strlen (linux_envs) + 1, 4); + grub_snprintf (linux_envs, sizeof ("cpuclock=XXXXXXXXXX"), + "cpuclock=%d", grub_arch_cpuclock); + linux_envp[3] = (grub_uint8_t *) linux_envs - (grub_uint8_t *) playground + + target_addr; + linux_envs += ALIGN_UP (grub_strlen (linux_envs) + 1, 4); + + + linux_envp[4] = 0; + + grub_loader_set (grub_linux_boot, grub_linux_unload, 1); + initrd_loaded = 0; + loaded = 1; + grub_dl_ref (my_mod); + + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_file_t file = 0; + grub_ssize_t size; + void *initrd_src; + grub_addr_t initrd_dest; + grub_err_t err; + + if (argc == 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "no initrd specified"); + + if (!loaded) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "you need to load Linux first."); + + if (initrd_loaded) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "only one initrd can be loaded."); + + grub_file_filter_disable_compression (); + file = grub_file_open (argv[0]); + if (! file) + return grub_errno; + + size = grub_file_size (file); + + { + grub_relocator_chunk_t ch; + + err = grub_relocator_alloc_chunk_align (relocator, &ch, + (target_addr & 0x1fffffff) + + linux_size + 0x10000, + (0x10000000 - size), + size, 0x10000, + GRUB_RELOCATOR_PREFERENCE_NONE); + + if (err) + { + grub_file_close (file); + return err; + } + initrd_src = get_virtual_current_address (ch); + initrd_dest = get_physical_target_address (ch) | 0x80000000; + } + + if (grub_file_read (file, initrd_src, size) != size) + { + grub_error (GRUB_ERR_FILE_READ_ERROR, "couldn't read file"); + grub_file_close (file); + + return grub_errno; + } + + grub_snprintf ((char *) playground + rd_addr_arg_off, + sizeof ("rd_start=0xXXXXXXXXXXXXXXXX"), "rd_start=0x%llx", + (unsigned long long) initrd_dest); + ((grub_uint32_t *) (playground + argv_off))[linux_argc] + = target_addr + rd_addr_arg_off; + linux_argc++; + + grub_snprintf ((char *) playground + rd_size_arg_off, + sizeof ("rd_size=0xXXXXXXXXXXXXXXXXX"), "rd_size=0x%llx", + (unsigned long long) size); + ((grub_uint32_t *) (playground + argv_off))[linux_argc] + = target_addr + rd_size_arg_off; + linux_argc++; + + initrd_loaded = 1; + + grub_file_close (file); + + return GRUB_ERR_NONE; +} + +static grub_command_t cmd_linux, cmd_initrd; + +GRUB_MOD_INIT(linux) +{ + cmd_linux = grub_register_command ("linux", grub_cmd_linux, + 0, N_("Load Linux.")); + cmd_initrd = grub_register_command ("initrd", grub_cmd_initrd, + 0, N_("Load initrd.")); + my_mod = mod; +} + +GRUB_MOD_FINI(linux) +{ + grub_unregister_command (cmd_linux); + grub_unregister_command (cmd_initrd); +} diff --git a/grub-core/loader/multiboot.c b/grub-core/loader/multiboot.c new file mode 100644 index 0000000..d9e74b3 --- /dev/null +++ b/grub-core/loader/multiboot.c @@ -0,0 +1,358 @@ +/* multiboot.c - boot a multiboot OS image. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 1999,2000,2001,2002,2003,2004,2005,2007,2008,2009,2010 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see <http://www.gnu.org/licenses/>. + */ + +/* + * FIXME: The following features from the Multiboot specification still + * need to be implemented: + * - drives table + * - ROM configuration table + * - SMBIOS tables + * - Networking information + */ + +#include <grub/loader.h> +#include <grub/command.h> +#include <grub/multiboot.h> +#include <grub/cpu/multiboot.h> +#include <grub/elf.h> +#include <grub/aout.h> +#include <grub/file.h> +#include <grub/err.h> +#include <grub/dl.h> +#include <grub/mm.h> +#include <grub/misc.h> +#include <grub/env.h> +#include <grub/cpu/relocator.h> +#include <grub/video.h> +#include <grub/memory.h> +#include <grub/i18n.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +#ifdef GRUB_MACHINE_EFI +#include <grub/efi/efi.h> +#endif + +struct grub_relocator *grub_multiboot_relocator = NULL; +grub_uint32_t grub_multiboot_payload_eip; +#if defined (GRUB_MACHINE_PCBIOS) || defined (GRUB_MACHINE_MULTIBOOT) || defined (GRUB_MACHINE_COREBOOT) || defined (GRUB_MACHINE_QEMU) +#define DEFAULT_VIDEO_MODE "text" +#else +#define DEFAULT_VIDEO_MODE "auto" +#endif + +static int accepts_video; +static int accepts_ega_text; +static int console_required; +static grub_dl_t my_mod; + + +/* Return the length of the Multiboot mmap that will be needed to allocate + our platform's map. */ +grub_uint32_t +grub_get_multiboot_mmap_count (void) +{ + grub_size_t count = 0; + + auto int NESTED_FUNC_ATTR hook (grub_uint64_t, grub_uint64_t, grub_uint32_t); + int NESTED_FUNC_ATTR hook (grub_uint64_t addr __attribute__ ((unused)), + grub_uint64_t size __attribute__ ((unused)), + grub_memory_type_t type __attribute__ ((unused))) + { + count++; + return 0; + } + + grub_mmap_iterate (hook); + + return count; +} + +grub_err_t +grub_multiboot_set_video_mode (void) +{ + grub_err_t err; + const char *modevar; + + if (accepts_video || !GRUB_MACHINE_HAS_VGA_TEXT) + { + modevar = grub_env_get ("gfxpayload"); + if (! modevar || *modevar == 0) + err = grub_video_set_mode (DEFAULT_VIDEO_MODE, 0, 0); + else + { + char *tmp; + tmp = grub_xasprintf ("%s;" DEFAULT_VIDEO_MODE, modevar); + if (! tmp) + return grub_errno; + err = grub_video_set_mode (tmp, 0, 0); + grub_free (tmp); + } + } + else + err = grub_video_set_mode ("text", 0, 0); + + return err; +} + +static grub_err_t +grub_multiboot_boot (void) +{ + grub_err_t err; + struct grub_relocator32_state state = MULTIBOOT_INITIAL_STATE; + + state.MULTIBOOT_ENTRY_REGISTER = grub_multiboot_payload_eip; + + err = grub_multiboot_make_mbi (&state.MULTIBOOT_MBI_REGISTER); + + if (err) + return err; + +#ifdef GRUB_MACHINE_EFI + err = grub_efi_finish_boot_services (NULL, NULL, NULL, NULL, NULL); + if (err) + return err; +#endif + + grub_relocator32_boot (grub_multiboot_relocator, state); + + /* Not reached. */ + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_multiboot_unload (void) +{ + grub_multiboot_free_mbi (); + + grub_relocator_unload (grub_multiboot_relocator); + grub_multiboot_relocator = NULL; + + grub_dl_unref (my_mod); + + return GRUB_ERR_NONE; +} + +#define MULTIBOOT_LOAD_ELF64 +#include "multiboot_elfxx.c" +#undef MULTIBOOT_LOAD_ELF64 + +#define MULTIBOOT_LOAD_ELF32 +#include "multiboot_elfxx.c" +#undef MULTIBOOT_LOAD_ELF32 + +/* Load ELF32 or ELF64. */ +grub_err_t +grub_multiboot_load_elf (grub_file_t file, void *buffer) +{ + if (grub_multiboot_is_elf32 (buffer)) + return grub_multiboot_load_elf32 (file, buffer); + else if (grub_multiboot_is_elf64 (buffer)) + return grub_multiboot_load_elf64 (file, buffer); + + return grub_error (GRUB_ERR_UNKNOWN_OS, "unknown ELF class"); +} + +grub_err_t +grub_multiboot_set_console (int console_type, int accepted_consoles, + int width, int height, int depth, + int console_req) +{ + console_required = console_req; + if (!(accepted_consoles + & (GRUB_MULTIBOOT_CONSOLE_FRAMEBUFFER + | (GRUB_MACHINE_HAS_VGA_TEXT ? GRUB_MULTIBOOT_CONSOLE_EGA_TEXT : 0)))) + { + if (console_required) + return grub_error (GRUB_ERR_BAD_OS, + "OS requires a console but none is available"); + grub_printf ("WARNING: no console will be available to OS"); + accepts_video = 0; + accepts_ega_text = 0; + return GRUB_ERR_NONE; + } + + if (console_type == GRUB_MULTIBOOT_CONSOLE_FRAMEBUFFER) + { + char *buf; + if (depth && width && height) + buf = grub_xasprintf ("%dx%dx%d,%dx%d,auto", width, + height, depth, width, height); + else if (width && height) + buf = grub_xasprintf ("%dx%d,auto", width, height); + else + buf = grub_strdup ("auto"); + + if (!buf) + return grub_errno; + grub_env_set ("gfxpayload", buf); + grub_free (buf); + } + else + grub_env_set ("gfxpayload", "text"); + + accepts_video = !!(accepted_consoles & GRUB_MULTIBOOT_CONSOLE_FRAMEBUFFER); + accepts_ega_text = !!(accepted_consoles & GRUB_MULTIBOOT_CONSOLE_EGA_TEXT); + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_cmd_multiboot (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_file_t file = 0; + grub_err_t err; + + grub_loader_unset (); + + if (argc == 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "no kernel specified"); + + file = grub_file_open (argv[0]); + if (! file) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "couldn't open file"); + + grub_dl_ref (my_mod); + + /* Skip filename. */ + grub_multiboot_init_mbi (argc - 1, argv + 1); + + grub_relocator_unload (grub_multiboot_relocator); + grub_multiboot_relocator = grub_relocator_new (); + + if (!grub_multiboot_relocator) + goto fail; + + err = grub_multiboot_load (file); + if (err) + goto fail; + + grub_multiboot_set_bootdev (); + + grub_loader_set (grub_multiboot_boot, grub_multiboot_unload, 0); + + fail: + if (file) + grub_file_close (file); + + if (grub_errno != GRUB_ERR_NONE) + { + grub_relocator_unload (grub_multiboot_relocator); + grub_multiboot_relocator = NULL; + grub_dl_unref (my_mod); + } + + return grub_errno; +} + +static grub_err_t +grub_cmd_module (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_file_t file = 0; + grub_ssize_t size; + void *module = NULL; + grub_addr_t target; + grub_err_t err; + int nounzip = 0; + + if (argc == 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "no module specified"); + + if (grub_strcmp (argv[0], "--nounzip") == 0) + { + argv++; + argc--; + nounzip = 1; + } + + if (argc == 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "no module specified"); + + if (!grub_multiboot_relocator) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + "you need to load the multiboot kernel first"); + + if (nounzip) + grub_file_filter_disable_compression (); + + file = grub_file_open (argv[0]); + if (! file) + return grub_errno; + + size = grub_file_size (file); + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_align (grub_multiboot_relocator, &ch, + 0, (0xffffffff - size) + 1, + size, MULTIBOOT_MOD_ALIGN, + GRUB_RELOCATOR_PREFERENCE_NONE); + if (err) + { + grub_file_close (file); + return err; + } + module = get_virtual_current_address (ch); + target = (grub_addr_t) get_virtual_current_address (ch); + } + + err = grub_multiboot_add_module (target, size, argc - 1, argv + 1); + if (err) + { + grub_file_close (file); + return err; + } + + if (grub_file_read (file, module, size) != size) + { + grub_file_close (file); + return grub_error (GRUB_ERR_FILE_READ_ERROR, "couldn't read file"); + } + + grub_file_close (file); + return GRUB_ERR_NONE;; +} + +static grub_command_t cmd_multiboot, cmd_module; + +GRUB_MOD_INIT(multiboot) +{ + cmd_multiboot = +#ifdef GRUB_USE_MULTIBOOT2 + grub_register_command ("multiboot2", grub_cmd_multiboot, + 0, N_("Load a multiboot 2 kernel.")); + cmd_module = + grub_register_command ("module2", grub_cmd_module, + 0, N_("Load a multiboot 2 module.")); +#else + grub_register_command ("multiboot", grub_cmd_multiboot, + 0, N_("Load a multiboot kernel.")); + cmd_module = + grub_register_command ("module", grub_cmd_module, + 0, N_("Load a multiboot module.")); +#endif + + my_mod = mod; +} + +GRUB_MOD_FINI(multiboot) +{ + grub_unregister_command (cmd_multiboot); + grub_unregister_command (cmd_module); +} diff --git a/grub-core/loader/multiboot_elfxx.c b/grub-core/loader/multiboot_elfxx.c new file mode 100644 index 0000000..0c29fe9 --- /dev/null +++ b/grub-core/loader/multiboot_elfxx.c @@ -0,0 +1,225 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 1999,2000,2001,2002,2003,2004,2005,2007,2008,2009 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see <http://www.gnu.org/licenses/>. + */ + +#if defined(MULTIBOOT_LOAD_ELF32) +# define XX 32 +# define E_MACHINE MULTIBOOT_ELF32_MACHINE +# define ELFCLASSXX ELFCLASS32 +# define Elf_Ehdr Elf32_Ehdr +# define Elf_Phdr Elf32_Phdr +#elif defined(MULTIBOOT_LOAD_ELF64) +# define XX 64 +# define E_MACHINE MULTIBOOT_ELF64_MACHINE +# define ELFCLASSXX ELFCLASS64 +# define Elf_Ehdr Elf64_Ehdr +# define Elf_Phdr Elf64_Phdr +#else +#error "I'm confused" +#endif + +#include <grub/i386/relocator.h> + +#define CONCAT(a,b) CONCAT_(a, b) +#define CONCAT_(a,b) a ## b + +/* Check if BUFFER contains ELF32 (or ELF64). */ +static int +CONCAT(grub_multiboot_is_elf, XX) (void *buffer) +{ + Elf_Ehdr *ehdr = (Elf_Ehdr *) buffer; + + return ehdr->e_ident[EI_CLASS] == ELFCLASSXX; +} + +static grub_err_t +CONCAT(grub_multiboot_load_elf, XX) (grub_file_t file, void *buffer) +{ + Elf_Ehdr *ehdr = (Elf_Ehdr *) buffer; + char *phdr_base; + int i; + + if (ehdr->e_ident[EI_CLASS] != ELFCLASSXX) + return grub_error (GRUB_ERR_UNKNOWN_OS, "invalid ELF class"); + + if (ehdr->e_ident[EI_MAG0] != ELFMAG0 + || ehdr->e_ident[EI_MAG1] != ELFMAG1 + || ehdr->e_ident[EI_MAG2] != ELFMAG2 + || ehdr->e_ident[EI_MAG3] != ELFMAG3 + || ehdr->e_version != EV_CURRENT + || ehdr->e_ident[EI_DATA] != ELFDATA2LSB + || ehdr->e_machine != E_MACHINE) + return grub_error(GRUB_ERR_UNKNOWN_OS, "no valid ELF header found"); + + if (ehdr->e_type != ET_EXEC && ehdr->e_type != ET_DYN) + return grub_error (GRUB_ERR_UNKNOWN_OS, "invalid ELF file type"); + + /* FIXME: Should we support program headers at strange locations? */ + if (ehdr->e_phoff + ehdr->e_phnum * ehdr->e_phentsize > MULTIBOOT_SEARCH) + return grub_error (GRUB_ERR_BAD_OS, "program header at a too high offset"); + +#if defined (MULTIBOOT_LOAD_ELF64) && defined (__mips) + /* We still in 32-bit mode. */ + if (ehdr->e_entry < 0xffffffff80000000ULL) + return grub_error (GRUB_ERR_BAD_OS, "invalid entry point for ELF64"); +#else + /* We still in 32-bit mode. */ + if (ehdr->e_entry > 0xffffffff) + return grub_error (GRUB_ERR_BAD_OS, "invalid entry point for ELF64"); +#endif + + phdr_base = (char *) buffer + ehdr->e_phoff; +#define phdr(i) ((Elf_Phdr *) (phdr_base + (i) * ehdr->e_phentsize)) + + /* Load every loadable segment in memory. */ + for (i = 0; i < ehdr->e_phnum; i++) + { + if (phdr(i)->p_type == PT_LOAD) + { + grub_err_t err; + void *source; + + grub_dprintf ("multiboot_loader", "segment %d: paddr=0x%lx, memsz=0x%lx, vaddr=0x%lx\n", + i, (long) phdr(i)->p_paddr, (long) phdr(i)->p_memsz, (long) phdr(i)->p_vaddr); + + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_addr (grub_multiboot_relocator, + &ch, phdr(i)->p_paddr, + phdr(i)->p_memsz); + if (err) + { + grub_dprintf ("multiboot_loader", "Error loading phdr %d\n", i); + return err; + } + source = get_virtual_current_address (ch); + } + + if (phdr(i)->p_filesz != 0) + { + if (grub_file_seek (file, (grub_off_t) phdr(i)->p_offset) + == (grub_off_t) -1) + return grub_error (GRUB_ERR_BAD_OS, + "invalid offset in program header"); + + if (grub_file_read (file, source, phdr(i)->p_filesz) + != (grub_ssize_t) phdr(i)->p_filesz) + return grub_error (GRUB_ERR_BAD_OS, + "couldn't read segment from file"); + } + + if (phdr(i)->p_filesz < phdr(i)->p_memsz) + grub_memset ((grub_uint8_t *) source + phdr(i)->p_filesz, 0, + phdr(i)->p_memsz - phdr(i)->p_filesz); + } + } + + for (i = 0; i < ehdr->e_phnum; i++) + if (phdr(i)->p_vaddr <= ehdr->e_entry + && phdr(i)->p_vaddr + phdr(i)->p_memsz > ehdr->e_entry) + { + grub_multiboot_payload_eip = (ehdr->e_entry - phdr(i)->p_vaddr) + + phdr(i)->p_paddr; + break; + } + + if (i == ehdr->e_phnum) + return grub_error (GRUB_ERR_BAD_OS, "entry point isn't in a segment"); + +#if defined (__i386__) || defined (__x86_64__) + +#elif defined (__mips) + grub_multiboot_payload_eip |= 0x80000000; +#else +#error Please complete this +#endif + + if (ehdr->e_shnum) + { + grub_uint8_t *shdr, *shdrptr; + + shdr = grub_malloc (ehdr->e_shnum * ehdr->e_shentsize); + if (!shdr) + return grub_errno; + + if (grub_file_seek (file, ehdr->e_shoff) == (grub_off_t) -1) + return grub_error (GRUB_ERR_BAD_OS, + "invalid offset to section headers"); + + if (grub_file_read (file, shdr, ehdr->e_shnum * ehdr->e_shentsize) + != (grub_ssize_t) ehdr->e_shnum * ehdr->e_shentsize) + return grub_error (GRUB_ERR_BAD_OS, + "couldn't read sections headers from file"); + + for (shdrptr = shdr, i = 0; i < ehdr->e_shnum; + shdrptr += ehdr->e_shentsize, i++) + { + Elf_Shdr *sh = (Elf_Shdr *) shdrptr; + void *src; + grub_addr_t target; + grub_err_t err; + + /* This section is a loaded section, + so we don't care. */ + if (sh->sh_addr != 0) + continue; + + /* This section is empty, so we don't care. */ + if (sh->sh_size == 0) + continue; + + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_align (grub_multiboot_relocator, + &ch, 0, + (0xffffffff - sh->sh_size) + + 1, sh->sh_size, + sh->sh_addralign, + GRUB_RELOCATOR_PREFERENCE_NONE); + if (err) + { + grub_dprintf ("multiboot_loader", "Error loading shdr %d\n", i); + return err; + } + src = get_virtual_current_address (ch); + target = get_physical_target_address (ch); + } + + if (grub_file_seek (file, sh->sh_offset) == (grub_off_t) -1) + return grub_error (GRUB_ERR_BAD_OS, + "invalid offset in section header"); + + if (grub_file_read (file, src, sh->sh_size) + != (grub_ssize_t) sh->sh_size) + return grub_error (GRUB_ERR_BAD_OS, + "couldn't read segment from file"); + sh->sh_addr = target; + } + grub_multiboot_add_elfsyms (ehdr->e_shnum, ehdr->e_shentsize, + ehdr->e_shstrndx, shdr); + } + +#undef phdr + + return grub_errno; +} + +#undef XX +#undef E_MACHINE +#undef ELFCLASSXX +#undef Elf_Ehdr +#undef Elf_Phdr diff --git a/grub-core/loader/multiboot_mbi2.c b/grub-core/loader/multiboot_mbi2.c new file mode 100644 index 0000000..05f581b --- /dev/null +++ b/grub-core/loader/multiboot_mbi2.c @@ -0,0 +1,911 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 1999,2000,2001,2002,2003,2004,2005,2007,2008,2009 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <grub/memory.h> +#ifdef GRUB_MACHINE_PCBIOS +#include <grub/machine/biosnum.h> +#include <grub/machine/apm.h> +#include <grub/machine/memory.h> +#endif +#include <grub/multiboot.h> +#include <grub/cpu/multiboot.h> +#include <grub/cpu/relocator.h> +#include <grub/disk.h> +#include <grub/device.h> +#include <grub/partition.h> +#include <grub/mm.h> +#include <grub/misc.h> +#include <grub/env.h> +#include <grub/video.h> +#include <grub/acpi.h> + +#if defined (GRUB_MACHINE_EFI) +#include <grub/efi/efi.h> +#endif + +#if defined (GRUB_MACHINE_PCBIOS) || defined (GRUB_MACHINE_COREBOOT) || defined (GRUB_MACHINE_MULTIBOOT) || defined (GRUB_MACHINE_QEMU) +#include <grub/i386/pc/vbe.h> +#define HAS_VGA_TEXT 1 +#else +#define HAS_VGA_TEXT 0 +#endif + +struct module +{ + struct module *next; + grub_addr_t start; + grub_size_t size; + char *cmdline; + int cmdline_size; +}; + +static struct module *modules, *modules_last; +static grub_size_t cmdline_size; +static grub_size_t total_modcmd; +static unsigned modcnt; +static char *cmdline = NULL; +static int bootdev_set; +static grub_uint32_t biosdev, slice, part; +static grub_size_t elf_sec_num, elf_sec_entsize; +static unsigned elf_sec_shstrndx; +static void *elf_sections; + +void +grub_multiboot_add_elfsyms (grub_size_t num, grub_size_t entsize, + unsigned shndx, void *data) +{ + elf_sec_num = num; + elf_sec_shstrndx = shndx; + elf_sec_entsize = entsize; + elf_sections = data; +} + +grub_err_t +grub_multiboot_load (grub_file_t file) +{ + char *buffer; + grub_ssize_t len; + struct multiboot_header *header; + grub_err_t err; + struct multiboot_header_tag *tag; + struct multiboot_header_tag_address *addr_tag = NULL; + int entry_specified = 0; + grub_addr_t entry = 0; + grub_uint32_t console_required = 0; + struct multiboot_header_tag_framebuffer *fbtag = NULL; + int accepted_consoles = GRUB_MULTIBOOT_CONSOLE_EGA_TEXT; + + buffer = grub_malloc (MULTIBOOT_SEARCH); + if (!buffer) + return grub_errno; + + len = grub_file_read (file, buffer, MULTIBOOT_SEARCH); + if (len < 32) + { + grub_free (buffer); + return grub_error (GRUB_ERR_BAD_OS, "file too small"); + } + + /* Look for the multiboot header in the buffer. The header should + be at least 12 bytes and aligned on a 4-byte boundary. */ + for (header = (struct multiboot_header *) buffer; + ((char *) header <= buffer + len - 12) || (header = 0); + header = (struct multiboot_header *) ((char *) header + MULTIBOOT_HEADER_ALIGN)) + { + if (header->magic == MULTIBOOT_HEADER_MAGIC + && !(header->magic + header->architecture + + header->header_length + header->checksum) + && header->architecture == MULTIBOOT_ARCHITECTURE_CURRENT) + break; + } + + if (header == 0) + { + grub_free (buffer); + return grub_error (GRUB_ERR_BAD_ARGUMENT, "no multiboot header found"); + } + + for (tag = (struct multiboot_header_tag *) (header + 1); + tag->type != MULTIBOOT_TAG_TYPE_END; + tag = (struct multiboot_header_tag *) ((char *) tag + tag->size)) + switch (tag->type) + { + case MULTIBOOT_HEADER_TAG_INFORMATION_REQUEST: + { + unsigned i; + struct multiboot_header_tag_information_request *request_tag + = (struct multiboot_header_tag_information_request *) tag; + if (request_tag->flags & MULTIBOOT_HEADER_TAG_OPTIONAL) + break; + for (i = 0; i < (request_tag->size - sizeof (request_tag)) + / sizeof (request_tag->requests[0]); i++) + switch (request_tag->requests[i]) + { + case MULTIBOOT_TAG_TYPE_END: + case MULTIBOOT_TAG_TYPE_CMDLINE: + case MULTIBOOT_TAG_TYPE_BOOT_LOADER_NAME: + case MULTIBOOT_TAG_TYPE_MODULE: + case MULTIBOOT_TAG_TYPE_BASIC_MEMINFO: + case MULTIBOOT_TAG_TYPE_BOOTDEV: + case MULTIBOOT_TAG_TYPE_MMAP: + case MULTIBOOT_TAG_TYPE_FRAMEBUFFER: + case MULTIBOOT_TAG_TYPE_VBE: + case MULTIBOOT_TAG_TYPE_ELF_SECTIONS: + case MULTIBOOT_TAG_TYPE_APM: + case MULTIBOOT_TAG_TYPE_EFI32: + case MULTIBOOT_TAG_TYPE_EFI64: + case MULTIBOOT_TAG_TYPE_ACPI_OLD: + case MULTIBOOT_TAG_TYPE_ACPI_NEW: + break; + + default: + grub_free (buffer); + return grub_error (GRUB_ERR_UNKNOWN_OS, + "unsupported information tag: 0x%x", + request_tag->requests[i]); + } + break; + } + + case MULTIBOOT_HEADER_TAG_ADDRESS: + addr_tag = (struct multiboot_header_tag_address *) tag; + break; + + case MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS: + entry_specified = 1; + entry = ((struct multiboot_header_tag_entry_address *) tag)->entry_addr; + break; + + case MULTIBOOT_HEADER_TAG_CONSOLE_FLAGS: + if (!(((struct multiboot_header_tag_console_flags *) tag)->console_flags + & MULTIBOOT_CONSOLE_FLAGS_EGA_TEXT_SUPPORTED)) + accepted_consoles &= ~GRUB_MULTIBOOT_CONSOLE_EGA_TEXT; + if (((struct multiboot_header_tag_console_flags *) tag)->console_flags + & MULTIBOOT_CONSOLE_FLAGS_CONSOLE_REQUIRED) + console_required = 1; + break; + + case MULTIBOOT_HEADER_TAG_FRAMEBUFFER: + fbtag = (struct multiboot_header_tag_framebuffer *) tag; + accepted_consoles |= GRUB_MULTIBOOT_CONSOLE_FRAMEBUFFER; + break; + + /* GRUB always page-aligns modules. */ + case MULTIBOOT_HEADER_TAG_MODULE_ALIGN: + break; + + default: + if (! (tag->flags & MULTIBOOT_HEADER_TAG_OPTIONAL)) + { + grub_free (buffer); + return grub_error (GRUB_ERR_UNKNOWN_OS, + "unsupported tag: 0x%x", tag->type); + } + break; + } + + if (addr_tag && !entry_specified) + { + grub_free (buffer); + return grub_error (GRUB_ERR_UNKNOWN_OS, + "load address tag without entry address tag"); + } + + if (addr_tag) + { + int offset = ((char *) header - buffer - + (addr_tag->header_addr - addr_tag->load_addr)); + int load_size = ((addr_tag->load_end_addr == 0) ? file->size - offset : + addr_tag->load_end_addr - addr_tag->load_addr); + grub_size_t code_size; + void *source; + grub_relocator_chunk_t ch; + + if (addr_tag->bss_end_addr) + code_size = (addr_tag->bss_end_addr - addr_tag->load_addr); + else + code_size = load_size; + + err = grub_relocator_alloc_chunk_addr (grub_multiboot_relocator, + &ch, addr_tag->load_addr, + code_size); + if (err) + { + grub_dprintf ("multiboot_loader", "Error loading aout kludge\n"); + grub_free (buffer); + return err; + } + source = get_virtual_current_address (ch); + + if ((grub_file_seek (file, offset)) == (grub_off_t) -1) + { + grub_free (buffer); + return grub_errno; + } + + grub_file_read (file, source, load_size); + if (grub_errno) + { + grub_free (buffer); + return grub_errno; + } + + if (addr_tag->bss_end_addr) + grub_memset ((grub_uint32_t *) source + load_size, 0, + addr_tag->bss_end_addr - addr_tag->load_addr - load_size); + } + else + { + err = grub_multiboot_load_elf (file, buffer); + if (err) + { + grub_free (buffer); + return err; + } + } + + if (entry_specified) + grub_multiboot_payload_eip = entry; + + if (fbtag) + err = grub_multiboot_set_console (GRUB_MULTIBOOT_CONSOLE_FRAMEBUFFER, + accepted_consoles, + fbtag->width, fbtag->height, + fbtag->depth, console_required); + else + err = grub_multiboot_set_console (GRUB_MULTIBOOT_CONSOLE_EGA_TEXT, + accepted_consoles, + 0, 0, 0, console_required); + return err; +} + +static grub_size_t +acpiv2_size (void) +{ +#if GRUB_MACHINE_HAS_ACPI + struct grub_acpi_rsdp_v20 *p = grub_acpi_get_rsdpv2 (); + + if (!p) + return 0; + + return ALIGN_UP (sizeof (struct multiboot_tag_old_acpi) + + p->length, MULTIBOOT_TAG_ALIGN); +#else + return 0; +#endif +} + +static grub_size_t +grub_multiboot_get_mbi_size (void) +{ + return 2 * sizeof (grub_uint32_t) + sizeof (struct multiboot_tag) + + (sizeof (struct multiboot_tag_string) + + ALIGN_UP (cmdline_size, MULTIBOOT_TAG_ALIGN)) + + (sizeof (struct multiboot_tag_string) + + ALIGN_UP (sizeof (PACKAGE_STRING), MULTIBOOT_TAG_ALIGN)) + + (modcnt * sizeof (struct multiboot_tag_module) + total_modcmd) + + ALIGN_UP (sizeof (struct multiboot_tag_basic_meminfo), + MULTIBOOT_TAG_ALIGN) + + ALIGN_UP (sizeof (struct multiboot_tag_bootdev), MULTIBOOT_TAG_ALIGN) + + ALIGN_UP (sizeof (struct multiboot_tag_elf_sections), MULTIBOOT_TAG_ALIGN) + + ALIGN_UP (elf_sec_entsize * elf_sec_num, MULTIBOOT_TAG_ALIGN) + + ALIGN_UP ((sizeof (struct multiboot_tag_mmap) + + grub_get_multiboot_mmap_count () + * sizeof (struct multiboot_mmap_entry)), MULTIBOOT_TAG_ALIGN) + + ALIGN_UP (sizeof (struct multiboot_tag_framebuffer), MULTIBOOT_TAG_ALIGN) + + ALIGN_UP (sizeof (struct multiboot_tag_efi32), MULTIBOOT_TAG_ALIGN) + + ALIGN_UP (sizeof (struct multiboot_tag_efi64), MULTIBOOT_TAG_ALIGN) + + ALIGN_UP (sizeof (struct multiboot_tag_old_acpi) + + sizeof (struct grub_acpi_rsdp_v10), MULTIBOOT_TAG_ALIGN) + + acpiv2_size () + + sizeof (struct multiboot_tag_vbe) + MULTIBOOT_TAG_ALIGN - 1 + + sizeof (struct multiboot_tag_apm) + MULTIBOOT_TAG_ALIGN - 1; +} + +/* Fill previously allocated Multiboot mmap. */ +static void +grub_fill_multiboot_mmap (struct multiboot_tag_mmap *tag) +{ + struct multiboot_mmap_entry *mmap_entry = tag->entries; + + auto int NESTED_FUNC_ATTR hook (grub_uint64_t, grub_uint64_t, + grub_memory_type_t); + int NESTED_FUNC_ATTR hook (grub_uint64_t addr, grub_uint64_t size, + grub_memory_type_t type) + { + mmap_entry->addr = addr; + mmap_entry->len = size; + switch (type) + { + case GRUB_MEMORY_AVAILABLE: + mmap_entry->type = MULTIBOOT_MEMORY_AVAILABLE; + break; + + case GRUB_MEMORY_ACPI: + mmap_entry->type = MULTIBOOT_MEMORY_ACPI_RECLAIMABLE; + break; + + case GRUB_MEMORY_NVS: + mmap_entry->type = MULTIBOOT_MEMORY_NVS; + break; + + case GRUB_MEMORY_BADRAM: + mmap_entry->type = MULTIBOOT_MEMORY_BADRAM; + break; + + default: + mmap_entry->type = MULTIBOOT_MEMORY_RESERVED; + break; + } + mmap_entry++; + + return 0; + } + + tag->type = MULTIBOOT_TAG_TYPE_MMAP; + tag->size = sizeof (struct multiboot_tag_mmap) + + sizeof (struct multiboot_mmap_entry) * grub_get_multiboot_mmap_count (); + tag->entry_size = sizeof (struct multiboot_mmap_entry); + tag->entry_version = 0; + + grub_mmap_iterate (hook); +} + +#if defined (GRUB_MACHINE_PCBIOS) +static void +fill_vbe_tag (struct multiboot_tag_vbe *tag) +{ + grub_vbe_status_t status; + void *scratch = (void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR; + + tag->type = MULTIBOOT_TAG_TYPE_VBE; + tag->size = 0; + + status = grub_vbe_bios_get_controller_info (scratch); + if (status != GRUB_VBE_STATUS_OK) + return; + + grub_memcpy (&tag->vbe_control_info, scratch, + sizeof (struct grub_vbe_info_block)); + + status = grub_vbe_bios_get_mode (scratch); + tag->vbe_mode = *(grub_uint32_t *) scratch; + if (status != GRUB_VBE_STATUS_OK) + return; + + /* get_mode_info isn't available for mode 3. */ + if (tag->vbe_mode == 3) + { + struct grub_vbe_mode_info_block *mode_info = (void *) &tag->vbe_mode_info; + grub_memset (mode_info, 0, + sizeof (struct grub_vbe_mode_info_block)); + mode_info->memory_model = GRUB_VBE_MEMORY_MODEL_TEXT; + mode_info->x_resolution = 80; + mode_info->y_resolution = 25; + } + else + { + status = grub_vbe_bios_get_mode_info (tag->vbe_mode, scratch); + if (status != GRUB_VBE_STATUS_OK) + return; + grub_memcpy (&tag->vbe_mode_info, scratch, + sizeof (struct grub_vbe_mode_info_block)); + } + grub_vbe_bios_get_pm_interface (&tag->vbe_interface_seg, + &tag->vbe_interface_off, + &tag->vbe_interface_len); + + tag->size = sizeof (*tag); +} +#endif + +static grub_err_t +retrieve_video_parameters (grub_uint8_t **ptrorig) +{ + grub_err_t err; + struct grub_video_mode_info mode_info; + void *framebuffer; + grub_video_driver_id_t driv_id; + struct grub_video_palette_data palette[256]; + struct multiboot_tag_framebuffer *tag + = (struct multiboot_tag_framebuffer *) *ptrorig; + + err = grub_multiboot_set_video_mode (); + if (err) + { + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + } + + grub_video_get_palette (0, ARRAY_SIZE (palette), palette); + + driv_id = grub_video_get_driver_id (); +#if HAS_VGA_TEXT + if (driv_id == GRUB_VIDEO_DRIVER_NONE) + { + struct grub_vbe_mode_info_block vbe_mode_info; + grub_uint32_t vbe_mode; + +#if defined (GRUB_MACHINE_PCBIOS) + { + grub_vbe_status_t status; + void *scratch = (void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR; + status = grub_vbe_bios_get_mode (scratch); + vbe_mode = *(grub_uint32_t *) scratch; + if (status != GRUB_VBE_STATUS_OK) + return GRUB_ERR_NONE; + } +#else + vbe_mode = 3; +#endif + + /* get_mode_info isn't available for mode 3. */ + if (vbe_mode == 3) + { + grub_memset (&vbe_mode_info, 0, + sizeof (struct grub_vbe_mode_info_block)); + vbe_mode_info.memory_model = GRUB_VBE_MEMORY_MODEL_TEXT; + vbe_mode_info.x_resolution = 80; + vbe_mode_info.y_resolution = 25; + } +#if defined (GRUB_MACHINE_PCBIOS) + else + { + grub_vbe_status_t status; + void *scratch = (void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR; + status = grub_vbe_bios_get_mode_info (vbe_mode, scratch); + if (status != GRUB_VBE_STATUS_OK) + return GRUB_ERR_NONE; + grub_memcpy (&vbe_mode_info, scratch, + sizeof (struct grub_vbe_mode_info_block)); + } +#endif + + if (vbe_mode_info.memory_model == GRUB_VBE_MEMORY_MODEL_TEXT) + { + tag = (struct multiboot_tag_framebuffer *) *ptrorig; + tag->common.type = MULTIBOOT_TAG_TYPE_FRAMEBUFFER; + tag->common.size = 0; + + tag->common.framebuffer_addr = 0xb8000; + + tag->common.framebuffer_pitch = 2 * vbe_mode_info.x_resolution; + tag->common.framebuffer_width = vbe_mode_info.x_resolution; + tag->common.framebuffer_height = vbe_mode_info.y_resolution; + + tag->common.framebuffer_bpp = 16; + + tag->common.framebuffer_type = MULTIBOOT_FRAMEBUFFER_TYPE_EGA_TEXT; + tag->common.size = sizeof (tag->common); + tag->common.reserved = 0; + *ptrorig += ALIGN_UP (tag->common.size, MULTIBOOT_TAG_ALIGN); + } + return GRUB_ERR_NONE; + } +#else + if (driv_id == GRUB_VIDEO_DRIVER_NONE) + return GRUB_ERR_NONE; +#endif + +#if GRUB_MACHINE_HAS_VBE + { + struct multiboot_tag_vbe *tag_vbe = (struct multiboot_tag_vbe *) *ptrorig; + + fill_vbe_tag (tag_vbe); + + *ptrorig += ALIGN_UP (tag_vbe->size, MULTIBOOT_TAG_ALIGN); + } +#endif + + err = grub_video_get_info_and_fini (&mode_info, &framebuffer); + if (err) + return err; + + tag = (struct multiboot_tag_framebuffer *) *ptrorig; + tag->common.type = MULTIBOOT_TAG_TYPE_FRAMEBUFFER; + tag->common.size = 0; + + tag->common.framebuffer_addr = (grub_addr_t) framebuffer; + tag->common.framebuffer_pitch = mode_info.pitch; + + tag->common.framebuffer_width = mode_info.width; + tag->common.framebuffer_height = mode_info.height; + + tag->common.framebuffer_bpp = mode_info.bpp; + + tag->common.reserved = 0; + + if (mode_info.mode_type & GRUB_VIDEO_MODE_TYPE_INDEX_COLOR) + { + unsigned i; + tag->common.framebuffer_type = MULTIBOOT_FRAMEBUFFER_TYPE_INDEXED; + tag->framebuffer_palette_num_colors = mode_info.number_of_colors; + if (tag->framebuffer_palette_num_colors > ARRAY_SIZE (palette)) + tag->framebuffer_palette_num_colors = ARRAY_SIZE (palette); + tag->common.size = sizeof (struct multiboot_tag_framebuffer_common) + + sizeof (multiboot_uint16_t) + tag->framebuffer_palette_num_colors + * sizeof (struct multiboot_color); + for (i = 0; i < tag->framebuffer_palette_num_colors; i++) + { + tag->framebuffer_palette[i].red = palette[i].r; + tag->framebuffer_palette[i].green = palette[i].g; + tag->framebuffer_palette[i].blue = palette[i].b; + } + } + else + { + tag->common.framebuffer_type = MULTIBOOT_FRAMEBUFFER_TYPE_RGB; + tag->framebuffer_red_field_position = mode_info.red_field_pos; + tag->framebuffer_red_mask_size = mode_info.red_mask_size; + tag->framebuffer_green_field_position = mode_info.green_field_pos; + tag->framebuffer_green_mask_size = mode_info.green_mask_size; + tag->framebuffer_blue_field_position = mode_info.blue_field_pos; + tag->framebuffer_blue_mask_size = mode_info.blue_mask_size; + + tag->common.size = sizeof (struct multiboot_tag_framebuffer_common) + 6; + } + *ptrorig += ALIGN_UP (tag->common.size, MULTIBOOT_TAG_ALIGN); + + return GRUB_ERR_NONE; +} + +grub_err_t +grub_multiboot_make_mbi (grub_uint32_t *target) +{ + grub_uint8_t *ptrorig; + grub_uint8_t *mbistart; + grub_err_t err; + grub_size_t bufsize; + grub_relocator_chunk_t ch; + + bufsize = grub_multiboot_get_mbi_size (); + + err = grub_relocator_alloc_chunk_align (grub_multiboot_relocator, &ch, + 0, 0xffffffff - bufsize, + bufsize, MULTIBOOT_TAG_ALIGN, + GRUB_RELOCATOR_PREFERENCE_NONE); + if (err) + return err; + + ptrorig = get_virtual_current_address (ch); +#if defined (__i386__) || defined (__x86_64__) + *target = get_physical_target_address (ch); +#elif defined (__mips) + *target = get_physical_target_address (ch) | 0x80000000; +#else +#error Please complete this +#endif + + mbistart = ptrorig; + ptrorig += 2 * sizeof (grub_uint32_t); + + { + struct multiboot_tag_string *tag = (struct multiboot_tag_string *) ptrorig; + tag->type = MULTIBOOT_TAG_TYPE_CMDLINE; + tag->size = sizeof (struct multiboot_tag_string) + cmdline_size; + grub_memcpy (tag->string, cmdline, cmdline_size); + ptrorig += ALIGN_UP (tag->size, MULTIBOOT_TAG_ALIGN); + } + + { + struct multiboot_tag_string *tag = (struct multiboot_tag_string *) ptrorig; + tag->type = MULTIBOOT_TAG_TYPE_BOOT_LOADER_NAME; + tag->size = sizeof (struct multiboot_tag_string) + sizeof (PACKAGE_STRING); + grub_memcpy (tag->string, PACKAGE_STRING, sizeof (PACKAGE_STRING)); + ptrorig += ALIGN_UP (tag->size, MULTIBOOT_TAG_ALIGN); + } + +#ifdef GRUB_MACHINE_PCBIOS + { + struct grub_apm_info info; + if (grub_apm_get_info (&info)) + { + struct multiboot_tag_apm *tag = (struct multiboot_tag_apm *) ptrorig; + + tag->type = MULTIBOOT_TAG_TYPE_APM; + tag->size = sizeof (struct multiboot_tag_apm); + + tag->cseg = info.cseg; + tag->offset = info.offset; + tag->cseg_16 = info.cseg_16; + tag->dseg = info.dseg; + tag->flags = info.flags; + tag->cseg_len = info.cseg_len; + tag->dseg_len = info.dseg_len; + tag->cseg_16_len = info.cseg_16_len; + tag->version = info.version; + + ptrorig += ALIGN_UP (tag->size, MULTIBOOT_TAG_ALIGN); + } + } +#endif + + { + unsigned i; + struct module *cur; + + for (i = 0, cur = modules; i < modcnt; i++, cur = cur->next) + { + struct multiboot_tag_module *tag + = (struct multiboot_tag_module *) ptrorig; + tag->type = MULTIBOOT_TAG_TYPE_MODULE; + tag->size = sizeof (struct multiboot_tag_module) + cur->cmdline_size; + tag->mod_start = cur->start; + tag->mod_end = tag->mod_start + cur->size; + grub_memcpy (tag->cmdline, cur->cmdline, cur->cmdline_size); + ptrorig += ALIGN_UP (tag->size, MULTIBOOT_TAG_ALIGN); + } + } + + { + struct multiboot_tag_mmap *tag = (struct multiboot_tag_mmap *) ptrorig; + grub_fill_multiboot_mmap (tag); + ptrorig += ALIGN_UP (tag->size, MULTIBOOT_TAG_ALIGN); + } + + { + struct multiboot_tag_elf_sections *tag + = (struct multiboot_tag_elf_sections *) ptrorig; + tag->type = MULTIBOOT_TAG_TYPE_ELF_SECTIONS; + tag->size = sizeof (struct multiboot_tag_elf_sections) + + elf_sec_entsize * elf_sec_num; + grub_memcpy (tag->sections, elf_sections, elf_sec_entsize * elf_sec_num); + tag->num = elf_sec_num; + tag->entsize = elf_sec_entsize; + tag->shndx = elf_sec_shstrndx; + ptrorig += ALIGN_UP (tag->size, MULTIBOOT_TAG_ALIGN); + } + + { + struct multiboot_tag_basic_meminfo *tag + = (struct multiboot_tag_basic_meminfo *) ptrorig; + tag->type = MULTIBOOT_TAG_TYPE_BASIC_MEMINFO; + tag->size = sizeof (struct multiboot_tag_basic_meminfo); + + /* Convert from bytes to kilobytes. */ + tag->mem_lower = grub_mmap_get_lower () / 1024; + tag->mem_upper = grub_mmap_get_upper () / 1024; + ptrorig += ALIGN_UP (tag->size, MULTIBOOT_TAG_ALIGN); + } + + if (bootdev_set) + { + struct multiboot_tag_bootdev *tag + = (struct multiboot_tag_bootdev *) ptrorig; + tag->type = MULTIBOOT_TAG_TYPE_BOOTDEV; + tag->size = sizeof (struct multiboot_tag_bootdev); + + tag->biosdev = biosdev; + tag->slice = slice; + tag->part = part; + ptrorig += ALIGN_UP (tag->size, MULTIBOOT_TAG_ALIGN); + } + + { + err = retrieve_video_parameters (&ptrorig); + if (err) + { + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + } + } + +#if defined (GRUB_MACHINE_EFI) && defined (__x86_64__) + { + struct multiboot_tag_efi64 *tag = (struct multiboot_tag_efi64 *) ptrorig; + tag->type = MULTIBOOT_TAG_TYPE_EFI64; + tag->size = sizeof (*tag); + tag->pointer = (grub_addr_t) grub_efi_system_table; + ptrorig += ALIGN_UP (tag->size, MULTIBOOT_TAG_ALIGN); + } +#endif + +#if defined (GRUB_MACHINE_EFI) && defined (__i386__) + { + struct multiboot_tag_efi32 *tag = (struct multiboot_tag_efi32 *) ptrorig; + tag->type = MULTIBOOT_TAG_TYPE_EFI32; + tag->size = sizeof (*tag); + tag->pointer = (grub_addr_t) grub_efi_system_table; + ptrorig += ALIGN_UP (tag->size, MULTIBOOT_TAG_ALIGN); + } +#endif + +#if GRUB_MACHINE_HAS_ACPI + { + struct multiboot_tag_old_acpi *tag = (struct multiboot_tag_old_acpi *) + ptrorig; + struct grub_acpi_rsdp_v10 *a = grub_acpi_get_rsdpv1 (); + if (a) + { + tag->type = MULTIBOOT_TAG_TYPE_ACPI_OLD; + tag->size = sizeof (*tag) + sizeof (*a); + grub_memcpy (tag->rsdp, a, sizeof (*a)); + ptrorig += ALIGN_UP (tag->size, MULTIBOOT_TAG_ALIGN); + } + } + + { + struct multiboot_tag_new_acpi *tag = (struct multiboot_tag_new_acpi *) + ptrorig; + struct grub_acpi_rsdp_v20 *a = grub_acpi_get_rsdpv2 (); + if (a) + { + tag->type = MULTIBOOT_TAG_TYPE_ACPI_NEW; + tag->size = sizeof (*tag) + a->length; + grub_memcpy (tag->rsdp, a, a->length); + ptrorig += ALIGN_UP (tag->size, MULTIBOOT_TAG_ALIGN); + } + } +#endif + + { + struct multiboot_tag *tag = (struct multiboot_tag *) ptrorig; + tag->type = MULTIBOOT_TAG_TYPE_END; + tag->size = sizeof (struct multiboot_tag); + ptrorig += ALIGN_UP (tag->size, MULTIBOOT_TAG_ALIGN); + } + + ((grub_uint32_t *) mbistart)[0] = ptrorig - mbistart; + ((grub_uint32_t *) mbistart)[1] = 0; + + return GRUB_ERR_NONE; +} + +void +grub_multiboot_free_mbi (void) +{ + struct module *cur, *next; + + cmdline_size = 0; + total_modcmd = 0; + modcnt = 0; + grub_free (cmdline); + cmdline = NULL; + bootdev_set = 0; + + for (cur = modules; cur; cur = next) + { + next = cur->next; + grub_free (cur->cmdline); + grub_free (cur); + } + modules = NULL; + modules_last = NULL; +} + +grub_err_t +grub_multiboot_init_mbi (int argc, char *argv[]) +{ + grub_ssize_t len = 0; + char *p; + int i; + + grub_multiboot_free_mbi (); + + for (i = 0; i < argc; i++) + len += grub_strlen (argv[i]) + 1; + if (len == 0) + len = 1; + + cmdline = p = grub_malloc (len); + if (! cmdline) + return grub_errno; + cmdline_size = len; + + for (i = 0; i < argc; i++) + { + p = grub_stpcpy (p, argv[i]); + *(p++) = ' '; + } + + /* Remove the space after the last word. */ + if (p != cmdline) + p--; + *p = '\0'; + + return GRUB_ERR_NONE; +} + +grub_err_t +grub_multiboot_add_module (grub_addr_t start, grub_size_t size, + int argc, char *argv[]) +{ + struct module *newmod; + char *p; + grub_ssize_t len = 0; + int i; + + newmod = grub_malloc (sizeof (*newmod)); + if (!newmod) + return grub_errno; + newmod->start = start; + newmod->size = size; + + for (i = 0; i < argc; i++) + len += grub_strlen (argv[i]) + 1; + + if (len == 0) + len = 1; + + newmod->cmdline = p = grub_malloc (len); + if (! newmod->cmdline) + { + grub_free (newmod); + return grub_errno; + } + newmod->cmdline_size = len; + total_modcmd += ALIGN_UP (len, MULTIBOOT_TAG_ALIGN); + + for (i = 0; i < argc; i++) + { + p = grub_stpcpy (p, argv[i]); + *(p++) = ' '; + } + + /* Remove the space after the last word. */ + if (p != newmod->cmdline) + p--; + *p = '\0'; + + if (modules_last) + modules_last->next = newmod; + else + { + modules = newmod; + modules_last->next = NULL; + } + modules_last = newmod; + + modcnt++; + + return GRUB_ERR_NONE; +} + +void +grub_multiboot_set_bootdev (void) +{ + grub_device_t dev; + + slice = ~0; + part = ~0; + +#ifdef GRUB_MACHINE_PCBIOS + biosdev = grub_get_root_biosnumber (); +#else + biosdev = 0xffffffff; +#endif + + if (biosdev == 0xffffffff) + return; + + dev = grub_device_open (0); + if (dev && dev->disk && dev->disk->partition) + { + if (dev->disk->partition->parent) + { + part = dev->disk->partition->number; + slice = dev->disk->partition->parent->number; + } + else + slice = dev->disk->partition->number; + } + if (dev) + grub_device_close (dev); + + bootdev_set = 1; +} diff --git a/grub-core/loader/powerpc/ieee1275/linux.c b/grub-core/loader/powerpc/ieee1275/linux.c new file mode 100644 index 0000000..12a3fa9 --- /dev/null +++ b/grub-core/loader/powerpc/ieee1275/linux.c @@ -0,0 +1,385 @@ +/* linux.c - boot Linux */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2003,2004,2005,2007,2009 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <grub/elf.h> +#include <grub/elfload.h> +#include <grub/loader.h> +#include <grub/dl.h> +#include <grub/mm.h> +#include <grub/misc.h> +#include <grub/ieee1275/ieee1275.h> +#include <grub/command.h> +#include <grub/i18n.h> +#include <grub/memory.h> +#include <grub/lib/cmdline.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +#define ELF32_LOADMASK (0xc0000000UL) +#define ELF64_LOADMASK (0xc000000000000000ULL) + +static grub_dl_t my_mod; + +static int loaded; + +static grub_addr_t initrd_addr; +static grub_size_t initrd_size; + +static grub_addr_t linux_addr; +static grub_size_t linux_size; + +static char *linux_args; + +typedef void (*kernel_entry_t) (void *, unsigned long, int (void *), + unsigned long, unsigned long); + +static grub_addr_t +grub_linux_claimmap_iterate (grub_addr_t target, grub_size_t size, + grub_size_t align) +{ + grub_addr_t found_addr = (grub_addr_t) -1; + + auto int NESTED_FUNC_ATTR alloc_mem (grub_uint64_t addr, grub_uint64_t len, + grub_memory_type_t type); + int NESTED_FUNC_ATTR alloc_mem (grub_uint64_t addr, grub_uint64_t len, + grub_memory_type_t type) + { + grub_uint64_t end = addr + len; + addr = ALIGN_UP (addr, align); + target = ALIGN_UP (target, align); + + /* Target above the memory chunk. */ + if (type != GRUB_MEMORY_AVAILABLE || target > end) + return 0; + + /* Target inside the memory chunk. */ + if (target >= addr && target < end && size <= end - target) + { + if (grub_claimmap (target, size) == GRUB_ERR_NONE) + { + found_addr = target; + return 1; + } + } + /* Target below the memory chunk. */ + if (target < addr && addr + size <= end) + { + if (grub_claimmap (addr, size) == GRUB_ERR_NONE) + { + found_addr = addr; + return 1; + } + } + return 0; + } + + grub_machine_mmap_iterate (alloc_mem); + + return found_addr; +} + +static grub_err_t +grub_linux_boot (void) +{ + kernel_entry_t linuxmain; + grub_ssize_t actual; + + /* Set the command line arguments. */ + grub_ieee1275_set_property (grub_ieee1275_chosen, "bootargs", linux_args, + grub_strlen (linux_args) + 1, &actual); + + grub_dprintf ("loader", "Entry point: 0x%x\n", linux_addr); + grub_dprintf ("loader", "Initrd at: 0x%x, size 0x%x\n", initrd_addr, + initrd_size); + grub_dprintf ("loader", "Boot arguments: %s\n", linux_args); + grub_dprintf ("loader", "Jumping to Linux...\n"); + + /* Boot the kernel. */ + linuxmain = (kernel_entry_t) linux_addr; + linuxmain ((void *) initrd_addr, initrd_size, grub_ieee1275_entry_fn, 0, 0); + + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_linux_release_mem (void) +{ + grub_free (linux_args); + linux_args = 0; + + if (linux_addr && grub_ieee1275_release (linux_addr, linux_size)) + return grub_error (GRUB_ERR_OUT_OF_MEMORY, "cannot release memory"); + + if (initrd_addr && grub_ieee1275_release (initrd_addr, initrd_size)) + return grub_error (GRUB_ERR_OUT_OF_MEMORY, "cannot release memory"); + + linux_addr = 0; + initrd_addr = 0; + + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_linux_unload (void) +{ + grub_err_t err; + + err = grub_linux_release_mem (); + grub_dl_unref (my_mod); + + loaded = 0; + + return err; +} + +static grub_err_t +grub_linux_load32 (grub_elf_t elf) +{ + Elf32_Addr base_addr; + grub_addr_t seg_addr; + grub_uint32_t align; + grub_uint32_t offset; + Elf32_Addr entry; + + linux_size = grub_elf32_size (elf, &base_addr, &align); + if (linux_size == 0) + return grub_errno; + /* Pad it; the kernel scribbles over memory beyond its load address. */ + linux_size += 0x100000; + + /* Linux's entry point incorrectly contains a virtual address. */ + entry = elf->ehdr.ehdr32.e_entry & ~ELF32_LOADMASK; + + /* Linux's incorrectly contains a virtual address. */ + base_addr &= ~ELF32_LOADMASK; + offset = entry - base_addr; + + /* On some systems, firmware occupies the memory we're trying to use. + * Happily, Linux can be loaded anywhere (it relocates itself). Iterate + * until we find an open area. */ + seg_addr = grub_linux_claimmap_iterate (base_addr & ~ELF32_LOADMASK, linux_size, align); + if (seg_addr == (grub_addr_t) -1) + return grub_error (GRUB_ERR_OUT_OF_MEMORY, "couldn't claim memory"); + + linux_addr = seg_addr + offset; + + /* Now load the segments into the area we claimed. */ + auto grub_err_t offset_phdr (Elf32_Phdr *phdr, grub_addr_t *addr, int *do_load); + grub_err_t offset_phdr (Elf32_Phdr *phdr, grub_addr_t *addr, int *do_load) + { + if (phdr->p_type != PT_LOAD) + { + *do_load = 0; + return 0; + } + *do_load = 1; + + *addr = (phdr->p_paddr - base_addr) + seg_addr; + return 0; + } + return grub_elf32_load (elf, offset_phdr, 0, 0); +} + +static grub_err_t +grub_linux_load64 (grub_elf_t elf) +{ + Elf64_Addr base_addr; + grub_addr_t seg_addr; + grub_uint64_t align; + grub_uint64_t offset; + Elf64_Addr entry; + + linux_size = grub_elf64_size (elf, &base_addr, &align); + if (linux_size == 0) + return grub_errno; + /* Pad it; the kernel scribbles over memory beyond its load address. */ + linux_size += 0x100000; + + base_addr &= ~ELF64_LOADMASK; + entry = elf->ehdr.ehdr64.e_entry & ~ELF64_LOADMASK; + offset = entry - base_addr; + /* Linux's incorrectly contains a virtual address. */ + + /* On some systems, firmware occupies the memory we're trying to use. + * Happily, Linux can be loaded anywhere (it relocates itself). Iterate + * until we find an open area. */ + seg_addr = grub_linux_claimmap_iterate (base_addr & ~ELF64_LOADMASK, linux_size, align); + if (seg_addr == (grub_addr_t) -1) + return grub_error (GRUB_ERR_OUT_OF_MEMORY, "couldn't claim memory"); + + linux_addr = seg_addr + offset; + + /* Now load the segments into the area we claimed. */ + auto grub_err_t offset_phdr (Elf64_Phdr *phdr, grub_addr_t *addr, int *do_load); + grub_err_t offset_phdr (Elf64_Phdr *phdr, grub_addr_t *addr, int *do_load) + { + if (phdr->p_type != PT_LOAD) + { + *do_load = 0; + return 0; + } + *do_load = 1; + + *addr = (phdr->p_paddr - base_addr) + seg_addr; + return 0; + } + return grub_elf64_load (elf, offset_phdr, 0, 0); +} + +static grub_err_t +grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_elf_t elf = 0; + int size; + + grub_dl_ref (my_mod); + + if (argc == 0) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, "no kernel specified"); + goto out; + } + + elf = grub_elf_open (argv[0]); + if (! elf) + goto out; + + if (elf->ehdr.ehdr32.e_type != ET_EXEC && elf->ehdr.ehdr32.e_type != ET_DYN) + { + grub_error (GRUB_ERR_UNKNOWN_OS, + "this ELF file is not of the right type"); + goto out; + } + + /* Release the previously used memory. */ + grub_loader_unset (); + + if (grub_elf_is_elf32 (elf)) + grub_linux_load32 (elf); + else + if (grub_elf_is_elf64 (elf)) + grub_linux_load64 (elf); + else + { + grub_error (GRUB_ERR_BAD_FILE_TYPE, "unknown ELF class"); + goto out; + } + + size = grub_loader_cmdline_size(argc, argv); + linux_args = grub_malloc (size + sizeof (LINUX_IMAGE)); + if (! linux_args) + goto out; + + /* Create kernel command line. */ + grub_memcpy (linux_args, LINUX_IMAGE, sizeof (LINUX_IMAGE)); + grub_create_loader_cmdline (argc, argv, linux_args + sizeof (LINUX_IMAGE) - 1, + size); + +out: + + if (elf) + grub_elf_close (elf); + + if (grub_errno != GRUB_ERR_NONE) + { + grub_linux_release_mem (); + grub_dl_unref (my_mod); + loaded = 0; + } + else + { + grub_loader_set (grub_linux_boot, grub_linux_unload, 1); + initrd_addr = 0; + loaded = 1; + } + + return grub_errno; +} + +static grub_err_t +grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_file_t file = 0; + grub_ssize_t size; + grub_addr_t first_addr; + grub_addr_t addr; + + if (argc == 0) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, "no initrd specified"); + goto fail; + } + + if (!loaded) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, "you need to load the kernel first"); + goto fail; + } + + grub_file_filter_disable_compression (); + file = grub_file_open (argv[0]); + if (! file) + goto fail; + + first_addr = linux_addr + linux_size; + size = grub_file_size (file); + + /* Attempt to claim at a series of addresses until successful in + the same way that grub_rescue_cmd_linux does. */ + addr = grub_linux_claimmap_iterate (first_addr, size, 0x100000); + if (addr == (grub_addr_t) -1) + goto fail; + + grub_dprintf ("loader", "Loading initrd at 0x%x, size 0x%x\n", addr, size); + + if (grub_file_read (file, (void *) addr, size) != size) + { + grub_ieee1275_release (addr, size); + grub_error (GRUB_ERR_FILE_READ_ERROR, "couldn't read file"); + goto fail; + } + + initrd_addr = addr; + initrd_size = size; + + fail: + if (file) + grub_file_close (file); + + return grub_errno; +} + +static grub_command_t cmd_linux, cmd_initrd; + +GRUB_MOD_INIT(linux) +{ + cmd_linux = grub_register_command ("linux", grub_cmd_linux, + 0, N_("Load Linux.")); + cmd_initrd = grub_register_command ("initrd", grub_cmd_initrd, + 0, N_("Load initrd.")); + my_mod = mod; +} + +GRUB_MOD_FINI(linux) +{ + grub_unregister_command (cmd_linux); + grub_unregister_command (cmd_initrd); +} diff --git a/grub-core/loader/sparc64/ieee1275/linux.c b/grub-core/loader/sparc64/ieee1275/linux.c new file mode 100644 index 0000000..8ed61f8 --- /dev/null +++ b/grub-core/loader/sparc64/ieee1275/linux.c @@ -0,0 +1,525 @@ +/* linux.c - boot Linux */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2003, 2004, 2005, 2007, 2009 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <grub/elf.h> +#include <grub/elfload.h> +#include <grub/loader.h> +#include <grub/dl.h> +#include <grub/mm.h> +#include <grub/misc.h> +#include <grub/ieee1275/ieee1275.h> +#include <grub/command.h> +#include <grub/i18n.h> +#include <grub/memory.h> +#include <grub/lib/cmdline.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +static grub_dl_t my_mod; + +static int loaded; + +/* /virtual-memory/translations property layout */ +struct grub_ieee1275_translation { + grub_uint64_t vaddr; + grub_uint64_t size; + grub_uint64_t data; +}; + +static struct grub_ieee1275_translation *of_trans; +static int of_num_trans; + +static grub_addr_t phys_base; +static grub_addr_t grub_phys_start; +static grub_addr_t grub_phys_end; + +static grub_addr_t initrd_addr; +static grub_addr_t initrd_paddr; +static grub_size_t initrd_size; + +static Elf64_Addr linux_entry; +static grub_addr_t linux_addr; +static grub_addr_t linux_paddr; +static grub_size_t linux_size; + +static char *linux_args; + +struct linux_bootstr_info { + int len, valid; + char buf[]; +}; + +struct linux_hdrs { + /* All HdrS versions support these fields. */ + unsigned int start_insns[2]; + char magic[4]; /* "HdrS" */ + unsigned int linux_kernel_version; /* LINUX_VERSION_CODE */ + unsigned short hdrs_version; + unsigned short root_flags; + unsigned short root_dev; + unsigned short ram_flags; + unsigned int __deprecated_ramdisk_image; + unsigned int ramdisk_size; + + /* HdrS versions 0x0201 and higher only */ + char *reboot_command; + + /* HdrS versions 0x0202 and higher only */ + struct linux_bootstr_info *bootstr_info; + + /* HdrS versions 0x0301 and higher only */ + unsigned long ramdisk_image; +}; + +static grub_err_t +grub_linux_boot (void) +{ + struct linux_bootstr_info *bp; + struct linux_hdrs *hp; + grub_addr_t addr; + + hp = (struct linux_hdrs *) linux_addr; + + /* Any pointer we dereference in the kernel image must be relocated + to where we actually loaded the kernel. */ + addr = (grub_addr_t) hp->bootstr_info; + addr += (linux_addr - linux_entry); + bp = (struct linux_bootstr_info *) addr; + + /* Set the command line arguments, unless the kernel has been + built with a fixed CONFIG_CMDLINE. */ + if (!bp->valid) + { + int len = grub_strlen (linux_args) + 1; + if (bp->len < len) + len = bp->len; + memcpy(bp->buf, linux_args, len); + bp->buf[len-1] = '\0'; + bp->valid = 1; + } + + if (initrd_addr) + { + /* The kernel expects the physical address, adjusted relative + to the lowest address advertised in "/memory"'s available + property. + + The history of this is that back when the kernel only supported + specifying a 32-bit ramdisk address, this was the way to still + be able to specify the ramdisk physical address even if memory + started at some place above 4GB. + + The magic 0x400000 is KERNBASE, I have no idea why SILO adds + that term into the address, but it does and thus we have to do + it too as this is what the kernel expects. */ + hp->ramdisk_image = initrd_paddr - phys_base + 0x400000; + hp->ramdisk_size = initrd_size; + } + + grub_dprintf ("loader", "Entry point: 0x%lx\n", linux_addr); + grub_dprintf ("loader", "Initrd at: 0x%lx, size 0x%lx\n", initrd_addr, + initrd_size); + grub_dprintf ("loader", "Boot arguments: %s\n", linux_args); + grub_dprintf ("loader", "Jumping to Linux...\n"); + + /* Boot the kernel. */ + asm volatile ("sethi %hi(grub_ieee1275_entry_fn), %o1\n" + "ldx [%o1 + %lo(grub_ieee1275_entry_fn)], %o4\n" + "sethi %hi(grub_ieee1275_original_stack), %o1\n" + "ldx [%o1 + %lo(grub_ieee1275_original_stack)], %o6\n" + "sethi %hi(linux_addr), %o1\n" + "ldx [%o1 + %lo(linux_addr)], %o5\n" + "mov %g0, %o0\n" + "mov %g0, %o2\n" + "mov %g0, %o3\n" + "jmp %o5\n" + "mov %g0, %o1\n"); + + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_linux_release_mem (void) +{ + grub_free (linux_args); + linux_args = 0; + linux_addr = 0; + initrd_addr = 0; + + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_linux_unload (void) +{ + grub_err_t err; + + err = grub_linux_release_mem (); + grub_dl_unref (my_mod); + + loaded = 0; + + return err; +} + +#define FOUR_MB (4 * 1024 * 1024) + +static grub_addr_t +alloc_phys (grub_addr_t size) +{ + grub_addr_t ret = (grub_addr_t) -1; + + auto int NESTED_FUNC_ATTR choose (grub_uint64_t addr, grub_uint64_t len, + grub_memory_type_t type); + int NESTED_FUNC_ATTR choose (grub_uint64_t addr, grub_uint64_t len, + grub_memory_type_t type) + { + grub_addr_t end = addr + len; + + if (type != 1) + return 0; + + addr = ALIGN_UP (addr, FOUR_MB); + if (addr + size >= end) + return 0; + + if (addr >= grub_phys_start && addr < grub_phys_end) + { + addr = ALIGN_UP (grub_phys_end, FOUR_MB); + if (addr + size >= end) + return 0; + } + if ((addr + size) >= grub_phys_start + && (addr + size) < grub_phys_end) + { + addr = ALIGN_UP (grub_phys_end, FOUR_MB); + if (addr + size >= end) + return 0; + } + + if (loaded) + { + grub_addr_t linux_end = ALIGN_UP (linux_paddr + linux_size, FOUR_MB); + + if (addr >= linux_paddr && addr < linux_end) + { + addr = linux_end; + if (addr + size >= end) + return 0; + } + if ((addr + size) >= linux_paddr + && (addr + size) < linux_end) + { + addr = linux_end; + if (addr + size >= end) + return 0; + } + } + + ret = addr; + return 1; + } + + grub_machine_mmap_iterate (choose); + + return ret; +} + +static grub_err_t +grub_linux_load64 (grub_elf_t elf) +{ + grub_addr_t off, paddr, base; + int ret; + + linux_entry = elf->ehdr.ehdr64.e_entry; + linux_addr = 0x40004000; + off = 0x4000; + linux_size = grub_elf64_size (elf, 0, 0); + if (linux_size == 0) + return grub_errno; + + grub_dprintf ("loader", "Attempting to claim at 0x%lx, size 0x%lx.\n", + linux_addr, linux_size); + + paddr = alloc_phys (linux_size + off); + if (paddr == (grub_addr_t) -1) + return grub_error (GRUB_ERR_OUT_OF_MEMORY, + "couldn't allocate physical memory"); + ret = grub_ieee1275_map (paddr, linux_addr - off, + linux_size + off, IEEE1275_MAP_DEFAULT); + if (ret) + return grub_error (GRUB_ERR_OUT_OF_MEMORY, + "couldn't map physical memory"); + + grub_dprintf ("loader", "Loading Linux at vaddr 0x%lx, paddr 0x%lx, size 0x%lx\n", + linux_addr, paddr, linux_size); + + linux_paddr = paddr; + + base = linux_entry - off; + + /* Now load the segments into the area we claimed. */ + auto grub_err_t offset_phdr (Elf64_Phdr *phdr, grub_addr_t *addr, int *do_load); + grub_err_t offset_phdr (Elf64_Phdr *phdr, grub_addr_t *addr, int *do_load) + { + if (phdr->p_type != PT_LOAD) + { + *do_load = 0; + return 0; + } + *do_load = 1; + + /* Adjust the program load address to linux_addr. */ + *addr = (phdr->p_paddr - base) + (linux_addr - off); + return 0; + } + return grub_elf64_load (elf, offset_phdr, 0, 0); +} + +static grub_err_t +grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_file_t file = 0; + grub_elf_t elf = 0; + int size; + + grub_dl_ref (my_mod); + + if (argc == 0) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, "no kernel specified"); + goto out; + } + + file = grub_file_open (argv[0]); + if (!file) + goto out; + + elf = grub_elf_file (file); + if (! elf) + goto out; + + if (elf->ehdr.ehdr32.e_type != ET_EXEC) + { + grub_error (GRUB_ERR_UNKNOWN_OS, + "this ELF file is not of the right type"); + goto out; + } + + /* Release the previously used memory. */ + grub_loader_unset (); + + if (grub_elf_is_elf64 (elf)) + grub_linux_load64 (elf); + else + { + grub_error (GRUB_ERR_BAD_FILE_TYPE, "unknown ELF class"); + goto out; + } + + size = grub_loader_cmdline_size(argc, argv); + + linux_args = grub_malloc (size + sizeof (LINUX_IMAGE)); + if (! linux_args) + goto out; + + /* Create kernel command line. */ + grub_memcpy (linux_args, LINUX_IMAGE, sizeof (LINUX_IMAGE)); + grub_create_loader_cmdline (argc, argv, linux_args + sizeof (LINUX_IMAGE) - 1, + size); + +out: + if (elf) + grub_elf_close (elf); + else if (file) + grub_file_close (file); + + if (grub_errno != GRUB_ERR_NONE) + { + grub_linux_release_mem (); + grub_dl_unref (my_mod); + loaded = 0; + } + else + { + grub_loader_set (grub_linux_boot, grub_linux_unload, 1); + initrd_addr = 0; + loaded = 1; + } + + return grub_errno; +} + +static grub_err_t +grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_file_t file = 0; + grub_ssize_t size; + grub_addr_t paddr; + grub_addr_t addr; + int ret; + + if (argc == 0) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, "no initrd specified"); + goto fail; + } + + if (!loaded) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, "you need to load the kernel first"); + goto fail; + } + + grub_file_filter_disable_compression (); + file = grub_file_open (argv[0]); + if (! file) + goto fail; + + addr = 0x60000000; + size = grub_file_size (file); + + paddr = alloc_phys (size); + if (paddr == (grub_addr_t) -1) + { + grub_error (GRUB_ERR_OUT_OF_MEMORY, + "couldn't allocate physical memory"); + goto fail; + } + ret = grub_ieee1275_map (paddr, addr, size, IEEE1275_MAP_DEFAULT); + if (ret) + { + grub_error (GRUB_ERR_OUT_OF_MEMORY, + "couldn't map physical memory"); + goto fail; + } + + grub_dprintf ("loader", "Loading initrd at vaddr 0x%lx, paddr 0x%lx, size 0x%lx\n", + addr, paddr, size); + + if (grub_file_read (file, (void *) addr, size) != size) + { + grub_error (GRUB_ERR_FILE_READ_ERROR, "couldn't read file"); + goto fail; + } + + initrd_addr = addr; + initrd_paddr = paddr; + initrd_size = size; + + fail: + if (file) + grub_file_close (file); + + return grub_errno; +} + +static void +determine_phys_base (void) +{ + auto int NESTED_FUNC_ATTR get_physbase (grub_uint64_t addr, grub_uint64_t len __attribute__((unused)), grub_uint32_t type); + int NESTED_FUNC_ATTR get_physbase (grub_uint64_t addr, grub_uint64_t len __attribute__((unused)), grub_uint32_t type) + { + if (type != 1) + return 0; + if (addr < phys_base) + phys_base = addr; + return 0; + } + + phys_base = ~(grub_uint64_t) 0; + grub_machine_mmap_iterate (get_physbase); +} + +static void +fetch_translations (void) +{ + grub_ieee1275_phandle_t node; + grub_ssize_t actual; + int i; + + if (grub_ieee1275_finddevice ("/virtual-memory", &node)) + { + grub_printf ("Cannot find /virtual-memory node.\n"); + return; + } + + if (grub_ieee1275_get_property_length (node, "translations", &actual)) + { + grub_printf ("Cannot find /virtual-memory/translations size.\n"); + return; + } + + of_trans = grub_malloc (actual); + if (!of_trans) + { + grub_printf ("Cannot allocate translations buffer.\n"); + return; + } + + if (grub_ieee1275_get_property (node, "translations", of_trans, actual, &actual)) + { + grub_printf ("Cannot fetch /virtual-memory/translations property.\n"); + return; + } + + of_num_trans = actual / sizeof(struct grub_ieee1275_translation); + + for (i = 0; i < of_num_trans; i++) + { + struct grub_ieee1275_translation *p = &of_trans[i]; + + if (p->vaddr == 0x2000) + { + grub_addr_t phys, tte = p->data; + + phys = tte & ~(0xff00000000001fffULL); + + grub_phys_start = phys; + grub_phys_end = grub_phys_start + p->size; + grub_dprintf ("loader", "Grub lives at phys_start[%lx] phys_end[%lx]\n", + (unsigned long) grub_phys_start, + (unsigned long) grub_phys_end); + break; + } + } +} + + +static grub_command_t cmd_linux, cmd_initrd; + +GRUB_MOD_INIT(linux) +{ + determine_phys_base (); + fetch_translations (); + + cmd_linux = grub_register_command ("linux", grub_cmd_linux, + 0, N_("Load Linux.")); + cmd_initrd = grub_register_command ("initrd", grub_cmd_initrd, + 0, N_("Load initrd.")); + my_mod = mod; +} + +GRUB_MOD_FINI(linux) +{ + grub_unregister_command (cmd_linux); + grub_unregister_command (cmd_initrd); +} diff --git a/grub-core/loader/xnu.c b/grub-core/loader/xnu.c new file mode 100644 index 0000000..a98d60c --- /dev/null +++ b/grub-core/loader/xnu.c @@ -0,0 +1,1498 @@ +/* xnu.c - load xnu kernel. Thanks to Florian Idelberger for all the + time he spent testing this + */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2009 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <grub/file.h> +#include <grub/xnu.h> +#include <grub/cpu/xnu.h> +#include <grub/mm.h> +#include <grub/dl.h> +#include <grub/loader.h> +#include <grub/machoload.h> +#include <grub/macho.h> +#include <grub/cpu/macho.h> +#include <grub/command.h> +#include <grub/misc.h> +#include <grub/extcmd.h> +#include <grub/env.h> +#include <grub/i18n.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +#if defined (__i386) && !defined (GRUB_MACHINE_EFI) +#include <grub/autoefi.h> +#endif + +struct grub_xnu_devtree_key *grub_xnu_devtree_root = 0; +static int driverspackagenum = 0; +static int driversnum = 0; +int grub_xnu_is_64bit = 0; + +grub_addr_t grub_xnu_heap_target_start = 0; +grub_size_t grub_xnu_heap_size = 0; +struct grub_relocator *grub_xnu_relocator; + +static grub_err_t +grub_xnu_register_memory (char *prefix, int *suffix, + grub_addr_t addr, grub_size_t size); +grub_err_t +grub_xnu_heap_malloc (int size, void **src, grub_addr_t *target) +{ + grub_err_t err; + grub_relocator_chunk_t ch; + + err = grub_relocator_alloc_chunk_addr (grub_xnu_relocator, &ch, + grub_xnu_heap_target_start + + grub_xnu_heap_size, size); + if (err) + return err; + + *src = get_virtual_current_address (ch); + *target = grub_xnu_heap_target_start + grub_xnu_heap_size; + grub_xnu_heap_size += size; + grub_dprintf ("xnu", "val=%p\n", *src); + return GRUB_ERR_NONE; +} + +/* Make sure next block of the heap will be aligned. + Please notice: aligned are pointers AFTER relocation + and not the current ones. */ +grub_err_t +grub_xnu_align_heap (int align) +{ + grub_xnu_heap_size + = ALIGN_UP (grub_xnu_heap_target_start+ grub_xnu_heap_size, align) + - grub_xnu_heap_target_start; + return GRUB_ERR_NONE; +} + +/* Free subtree pointed by CUR. */ +void +grub_xnu_free_devtree (struct grub_xnu_devtree_key *cur) +{ + struct grub_xnu_devtree_key *d; + while (cur) + { + grub_free (cur->name); + if (cur->datasize == -1) + grub_xnu_free_devtree (cur->first_child); + else if (cur->data) + grub_free (cur->data); + d = cur->next; + grub_free (cur); + cur = d; + } +} + +/* Compute the size of device tree in xnu format. */ +static grub_size_t +grub_xnu_writetree_get_size (struct grub_xnu_devtree_key *start, char *name) +{ + grub_size_t ret; + struct grub_xnu_devtree_key *cur; + + /* Key header. */ + ret = 2 * sizeof (grub_uint32_t); + + /* "name" value. */ + ret += 32 + sizeof (grub_uint32_t) + + grub_strlen (name) + 4 + - (grub_strlen (name) % 4); + + for (cur = start; cur; cur = cur->next) + if (cur->datasize != -1) + { + int align_overhead; + + align_overhead = 4 - (cur->datasize % 4); + if (align_overhead == 4) + align_overhead = 0; + ret += 32 + sizeof (grub_uint32_t) + cur->datasize + align_overhead; + } + else + ret += grub_xnu_writetree_get_size (cur->first_child, cur->name); + return ret; +} + +/* Write devtree in XNU format at curptr assuming the head is named NAME.*/ +static void * +grub_xnu_writetree_toheap_real (void *curptr, + struct grub_xnu_devtree_key *start, char *name) +{ + struct grub_xnu_devtree_key *cur; + int nkeys = 0, nvals = 0; + for (cur = start; cur; cur = cur->next) + { + if (cur->datasize == -1) + nkeys++; + else + nvals++; + } + /* For the name. */ + nvals++; + + *((grub_uint32_t *) curptr) = nvals; + curptr = ((grub_uint32_t *) curptr) + 1; + *((grub_uint32_t *) curptr) = nkeys; + curptr = ((grub_uint32_t *) curptr) + 1; + + /* First comes "name" value. */ + grub_memset (curptr, 0, 32); + grub_memcpy (curptr, "name", 4); + curptr = ((grub_uint8_t *) curptr) + 32; + *((grub_uint32_t *)curptr) = grub_strlen (name) + 1; + curptr = ((grub_uint32_t *) curptr) + 1; + grub_memcpy (curptr, name, grub_strlen (name)); + curptr = ((grub_uint8_t *) curptr) + grub_strlen (name); + grub_memset (curptr, 0, 4 - (grub_strlen (name) % 4)); + curptr = ((grub_uint8_t *) curptr) + (4 - (grub_strlen (name) % 4)); + + /* Then the other values. */ + for (cur = start; cur; cur = cur->next) + if (cur->datasize != -1) + { + int align_overhead; + + align_overhead = 4 - (cur->datasize % 4); + if (align_overhead == 4) + align_overhead = 0; + grub_memset (curptr, 0, 32); + grub_strncpy (curptr, cur->name, 31); + curptr = ((grub_uint8_t *) curptr) + 32; + *((grub_uint32_t *) curptr) = cur->datasize; + curptr = ((grub_uint32_t *) curptr) + 1; + grub_memcpy (curptr, cur->data, cur->datasize); + curptr = ((grub_uint8_t *) curptr) + cur->datasize; + grub_memset (curptr, 0, align_overhead); + curptr = ((grub_uint8_t *) curptr) + align_overhead; + } + + /* And then the keys. Recursively use this function. */ + for (cur = start; cur; cur = cur->next) + if (cur->datasize == -1) + if (!(curptr = grub_xnu_writetree_toheap_real (curptr, + cur->first_child, + cur->name))) + return 0; + return curptr; +} + +grub_err_t +grub_xnu_writetree_toheap (grub_addr_t *target, grub_size_t *size) +{ + struct grub_xnu_devtree_key *chosen; + struct grub_xnu_devtree_key *memorymap; + struct grub_xnu_devtree_key *driverkey; + struct grub_xnu_extdesc *extdesc; + grub_err_t err; + void *src; + + err = grub_xnu_align_heap (GRUB_XNU_PAGESIZE); + if (err) + return err; + + /* Device tree itself is in the memory map of device tree. */ + /* Create a dummy value in memory-map. */ + chosen = grub_xnu_create_key (&grub_xnu_devtree_root, "chosen"); + if (! chosen) + return grub_errno; + memorymap = grub_xnu_create_key (&(chosen->first_child), "memory-map"); + if (! memorymap) + return grub_errno; + + driverkey = (struct grub_xnu_devtree_key *) grub_malloc (sizeof (*driverkey)); + if (! driverkey) + return grub_error (GRUB_ERR_OUT_OF_MEMORY, "can't write device tree"); + driverkey->name = grub_strdup ("DeviceTree"); + if (! driverkey->name) + return grub_error (GRUB_ERR_OUT_OF_MEMORY, "can't write device tree"); + driverkey->datasize = sizeof (*extdesc); + driverkey->next = memorymap->first_child; + memorymap->first_child = driverkey; + driverkey->data = extdesc + = (struct grub_xnu_extdesc *) grub_malloc (sizeof (*extdesc)); + if (! driverkey->data) + return grub_error (GRUB_ERR_OUT_OF_MEMORY, "can't write device tree"); + + /* Allocate the space based on the size with dummy value. */ + *size = grub_xnu_writetree_get_size (grub_xnu_devtree_root, "/"); + err = grub_xnu_heap_malloc (ALIGN_UP (*size + 1, GRUB_XNU_PAGESIZE), + &src, target); + if (err) + return err; + + /* Put real data in the dummy. */ + extdesc->addr = *target; + extdesc->size = (grub_uint32_t) *size; + + /* Write the tree to heap. */ + grub_xnu_writetree_toheap_real (src, grub_xnu_devtree_root, "/"); + return GRUB_ERR_NONE; +} + +/* Find a key or value in parent key. */ +struct grub_xnu_devtree_key * +grub_xnu_find_key (struct grub_xnu_devtree_key *parent, char *name) +{ + struct grub_xnu_devtree_key *cur; + for (cur = parent; cur; cur = cur->next) + if (grub_strcmp (cur->name, name) == 0) + return cur; + return 0; +} + +struct grub_xnu_devtree_key * +grub_xnu_create_key (struct grub_xnu_devtree_key **parent, char *name) +{ + struct grub_xnu_devtree_key *ret; + ret = grub_xnu_find_key (*parent, name); + if (ret) + return ret; + ret = (struct grub_xnu_devtree_key *) grub_zalloc (sizeof (*ret)); + if (! ret) + { + grub_error (GRUB_ERR_OUT_OF_MEMORY, "can't create key %s", name); + return 0; + } + ret->name = grub_strdup (name); + if (! ret->name) + { + grub_free (ret); + grub_error (GRUB_ERR_OUT_OF_MEMORY, "can't create key %s", name); + return 0; + } + ret->datasize = -1; + ret->next = *parent; + *parent = ret; + return ret; +} + +struct grub_xnu_devtree_key * +grub_xnu_create_value (struct grub_xnu_devtree_key **parent, char *name) +{ + struct grub_xnu_devtree_key *ret; + ret = grub_xnu_find_key (*parent, name); + if (ret) + { + if (ret->datasize == -1) + grub_xnu_free_devtree (ret->first_child); + else if (ret->datasize) + grub_free (ret->data); + ret->datasize = 0; + ret->data = 0; + return ret; + } + ret = (struct grub_xnu_devtree_key *) grub_zalloc (sizeof (*ret)); + if (! ret) + { + grub_error (GRUB_ERR_OUT_OF_MEMORY, "can't create value %s", name); + return 0; + } + ret->name = grub_strdup (name); + if (! ret->name) + { + grub_free (ret); + grub_error (GRUB_ERR_OUT_OF_MEMORY, "can't create value %s", name); + return 0; + } + ret->next = *parent; + *parent = ret; + return ret; +} + +static grub_err_t +grub_xnu_unload (void) +{ + grub_cpu_xnu_unload (); + + grub_xnu_free_devtree (grub_xnu_devtree_root); + grub_xnu_devtree_root = 0; + + /* Free loaded image. */ + driversnum = 0; + driverspackagenum = 0; + grub_relocator_unload (grub_xnu_relocator); + grub_xnu_relocator = NULL; + grub_xnu_heap_target_start = 0; + grub_xnu_heap_size = 0; + grub_xnu_unlock (); + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_cmd_xnu_kernel (grub_command_t cmd __attribute__ ((unused)), + int argc, char *args[]) +{ + grub_err_t err; + grub_macho_t macho; + grub_uint32_t startcode, endcode; + int i; + char *ptr; + void *loadaddr; + grub_addr_t loadaddr_target; + + if (argc < 1) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "file name required"); + + grub_xnu_unload (); + + macho = grub_macho_open (args[0]); + if (! macho) + return grub_errno; + if (! grub_macho_contains_macho32 (macho)) + { + grub_macho_close (macho); + return grub_error (GRUB_ERR_BAD_OS, + "kernel doesn't contain suitable 32-bit architecture"); + } + + err = grub_macho_size32 (macho, &startcode, &endcode, GRUB_MACHO_NOBSS); + if (err) + { + grub_macho_close (macho); + grub_xnu_unload (); + return err; + } + + grub_dprintf ("xnu", "endcode = %lx, startcode = %lx\n", + (unsigned long) endcode, (unsigned long) startcode); + + grub_xnu_relocator = grub_relocator_new (); + if (!grub_xnu_relocator) + return grub_errno; + grub_xnu_heap_target_start = startcode; + err = grub_xnu_heap_malloc (endcode - startcode, &loadaddr, + &loadaddr_target); + + if (err) + { + grub_macho_close (macho); + grub_xnu_unload (); + return err; + } + + /* Load kernel. */ + err = grub_macho_load32 (macho, (char *) loadaddr - startcode, + GRUB_MACHO_NOBSS); + if (err) + { + grub_macho_close (macho); + grub_xnu_unload (); + return err; + } + + grub_xnu_entry_point = grub_macho_get_entry_point32 (macho); + if (! grub_xnu_entry_point) + { + grub_macho_close (macho); + grub_xnu_unload (); + return grub_error (GRUB_ERR_BAD_OS, "couldn't find entry point"); + } + + grub_macho_close (macho); + + err = grub_xnu_align_heap (GRUB_XNU_PAGESIZE); + if (err) + { + grub_xnu_unload (); + return err; + } + + /* Copy parameters to kernel command line. */ + ptr = grub_xnu_cmdline; + for (i = 1; i < argc; i++) + { + if (ptr + grub_strlen (args[i]) + 1 + >= grub_xnu_cmdline + sizeof (grub_xnu_cmdline)) + break; + grub_memcpy (ptr, args[i], grub_strlen (args[i])); + ptr += grub_strlen (args[i]); + *ptr = ' '; + ptr++; + } + + /* Replace last space by '\0'. */ + if (ptr != grub_xnu_cmdline) + *(ptr - 1) = 0; + +#if defined (__i386) && !defined (GRUB_MACHINE_EFI) + err = grub_efiemu_autocore (); + if (err) + return err; +#endif + + grub_loader_set (grub_xnu_boot, grub_xnu_unload, 0); + + grub_xnu_lock (); + grub_xnu_is_64bit = 0; + + return 0; +} + +static grub_err_t +grub_cmd_xnu_kernel64 (grub_command_t cmd __attribute__ ((unused)), + int argc, char *args[]) +{ + grub_err_t err; + grub_macho_t macho; + grub_uint64_t startcode, endcode; + int i; + char *ptr; + void *loadaddr; + grub_addr_t loadaddr_target; + + if (argc < 1) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "file name required"); + + grub_xnu_unload (); + + macho = grub_macho_open (args[0]); + if (! macho) + return grub_errno; + if (! grub_macho_contains_macho64 (macho)) + { + grub_macho_close (macho); + return grub_error (GRUB_ERR_BAD_OS, + "kernel doesn't contain suitable 64-bit architecture"); + } + + err = grub_macho_size64 (macho, &startcode, &endcode, GRUB_MACHO_NOBSS); + if (err) + { + grub_macho_close (macho); + grub_xnu_unload (); + return err; + } + + startcode &= 0x0fffffff; + endcode &= 0x0fffffff; + + grub_dprintf ("xnu", "endcode = %lx, startcode = %lx\n", + (unsigned long) endcode, (unsigned long) startcode); + + grub_xnu_relocator = grub_relocator_new (); + if (!grub_xnu_relocator) + return grub_errno; + grub_xnu_heap_target_start = startcode; + err = grub_xnu_heap_malloc (endcode - startcode, &loadaddr, + &loadaddr_target); + + if (err) + { + grub_macho_close (macho); + grub_xnu_unload (); + return err; + } + + /* Load kernel. */ + err = grub_macho_load64 (macho, (char *) loadaddr - startcode, + GRUB_MACHO_NOBSS); + if (err) + { + grub_macho_close (macho); + grub_xnu_unload (); + return err; + } + + grub_xnu_entry_point = grub_macho_get_entry_point64 (macho) & 0x0fffffff; + if (! grub_xnu_entry_point) + { + grub_macho_close (macho); + grub_xnu_unload (); + return grub_error (GRUB_ERR_BAD_OS, "couldn't find entry point"); + } + + grub_macho_close (macho); + + err = grub_xnu_align_heap (GRUB_XNU_PAGESIZE); + if (err) + { + grub_xnu_unload (); + return err; + } + + /* Copy parameters to kernel command line. */ + ptr = grub_xnu_cmdline; + for (i = 1; i < argc; i++) + { + if (ptr + grub_strlen (args[i]) + 1 + >= grub_xnu_cmdline + sizeof (grub_xnu_cmdline)) + break; + grub_memcpy (ptr, args[i], grub_strlen (args[i])); + ptr += grub_strlen (args[i]); + *ptr = ' '; + ptr++; + } + + /* Replace last space by '\0'. */ + if (ptr != grub_xnu_cmdline) + *(ptr - 1) = 0; + +#if defined (__i386) && !defined (GRUB_MACHINE_EFI) + err = grub_efiemu_autocore (); + if (err) + return err; +#endif + + grub_loader_set (grub_xnu_boot, grub_xnu_unload, 0); + + grub_xnu_lock (); + grub_xnu_is_64bit = 1; + + return 0; +} + +/* Register a memory in a memory map under name PREFIXSUFFIX + and increment SUFFIX. */ +static grub_err_t +grub_xnu_register_memory (char *prefix, int *suffix, + grub_addr_t addr, grub_size_t size) +{ + struct grub_xnu_devtree_key *chosen; + struct grub_xnu_devtree_key *memorymap; + struct grub_xnu_devtree_key *driverkey; + struct grub_xnu_extdesc *extdesc; + + if (! grub_xnu_heap_size) + return grub_error (GRUB_ERR_BAD_OS, "no xnu kernel loaded"); + + chosen = grub_xnu_create_key (&grub_xnu_devtree_root, "chosen"); + if (! chosen) + return grub_errno; + memorymap = grub_xnu_create_key (&(chosen->first_child), "memory-map"); + if (! memorymap) + return grub_errno; + + driverkey = (struct grub_xnu_devtree_key *) grub_malloc (sizeof (*driverkey)); + if (! driverkey) + return grub_error (GRUB_ERR_OUT_OF_MEMORY, "can't register memory"); + if (suffix) + { + driverkey->name = grub_xasprintf ("%s%d", prefix, (*suffix)++); + if (!driverkey->name) + return grub_error (GRUB_ERR_OUT_OF_MEMORY, "can't register memory"); + } + else + driverkey->name = grub_strdup (prefix); + if (! driverkey->name) + return grub_error (GRUB_ERR_OUT_OF_MEMORY, "can't register extension"); + driverkey->datasize = sizeof (*extdesc); + driverkey->next = memorymap->first_child; + memorymap->first_child = driverkey; + driverkey->data = extdesc + = (struct grub_xnu_extdesc *) grub_malloc (sizeof (*extdesc)); + if (! driverkey->data) + return grub_error (GRUB_ERR_OUT_OF_MEMORY, "can't register extension"); + extdesc->addr = addr; + extdesc->size = (grub_uint32_t) size; + return GRUB_ERR_NONE; +} + +static inline char * +get_name_ptr (char *name) +{ + char *p = name, *p2; + /* Skip Info.plist. */ + p2 = grub_strrchr (p, '/'); + if (!p2) + return name; + if (p2 == name) + return name + 1; + p = p2 - 1; + + p2 = grub_strrchr (p, '/'); + if (!p2) + return name; + if (p2 == name) + return name + 1; + if (grub_memcmp (p2, "/Contents/", sizeof ("/Contents/") - 1) != 0) + return p2 + 1; + + p = p2 - 1; + + p2 = grub_strrchr (p, '/'); + if (!p2) + return name; + return p2 + 1; +} + +/* Load .kext. */ +static grub_err_t +grub_xnu_load_driver (char *infoplistname, grub_file_t binaryfile) +{ + grub_macho_t macho; + grub_err_t err; + grub_file_t infoplist; + struct grub_xnu_extheader *exthead; + int neededspace = sizeof (*exthead); + grub_uint8_t *buf; + void *buf0; + grub_addr_t buf_target; + grub_size_t infoplistsize = 0, machosize = 0; + char *name, *nameend; + int namelen; + + name = get_name_ptr (infoplistname); + nameend = grub_strchr (name, '/'); + + if (nameend) + namelen = nameend - name; + else + namelen = grub_strlen (name); + + neededspace += namelen + 1; + + if (! grub_xnu_heap_size) + return grub_error (GRUB_ERR_BAD_OS, "no xnu kernel loaded"); + + /* Compute the needed space. */ + if (binaryfile) + { + macho = grub_macho_file (binaryfile); + if (! macho || ! grub_macho_contains_macho32 (macho)) + { + if (macho) + grub_macho_close (macho); + return grub_error (GRUB_ERR_BAD_OS, + "extension doesn't contain suitable architecture"); + } + if (grub_xnu_is_64bit) + machosize = grub_macho_filesize64 (macho); + else + machosize = grub_macho_filesize32 (macho); + neededspace += machosize; + } + else + macho = 0; + + if (infoplistname) + infoplist = grub_file_open (infoplistname); + else + infoplist = 0; + grub_errno = GRUB_ERR_NONE; + if (infoplist) + { + infoplistsize = grub_file_size (infoplist); + neededspace += infoplistsize + 1; + } + else + infoplistsize = 0; + + /* Allocate the space. */ + err = grub_xnu_align_heap (GRUB_XNU_PAGESIZE); + if (err) + return err; + err = grub_xnu_heap_malloc (neededspace, &buf0, &buf_target); + if (err) + return err; + buf = buf0; + + exthead = (struct grub_xnu_extheader *) buf; + grub_memset (exthead, 0, sizeof (*exthead)); + buf += sizeof (*exthead); + + /* Load the binary. */ + if (macho) + { + exthead->binaryaddr = buf_target + (buf - (grub_uint8_t *) buf0); + exthead->binarysize = machosize; + if (grub_xnu_is_64bit) + err = grub_macho_readfile64 (macho, buf); + else + err = grub_macho_readfile32 (macho, buf); + if (err) + { + grub_macho_close (macho); + return err; + } + grub_macho_close (macho); + buf += machosize; + } + grub_errno = GRUB_ERR_NONE; + + /* Load the plist. */ + if (infoplist) + { + exthead->infoplistaddr = buf_target + (buf - (grub_uint8_t *) buf0); + exthead->infoplistsize = infoplistsize + 1; + if (grub_file_read (infoplist, buf, infoplistsize) + != (grub_ssize_t) (infoplistsize)) + { + grub_file_close (infoplist); + grub_error_push (); + return grub_error (GRUB_ERR_BAD_OS, "couldn't read file %s: ", + infoplistname); + } + grub_file_close (infoplist); + buf[infoplistsize] = 0; + buf += infoplistsize + 1; + } + grub_errno = GRUB_ERR_NONE; + + exthead->nameaddr = (buf - (grub_uint8_t *) buf0) + buf_target; + exthead->namesize = namelen + 1; + grub_memcpy (buf, name, namelen); + buf[namelen] = 0; + buf += namelen + 1; + + /* Announce to kernel */ + return grub_xnu_register_memory ("Driver-", &driversnum, buf_target, + neededspace); +} + +/* Load mkext. */ +static grub_err_t +grub_cmd_xnu_mkext (grub_command_t cmd __attribute__ ((unused)), + int argc, char *args[]) +{ + grub_file_t file; + void *loadto; + grub_addr_t loadto_target; + grub_err_t err; + grub_off_t readoff = 0; + grub_ssize_t readlen = -1; + struct grub_macho_fat_header head; + struct grub_macho_fat_arch *archs; + int narchs, i; + + if (argc != 1) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "file name required"); + + if (! grub_xnu_heap_size) + return grub_error (GRUB_ERR_BAD_OS, "no xnu kernel loaded"); + + file = grub_file_open (args[0]); + if (! file) + return grub_error (GRUB_ERR_FILE_NOT_FOUND, + "couldn't load driver package"); + + /* Sometimes caches are fat binary. Errgh. */ + if (grub_file_read (file, &head, sizeof (head)) + != (grub_ssize_t) (sizeof (head))) + { + /* I don't know the internal structure of package but + can hardly imagine a valid package shorter than 20 bytes. */ + grub_file_close (file); + grub_error_push (); + return grub_error (GRUB_ERR_BAD_OS, "couldn't read file %s", args[0]); + } + + /* Find the corresponding architecture. */ + if (grub_be_to_cpu32 (head.magic) == GRUB_MACHO_FAT_MAGIC) + { + narchs = grub_be_to_cpu32 (head.nfat_arch); + archs = grub_malloc (sizeof (struct grub_macho_fat_arch) * narchs); + if (! archs) + { + grub_file_close (file); + grub_error_push (); + return grub_error (GRUB_ERR_OUT_OF_MEMORY, + "couldn't read file %s", args[0]); + + } + if (grub_file_read (file, archs, + sizeof (struct grub_macho_fat_arch) * narchs) + != (grub_ssize_t) sizeof(struct grub_macho_fat_arch) * narchs) + { + grub_free (archs); + grub_error_push (); + return grub_error (GRUB_ERR_READ_ERROR, "cannot read fat header"); + } + for (i = 0; i < narchs; i++) + { + if (!grub_xnu_is_64bit && GRUB_MACHO_CPUTYPE_IS_HOST32 + (grub_be_to_cpu32 (archs[i].cputype))) + { + readoff = grub_be_to_cpu32 (archs[i].offset); + readlen = grub_be_to_cpu32 (archs[i].size); + } + if (grub_xnu_is_64bit && GRUB_MACHO_CPUTYPE_IS_HOST64 + (grub_be_to_cpu32 (archs[i].cputype))) + { + readoff = grub_be_to_cpu32 (archs[i].offset); + readlen = grub_be_to_cpu32 (archs[i].size); + } + } + grub_free (archs); + } + else + { + /* It's a flat file. Some sane people still exist. */ + readoff = 0; + readlen = grub_file_size (file); + } + + if (readlen == -1) + { + grub_file_close (file); + return grub_error (GRUB_ERR_BAD_OS, "no suitable architecture is found"); + } + + /* Allocate space. */ + err = grub_xnu_align_heap (GRUB_XNU_PAGESIZE); + if (err) + { + grub_file_close (file); + return err; + } + + err = grub_xnu_heap_malloc (readlen, &loadto, &loadto_target); + if (err) + { + grub_file_close (file); + return err; + } + + /* Read the file. */ + grub_file_seek (file, readoff); + if (grub_file_read (file, loadto, readlen) != (grub_ssize_t) (readlen)) + { + grub_file_close (file); + grub_error_push (); + return grub_error (GRUB_ERR_BAD_OS, "couldn't read file %s", args[0]); + } + grub_file_close (file); + + /* Pass it to kernel. */ + return grub_xnu_register_memory ("DriversPackage-", &driverspackagenum, + loadto_target, readlen); +} + +static grub_err_t +grub_cmd_xnu_ramdisk (grub_command_t cmd __attribute__ ((unused)), + int argc, char *args[]) +{ + grub_file_t file; + void *loadto; + grub_addr_t loadto_target; + grub_err_t err; + grub_size_t size; + + if (argc != 1) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "file name required"); + + if (! grub_xnu_heap_size) + return grub_error (GRUB_ERR_BAD_OS, "no xnu kernel loaded"); + + file = grub_file_open (args[0]); + if (! file) + return grub_error (GRUB_ERR_FILE_NOT_FOUND, + "couldn't load ramdisk"); + + err = grub_xnu_align_heap (GRUB_XNU_PAGESIZE); + if (err) + return err; + + size = grub_file_size (file); + + err = grub_xnu_heap_malloc (size, &loadto, &loadto_target); + if (err) + return err; + if (grub_file_read (file, loadto, size) + != (grub_ssize_t) (size)) + { + grub_file_close (file); + grub_error_push (); + return grub_error (GRUB_ERR_BAD_OS, "couldn't read file %s", args[0]); + } + return grub_xnu_register_memory ("RAMDisk", 0, loadto_target, size); +} + +/* Returns true if the kext should be loaded according to plist + and osbundlereq. Also fill BINNAME. */ +static int +grub_xnu_check_os_bundle_required (char *plistname, char *osbundlereq, + char **binname) +{ + grub_file_t file; + char *buf = 0, *tagstart = 0, *ptr1 = 0, *keyptr = 0; + char *stringptr = 0, *ptr2 = 0; + grub_size_t size; + int depth = 0; + int ret; + int osbundlekeyfound = 0, binnamekeyfound = 0; + if (binname) + *binname = 0; + + file = grub_file_open (plistname); + if (! file) + { + grub_file_close (file); + grub_error_push (); + grub_error (GRUB_ERR_BAD_OS, "couldn't read file %s", plistname); + return 0; + } + + size = grub_file_size (file); + buf = grub_malloc (size); + if (! buf) + { + grub_file_close (file); + grub_error_push (); + grub_error (GRUB_ERR_OUT_OF_MEMORY, "couldn't read file %s", plistname); + return 0; + } + if (grub_file_read (file, buf, size) != (grub_ssize_t) (size)) + { + grub_file_close (file); + grub_error_push (); + grub_error (GRUB_ERR_BAD_OS, "couldn't read file %s", plistname); + return 0; + } + grub_file_close (file); + + /* Set the return value for the case when no OSBundleRequired tag is found. */ + if (osbundlereq) + ret = grub_strword (osbundlereq, "all") || grub_strword (osbundlereq, "-"); + else + ret = 1; + + /* Parse plist. It's quite dirty and inextensible but does its job. */ + for (ptr1 = buf; ptr1 < buf + size; ptr1++) + switch (*ptr1) + { + case '<': + tagstart = ptr1; + *ptr1 = 0; + if (keyptr && depth == 4 + && grub_strcmp (keyptr, "OSBundleRequired") == 0) + osbundlekeyfound = 1; + if (keyptr && depth == 4 && + grub_strcmp (keyptr, "CFBundleExecutable") == 0) + binnamekeyfound = 1; + if (stringptr && osbundlekeyfound && osbundlereq && depth == 4) + { + for (ptr2 = stringptr; *ptr2; ptr2++) + *ptr2 = grub_tolower (*ptr2); + ret = grub_strword (osbundlereq, stringptr) + || grub_strword (osbundlereq, "all"); + } + if (stringptr && binnamekeyfound && binname && depth == 4) + { + if (*binname) + grub_free (*binname); + *binname = grub_strdup (stringptr); + } + + *ptr1 = '<'; + keyptr = 0; + stringptr = 0; + break; + case '>': + if (! tagstart) + { + grub_free (buf); + grub_error (GRUB_ERR_BAD_OS, "can't parse %s", plistname); + return 0; + } + *ptr1 = 0; + if (tagstart[1] == '?' || ptr1[-1] == '/') + { + osbundlekeyfound = 0; + *ptr1 = '>'; + break; + } + if (depth == 3 && grub_strcmp (tagstart + 1, "key") == 0) + keyptr = ptr1 + 1; + if (depth == 3 && grub_strcmp (tagstart + 1, "string") == 0) + stringptr = ptr1 + 1; + else if (grub_strcmp (tagstart + 1, "/key") != 0) + { + osbundlekeyfound = 0; + binnamekeyfound = 0; + } + *ptr1 = '>'; + + if (tagstart[1] == '/') + depth--; + else + depth++; + break; + } + grub_free (buf); + + return ret; +} + +/* Load all loadable kexts placed under DIRNAME and matching OSBUNDLEREQUIRED */ +grub_err_t +grub_xnu_scan_dir_for_kexts (char *dirname, char *osbundlerequired, + int maxrecursion) +{ + grub_device_t dev; + char *device_name; + grub_fs_t fs; + const char *path; + + auto int load_hook (const char *filename, + const struct grub_dirhook_info *info); + int load_hook (const char *filename, const struct grub_dirhook_info *info) + { + char *newdirname; + if (! info->dir) + return 0; + if (filename[0] == '.') + return 0; + + if (grub_strlen (filename) < 5 || + grub_memcmp (filename + grub_strlen (filename) - 5, ".kext", 5) != 0) + return 0; + + newdirname + = grub_malloc (grub_strlen (dirname) + grub_strlen (filename) + 2); + + /* It's a .kext. Try to load it. */ + if (newdirname) + { + grub_strcpy (newdirname, dirname); + newdirname[grub_strlen (newdirname) + 1] = 0; + newdirname[grub_strlen (newdirname)] = '/'; + grub_strcpy (newdirname + grub_strlen (newdirname), filename); + grub_xnu_load_kext_from_dir (newdirname, osbundlerequired, + maxrecursion); + if (grub_errno == GRUB_ERR_BAD_OS) + grub_errno = GRUB_ERR_NONE; + grub_free (newdirname); + } + return 0; + } + + if (! grub_xnu_heap_size) + return grub_error (GRUB_ERR_BAD_OS, "no xnu kernel loaded"); + + device_name = grub_file_get_device_name (dirname); + dev = grub_device_open (device_name); + if (dev) + { + fs = grub_fs_probe (dev); + path = grub_strchr (dirname, ')'); + if (! path) + path = dirname; + else + path++; + + if (fs) + (fs->dir) (dev, path, load_hook); + grub_device_close (dev); + } + grub_free (device_name); + + return GRUB_ERR_NONE; +} + +/* Load extension DIRNAME. (extensions are directories in xnu) */ +grub_err_t +grub_xnu_load_kext_from_dir (char *dirname, char *osbundlerequired, + int maxrecursion) +{ + grub_device_t dev; + char *plistname = 0; + char *newdirname; + char *newpath; + char *device_name; + grub_fs_t fs; + const char *path; + char *binsuffix; + int usemacos = 0; + grub_file_t binfile; + + auto int load_hook (const char *filename, + const struct grub_dirhook_info *info); + + int load_hook (const char *filename, const struct grub_dirhook_info *info) + { + if (grub_strlen (filename) > 15) + return 0; + grub_strcpy (newdirname + grub_strlen (dirname) + 1, filename); + + /* If the kext contains directory "Contents" all real stuff is in + this directory. */ + if (info->dir && grub_strcasecmp (filename, "Contents") == 0) + grub_xnu_load_kext_from_dir (newdirname, osbundlerequired, + maxrecursion - 1); + + /* Directory "Plugins" contains nested kexts. */ + if (info->dir && grub_strcasecmp (filename, "Plugins") == 0) + grub_xnu_scan_dir_for_kexts (newdirname, osbundlerequired, + maxrecursion - 1); + + /* Directory "MacOS" contains executable, otherwise executable is + on the top. */ + if (info->dir && grub_strcasecmp (filename, "MacOS") == 0) + usemacos = 1; + + /* Info.plist is the file which governs our future actions. */ + if (! info->dir && grub_strcasecmp (filename, "Info.plist") == 0 + && ! plistname) + plistname = grub_strdup (newdirname); + return 0; + } + + newdirname = grub_malloc (grub_strlen (dirname) + 20); + if (! newdirname) + return grub_error (GRUB_ERR_OUT_OF_MEMORY, "couldn't allocate buffer"); + grub_strcpy (newdirname, dirname); + newdirname[grub_strlen (dirname)] = '/'; + newdirname[grub_strlen (dirname) + 1] = 0; + device_name = grub_file_get_device_name (dirname); + dev = grub_device_open (device_name); + if (dev) + { + fs = grub_fs_probe (dev); + path = grub_strchr (dirname, ')'); + if (! path) + path = dirname; + else + path++; + + newpath = grub_strchr (newdirname, ')'); + if (! newpath) + newpath = newdirname; + else + newpath++; + + /* Look at the directory. */ + if (fs) + (fs->dir) (dev, path, load_hook); + + if (plistname && grub_xnu_check_os_bundle_required + (plistname, osbundlerequired, &binsuffix)) + { + if (binsuffix) + { + /* Open the binary. */ + char *binname = grub_malloc (grub_strlen (dirname) + + grub_strlen (binsuffix) + + sizeof ("/MacOS/")); + grub_strcpy (binname, dirname); + if (usemacos) + grub_strcpy (binname + grub_strlen (binname), "/MacOS/"); + else + grub_strcpy (binname + grub_strlen (binname), "/"); + grub_strcpy (binname + grub_strlen (binname), binsuffix); + grub_dprintf ("xnu", "%s:%s\n", plistname, binname); + binfile = grub_file_open (binname); + if (! binfile) + grub_errno = GRUB_ERR_NONE; + + /* Load the extension. */ + grub_xnu_load_driver (plistname, binfile); + grub_free (binname); + grub_free (binsuffix); + } + else + { + grub_dprintf ("xnu", "%s:0\n", plistname); + grub_xnu_load_driver (plistname, 0); + } + } + grub_free (plistname); + grub_device_close (dev); + } + grub_free (device_name); + + return GRUB_ERR_NONE; +} + + +static int locked=0; +static grub_dl_t my_mod; + +/* Load the kext. */ +static grub_err_t +grub_cmd_xnu_kext (grub_command_t cmd __attribute__ ((unused)), + int argc, char *args[]) +{ + grub_file_t binfile = 0; + + if (! grub_xnu_heap_size) + return grub_error (GRUB_ERR_BAD_OS, "no xnu kernel loaded"); + + if (argc == 2) + { + /* User explicitly specified plist and binary. */ + if (grub_strcmp (args[1], "-") != 0) + { + binfile = grub_file_open (args[1]); + if (! binfile) + { + grub_error (GRUB_ERR_BAD_OS, "can't open file"); + return GRUB_ERR_NONE; + } + } + return grub_xnu_load_driver (grub_strcmp (args[0], "-") ? args[0] : 0, + binfile); + } + + /* load kext normally. */ + if (argc == 1) + return grub_xnu_load_kext_from_dir (args[0], 0, 10); + + return grub_error (GRUB_ERR_BAD_ARGUMENT, "file name required"); +} + +/* Load a directory containing kexts. */ +static grub_err_t +grub_cmd_xnu_kextdir (grub_command_t cmd __attribute__ ((unused)), + int argc, char *args[]) +{ + if (argc != 1 && argc != 2) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "directory name required"); + + if (! grub_xnu_heap_size) + return grub_error (GRUB_ERR_BAD_OS, "no xnu kernel loaded"); + + if (argc == 1) + return grub_xnu_scan_dir_for_kexts (args[0], + "console,root,local-root,network-root", + 10); + else + { + char *osbundlerequired = grub_strdup (args[1]), *ptr; + grub_err_t err; + if (! osbundlerequired) + return grub_error (GRUB_ERR_OUT_OF_MEMORY, + "couldn't allocate string temporary space"); + for (ptr = osbundlerequired; *ptr; ptr++) + *ptr = grub_tolower (*ptr); + err = grub_xnu_scan_dir_for_kexts (args[0], osbundlerequired, 10); + grub_free (osbundlerequired); + return err; + } +} + +static inline int +hextoval (char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'a' && c <= 'z') + return c - 'a' + 10; + if (c >= 'A' && c <= 'Z') + return c - 'A' + 10; + return 0; +} + +static inline void +unescape (char *name, char *curdot, char *nextdot, int *len) +{ + char *ptr, *dptr; + dptr = name; + for (ptr = curdot; ptr < nextdot;) + if (ptr + 2 < nextdot && *ptr == '%') + { + *dptr = (hextoval (ptr[1]) << 4) | (hextoval (ptr[2])); + ptr += 3; + dptr++; + } + else + { + *dptr = *ptr; + ptr++; + dptr++; + } + *len = dptr - name; +} + +grub_err_t +grub_xnu_fill_devicetree (void) +{ + auto int iterate_env (struct grub_env_var *var); + int iterate_env (struct grub_env_var *var) + { + char *nextdot = 0, *curdot; + struct grub_xnu_devtree_key **curkey = &grub_xnu_devtree_root; + struct grub_xnu_devtree_key *curvalue; + char *name = 0, *data; + int len; + + if (grub_memcmp (var->name, "XNU.DeviceTree.", + sizeof ("XNU.DeviceTree.") - 1) != 0) + return 0; + + curdot = var->name + sizeof ("XNU.DeviceTree.") - 1; + nextdot = grub_strchr (curdot, '.'); + if (nextdot) + nextdot++; + while (nextdot) + { + name = grub_realloc (name, nextdot - curdot + 1); + + if (!name) + return 1; + + unescape (name, curdot, nextdot, &len); + name[len - 1] = 0; + + curkey = &(grub_xnu_create_key (curkey, name)->first_child); + + curdot = nextdot; + nextdot = grub_strchr (nextdot, '.'); + if (nextdot) + nextdot++; + } + + nextdot = curdot + grub_strlen (curdot) + 1; + + name = grub_realloc (name, nextdot - curdot + 1); + + if (!name) + return 1; + + unescape (name, curdot, nextdot, &len); + name[len] = 0; + + curvalue = grub_xnu_create_value (curkey, name); + grub_free (name); + + data = grub_malloc (grub_strlen (var->value) + 1); + if (!data) + return 1; + + unescape (data, var->value, var->value + grub_strlen (var->value), + &len); + curvalue->datasize = len; + curvalue->data = data; + + return 0; + } + + grub_env_iterate (iterate_env); + + return grub_errno; +} + +struct grub_video_bitmap *grub_xnu_bitmap = 0; +grub_xnu_bitmap_mode_t grub_xnu_bitmap_mode; + +/* Option array indices. */ +#define XNU_SPLASH_CMD_ARGINDEX_MODE 0 + +static const struct grub_arg_option xnu_splash_cmd_options[] = + { + {"mode", 'm', 0, "Background image mode.", "stretch|normal", + ARG_TYPE_STRING}, + {0, 0, 0, 0, 0, 0} + }; + +static grub_err_t +grub_cmd_xnu_splash (grub_extcmd_context_t ctxt, + int argc, char *args[]) +{ + grub_err_t err; + if (argc != 1) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "file name required"); + + if (! grub_xnu_heap_size) + return grub_error (GRUB_ERR_BAD_OS, "no xnu kernel loaded"); + + if (ctxt->state[XNU_SPLASH_CMD_ARGINDEX_MODE].set && + grub_strcmp (ctxt->state[XNU_SPLASH_CMD_ARGINDEX_MODE].arg, + "stretch") == 0) + grub_xnu_bitmap_mode = GRUB_XNU_BITMAP_STRETCH; + else + grub_xnu_bitmap_mode = GRUB_XNU_BITMAP_CENTER; + + err = grub_video_bitmap_load (&grub_xnu_bitmap, args[0]); + if (err) + grub_xnu_bitmap = 0; + + return err; +} + + +#ifndef GRUB_MACHINE_EMU +static grub_err_t +grub_cmd_xnu_resume (grub_command_t cmd __attribute__ ((unused)), + int argc, char *args[]) +{ + if (argc != 1) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "file name required"); + + return grub_xnu_resume (args[0]); +} +#endif + +void +grub_xnu_lock (void) +{ + if (!locked) + grub_dl_ref (my_mod); + locked = 1; +} + +void +grub_xnu_unlock (void) +{ + if (locked) + grub_dl_unref (my_mod); + locked = 0; +} + +static grub_command_t cmd_kernel64, cmd_kernel, cmd_mkext, cmd_kext; +static grub_command_t cmd_kextdir, cmd_ramdisk, cmd_resume; +static grub_extcmd_t cmd_splash; + +GRUB_MOD_INIT(xnu) +{ + cmd_kernel = grub_register_command ("xnu_kernel", grub_cmd_xnu_kernel, 0, + N_("Load XNU image.")); + cmd_kernel64 = grub_register_command ("xnu_kernel64", grub_cmd_xnu_kernel64, + 0, N_("Load 64-bit XNU image.")); + cmd_mkext = grub_register_command ("xnu_mkext", grub_cmd_xnu_mkext, 0, + N_("Load XNU extension package.")); + cmd_kext = grub_register_command ("xnu_kext", grub_cmd_xnu_kext, 0, + N_("Load XNU extension.")); + cmd_kextdir = grub_register_command ("xnu_kextdir", grub_cmd_xnu_kextdir, + N_("DIRECTORY [OSBundleRequired]"), + N_("Load XNU extension directory.")); + cmd_ramdisk = grub_register_command ("xnu_ramdisk", grub_cmd_xnu_ramdisk, 0, + "Load XNU ramdisk. " + "It will be seen as md0."); + cmd_splash = grub_register_extcmd ("xnu_splash", + grub_cmd_xnu_splash, 0, 0, + N_("Load a splash image for XNU."), + xnu_splash_cmd_options); + +#ifndef GRUB_MACHINE_EMU + cmd_resume = grub_register_command ("xnu_resume", grub_cmd_xnu_resume, + 0, N_("Load XNU hibernate image.")); +#endif + + grub_cpu_xnu_init (); + + my_mod = mod; +} + +GRUB_MOD_FINI(xnu) +{ +#ifndef GRUB_MACHINE_EMU + grub_unregister_command (cmd_resume); +#endif + grub_unregister_command (cmd_mkext); + grub_unregister_command (cmd_kext); + grub_unregister_command (cmd_kextdir); + grub_unregister_command (cmd_ramdisk); + grub_unregister_command (cmd_kernel); + grub_unregister_extcmd (cmd_splash); + grub_unregister_command (cmd_kernel64); + + grub_cpu_xnu_fini (); +} diff --git a/grub-core/loader/xnu_resume.c b/grub-core/loader/xnu_resume.c new file mode 100644 index 0000000..8f0e244 --- /dev/null +++ b/grub-core/loader/xnu_resume.c @@ -0,0 +1,172 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2009 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <grub/normal.h> +#include <grub/dl.h> +#include <grub/file.h> +#include <grub/disk.h> +#include <grub/misc.h> +#include <grub/xnu.h> +#include <grub/cpu/xnu.h> +#include <grub/mm.h> +#include <grub/loader.h> + +static void *grub_xnu_hibernate_image; + +static grub_err_t +grub_xnu_resume_unload (void) +{ + /* Free loaded image */ + if (grub_xnu_hibernate_image) + grub_free (grub_xnu_hibernate_image); + grub_xnu_hibernate_image = 0; + grub_xnu_unlock (); + return GRUB_ERR_NONE; +} + +grub_err_t +grub_xnu_resume (char *imagename) +{ + grub_file_t file; + grub_size_t total_header_size; + struct grub_xnu_hibernate_header hibhead; + void *code; + void *image; + grub_uint32_t codedest; + grub_uint32_t codesize; + grub_addr_t target_image; + grub_err_t err; + + grub_file_filter_disable_compression (); + file = grub_file_open (imagename); + if (! file) + return 0; + + /* Read the header. */ + if (grub_file_read (file, &hibhead, sizeof (hibhead)) + !=sizeof (hibhead)) + { + grub_file_close (file); + return grub_error (GRUB_ERR_READ_ERROR, + "cannot read the hibernate header"); + } + + /* Check the header. */ + if (hibhead.magic != GRUB_XNU_HIBERNATE_MAGIC) + { + grub_file_close (file); + return grub_error (GRUB_ERR_BAD_OS, + "hibernate header has incorrect magic number"); + } + if (hibhead.encoffset) + { + grub_file_close (file); + return grub_error (GRUB_ERR_BAD_OS, + "encrypted images aren't supported yet"); + } + + codedest = hibhead.launchcode_target_page; + codedest *= GRUB_XNU_PAGESIZE; + codesize = hibhead.launchcode_numpages; + codesize *= GRUB_XNU_PAGESIZE; + + /* FIXME: check that codedest..codedest+codesize is available. */ + + /* Calculate total size before pages to copy. */ + total_header_size = hibhead.extmapsize + sizeof (hibhead); + + /* Unload image if any. */ + if (grub_xnu_hibernate_image) + grub_free (grub_xnu_hibernate_image); + + /* Try to allocate necessary space. + FIXME: mm isn't good enough yet to handle huge allocations. + */ + grub_xnu_relocator = grub_relocator_new (); + if (!grub_xnu_relocator) + { + grub_file_close (file); + return grub_errno; + } + + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_addr (grub_xnu_relocator, &ch, codedest, + codesize + GRUB_XNU_PAGESIZE); + if (err) + { + grub_file_close (file); + return err; + } + code = get_virtual_current_address (ch); + } + + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_align (grub_xnu_relocator, &ch, 0, + (0xffffffff - hibhead.image_size) + 1, + hibhead.image_size, + GRUB_XNU_PAGESIZE, + GRUB_RELOCATOR_PREFERENCE_NONE); + if (err) + { + grub_file_close (file); + return err; + } + image = get_virtual_current_address (ch); + target_image = get_physical_target_address (ch); + } + + /* Read code part. */ + if (grub_file_seek (file, total_header_size) == (grub_off_t) -1 + || grub_file_read (file, code, codesize) + != (grub_ssize_t) codesize) + { + grub_file_close (file); + return grub_error (GRUB_ERR_READ_ERROR, "cannot read resume image"); + } + + /* Read image. */ + if (grub_file_seek (file, 0) == (grub_off_t) -1 + || grub_file_read (file, image, hibhead.image_size) + != (grub_ssize_t) hibhead.image_size) + { + grub_file_close (file); + return grub_error (GRUB_ERR_READ_ERROR, "cannot read resume image"); + } + grub_file_close (file); + + /* Setup variables needed by asm helper. */ + grub_xnu_heap_target_start = codedest; + grub_xnu_heap_size = target_image + hibhead.image_size - codedest; + grub_xnu_stack = (codedest + hibhead.stack); + grub_xnu_entry_point = (codedest + hibhead.entry_point); + grub_xnu_arg1 = target_image; + + grub_dprintf ("xnu", "entry point 0x%x\n", codedest + hibhead.entry_point); + grub_dprintf ("xnu", "image at 0x%x\n", + codedest + codesize + GRUB_XNU_PAGESIZE); + + /* We're ready now. */ + grub_loader_set (grub_xnu_boot_resume, + grub_xnu_resume_unload, 0); + + /* Prevent module from unloading. */ + grub_xnu_lock (); + return GRUB_ERR_NONE; +} |