diff options
Diffstat (limited to 'grub-core/loader/xnu.c')
-rw-r--r-- | grub-core/loader/xnu.c | 1555 |
1 files changed, 1555 insertions, 0 deletions
diff --git a/grub-core/loader/xnu.c b/grub-core/loader/xnu.c new file mode 100644 index 0000000..1c0cf6a --- /dev/null +++ b/grub-core/loader/xnu.c @@ -0,0 +1,1555 @@ +/* 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> +#include <grub/verify.h> +#include <grub/safemath.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; +int grub_xnu_darwin_version = 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 (const 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; + grub_addr_t tgt; + + if (grub_add (grub_xnu_heap_target_start, grub_xnu_heap_size, &tgt)) + return GRUB_ERR_OUT_OF_RANGE; + + err = grub_relocator_alloc_chunk_addr (grub_xnu_relocator, &ch, tgt, size); + if (err) + return err; + + *src = get_virtual_current_address (ch); + *target = tgt; + 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, + const 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, + const 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) + { + curptr = grub_xnu_writetree_toheap_real (curptr, + cur->first_child, + cur->name); + if (!curptr) + 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_zalloc (sizeof (*driverkey)); + if (! driverkey) + return grub_errno; + driverkey->name = grub_strdup ("DeviceTree"); + if (! driverkey->name) + { + err = grub_errno; + goto fail; + } + + 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) + { + err = grub_errno; + goto fail; + } + + /* 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) + goto fail; + + /* 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; + + fail: + memorymap->first_child = NULL; + + grub_free (driverkey->data); + grub_free (driverkey->name); + grub_free (driverkey); + + return err; +} + +/* Find a key or value in parent key. */ +struct grub_xnu_devtree_key * +grub_xnu_find_key (struct grub_xnu_devtree_key *parent, const 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, const 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) + return 0; + ret->name = grub_strdup (name); + if (! ret->name) + { + grub_free (ret); + 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, const 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) + return 0; + ret->name = grub_strdup (name); + if (! ret->name) + { + grub_free (ret); + 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, N_("filename expected")); + + grub_xnu_unload (); + + macho = grub_macho_open (args[0], GRUB_FILE_TYPE_XNU_KERNEL, 0); + if (! macho) + return grub_errno; + + err = grub_macho_size32 (macho, &startcode, &endcode, GRUB_MACHO_NOBSS, + args[0]); + 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, args[0], (char *) loadaddr - startcode, + GRUB_MACHO_NOBSS, &grub_xnu_darwin_version); + if (err) + { + grub_macho_close (macho); + grub_xnu_unload (); + return err; + } + + grub_xnu_entry_point = grub_macho_get_entry_point32 (macho, args[0]); + 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; + + err = grub_verify_string (grub_xnu_cmdline, GRUB_VERIFY_KERNEL_CMDLINE); + if (err) + return err; + +#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, N_("filename expected")); + + grub_xnu_unload (); + + macho = grub_macho_open (args[0], GRUB_FILE_TYPE_XNU_KERNEL, 1); + if (! macho) + return grub_errno; + + err = grub_macho_size64 (macho, &startcode, &endcode, GRUB_MACHO_NOBSS, + args[0]); + 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, args[0], (char *) loadaddr - startcode, + GRUB_MACHO_NOBSS, &grub_xnu_darwin_version); + if (err) + { + grub_macho_close (macho); + grub_xnu_unload (); + return err; + } + + grub_xnu_entry_point = grub_macho_get_entry_point64 (macho, args[0]) + & 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; + + err = grub_verify_string (grub_xnu_cmdline, GRUB_VERIFY_KERNEL_CMDLINE); + if (err) + return err; + +#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 (const 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, N_("you need to load the kernel first")); + + 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_errno; + if (suffix) + driverkey->name = grub_xasprintf ("%s%d", prefix, (*suffix)++); + else + driverkey->name = grub_strdup (prefix); + if (!driverkey->name) + { + grub_free (driverkey); + return grub_errno; + } + driverkey->datasize = sizeof (*extdesc); + driverkey->next = memorymap->first_child; + driverkey->data = extdesc + = (struct grub_xnu_extdesc *) grub_malloc (sizeof (*extdesc)); + if (! driverkey->data) + { + grub_free (driverkey->name); + grub_free (driverkey); + return grub_errno; + } + memorymap->first_child = driverkey; + 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, + const char *filename) +{ + 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; + + if (infoplistname == NULL) + return grub_error (GRUB_ERR_BAD_FILENAME, N_("missing p-list filename")); + + 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, N_("you need to load the kernel first")); + + /* Compute the needed space. */ + if (binaryfile) + { + macho = grub_macho_file (binaryfile, filename, grub_xnu_is_64bit); + if (!macho) + grub_file_close (binaryfile); + else + { + if (grub_xnu_is_64bit) + machosize = grub_macho_filesize64 (macho); + else + machosize = grub_macho_filesize32 (macho); + } + neededspace += machosize; + } + else + macho = 0; + + infoplist = grub_file_open (infoplistname, GRUB_FILE_TYPE_XNU_INFO_PLIST); + 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) + goto fail; + err = grub_xnu_heap_malloc (neededspace, &buf0, &buf_target); + if (err) + goto fail; + 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, filename, buf); + else + err = grub_macho_readfile32 (macho, filename, buf); + if (err) + goto fail; + 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); + if (!grub_errno) + grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + infoplistname); + return grub_errno; + } + 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); +fail: + if (macho) + grub_macho_close (macho); + return err; +} + +/* 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, N_("filename expected")); + + if (! grub_xnu_heap_size) + return grub_error (GRUB_ERR_BAD_OS, N_("you need to load the kernel first")); + + file = grub_file_open (args[0], GRUB_FILE_TYPE_XNU_MKEXT); + if (! file) + return grub_errno; + + /* 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); + if (!grub_errno) + grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), args[0]); + return grub_errno; + } + + /* 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_calloc (narchs, sizeof (struct grub_macho_fat_arch)); + if (! archs) + { + grub_file_close (file); + return grub_errno; + + } + 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); + if (!grub_errno) + grub_error (GRUB_ERR_READ_ERROR, N_("premature end of file %s"), + args[0]); + return grub_errno; + } + 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); + if (!grub_errno) + grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), args[0]); + return grub_errno; + } + 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, N_("filename expected")); + + if (! grub_xnu_heap_size) + return grub_error (GRUB_ERR_BAD_OS, N_("you need to load the kernel first")); + + file = grub_file_open (args[0], GRUB_FILE_TYPE_XNU_RAMDISK); + if (! file) + return grub_errno; + + 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); + if (!grub_errno) + grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), args[0]); + return grub_errno; + } + 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, + const 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, GRUB_FILE_TYPE_XNU_INFO_PLIST); + if (! file) + return 0; + + size = grub_file_size (file); + buf = grub_malloc (size); + if (! buf) + { + grub_file_close (file); + return 0; + } + if (grub_file_read (file, buf, size) != (grub_ssize_t) (size)) + { + grub_file_close (file); + if (!grub_errno) + grub_error (GRUB_ERR_BAD_OS, N_("premature end of 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; +} + +/* Context for grub_xnu_scan_dir_for_kexts. */ +struct grub_xnu_scan_dir_for_kexts_ctx +{ + char *dirname; + const char *osbundlerequired; + int maxrecursion; +}; + +/* Helper for grub_xnu_scan_dir_for_kexts. */ +static int +grub_xnu_scan_dir_for_kexts_load (const char *filename, + const struct grub_dirhook_info *info, + void *data) +{ + struct grub_xnu_scan_dir_for_kexts_ctx *ctx = data; + 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 (ctx->dirname) + grub_strlen (filename) + 2); + + /* It's a .kext. Try to load it. */ + if (newdirname) + { + grub_strcpy (newdirname, ctx->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, ctx->osbundlerequired, + ctx->maxrecursion); + if (grub_errno == GRUB_ERR_BAD_OS) + grub_errno = GRUB_ERR_NONE; + grub_free (newdirname); + } + return 0; +} + +/* Load all loadable kexts placed under DIRNAME and matching OSBUNDLEREQUIRED */ +grub_err_t +grub_xnu_scan_dir_for_kexts (char *dirname, const char *osbundlerequired, + int maxrecursion) +{ + struct grub_xnu_scan_dir_for_kexts_ctx ctx = { + .dirname = dirname, + .osbundlerequired = osbundlerequired, + .maxrecursion = maxrecursion + }; + grub_device_t dev; + char *device_name; + grub_fs_t fs; + const char *path; + + if (! grub_xnu_heap_size) + return grub_error (GRUB_ERR_BAD_OS, N_("you need to load the kernel first")); + + 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->fs_dir) (dev, path, grub_xnu_scan_dir_for_kexts_load, &ctx); + grub_device_close (dev); + } + grub_free (device_name); + + return GRUB_ERR_NONE; +} + +/* Context for grub_xnu_load_kext_from_dir. */ +struct grub_xnu_load_kext_from_dir_ctx +{ + char *dirname; + const char *osbundlerequired; + int maxrecursion; + char *plistname; + char *newdirname; + int usemacos; +}; + +/* Helper for grub_xnu_load_kext_from_dir. */ +static int +grub_xnu_load_kext_from_dir_load (const char *filename, + const struct grub_dirhook_info *info, + void *data) +{ + struct grub_xnu_load_kext_from_dir_ctx *ctx = data; + + if (grub_strlen (filename) > 15) + return 0; + grub_strcpy (ctx->newdirname + grub_strlen (ctx->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 (ctx->newdirname, ctx->osbundlerequired, + ctx->maxrecursion - 1); + + /* Directory "Plugins" contains nested kexts. */ + if (info->dir && grub_strcasecmp (filename, "Plugins") == 0) + grub_xnu_scan_dir_for_kexts (ctx->newdirname, ctx->osbundlerequired, + ctx->maxrecursion - 1); + + /* Directory "MacOS" contains executable, otherwise executable is + on the top. */ + if (info->dir && grub_strcasecmp (filename, "MacOS") == 0) + ctx->usemacos = 1; + + /* Info.plist is the file which governs our future actions. */ + if (! info->dir && grub_strcasecmp (filename, "Info.plist") == 0 + && ! ctx->plistname) + ctx->plistname = grub_strdup (ctx->newdirname); + return 0; +} + +/* Load extension DIRNAME. (extensions are directories in xnu) */ +grub_err_t +grub_xnu_load_kext_from_dir (char *dirname, const char *osbundlerequired, + int maxrecursion) +{ + struct grub_xnu_load_kext_from_dir_ctx ctx = { + .dirname = dirname, + .osbundlerequired = osbundlerequired, + .maxrecursion = maxrecursion, + .plistname = 0, + .usemacos = 0 + }; + grub_device_t dev; + char *newpath; + char *device_name; + grub_fs_t fs; + const char *path; + char *binsuffix; + grub_file_t binfile; + + ctx.newdirname = grub_malloc (grub_strlen (dirname) + 20); + if (! ctx.newdirname) + return grub_errno; + grub_strcpy (ctx.newdirname, dirname); + ctx.newdirname[grub_strlen (dirname)] = '/'; + ctx.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 (ctx.newdirname, ')'); + if (! newpath) + newpath = ctx.newdirname; + else + newpath++; + + /* Look at the directory. */ + if (fs) + (fs->fs_dir) (dev, path, grub_xnu_load_kext_from_dir_load, &ctx); + + if (ctx.plistname && grub_xnu_check_os_bundle_required + (ctx.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 (ctx.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", ctx.plistname, binname); + binfile = grub_file_open (binname, GRUB_FILE_TYPE_XNU_KEXT); + if (! binfile) + grub_errno = GRUB_ERR_NONE; + + /* Load the extension. */ + grub_xnu_load_driver (ctx.plistname, binfile, + binname); + grub_free (binname); + grub_free (binsuffix); + } + else + { + grub_dprintf ("xnu", "%s:0\n", ctx.plistname); + grub_xnu_load_driver (ctx.plistname, 0, 0); + } + } + grub_free (ctx.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, N_("you need to load the kernel first")); + + if (argc == 2) + { + /* User explicitly specified plist and binary. */ + if (grub_strcmp (args[1], "-") != 0) + { + binfile = grub_file_open (args[1], GRUB_FILE_TYPE_XNU_KEXT); + if (! binfile) + return grub_errno; + } + return grub_xnu_load_driver (grub_strcmp (args[0], "-") ? args[0] : 0, + binfile, args[1]); + } + + /* load kext normally. */ + if (argc == 1) + return grub_xnu_load_kext_from_dir (args[0], 0, 10); + + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); +} + +/* 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, N_("you need to load the kernel first")); + + 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_errno; + 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) +{ + struct grub_env_var *var; + FOR_SORTED_ENV (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) + continue; + + 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 grub_errno; + + 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 grub_errno; + + unescape (name, curdot, nextdot, &len); + name[len] = 0; + + curvalue = grub_xnu_create_value (curkey, name); + grub_free (name); + if (!curvalue) + return grub_errno; + + data = grub_malloc (grub_strlen (var->value) + 1); + if (!data) + return grub_errno; + + unescape (data, var->value, var->value + grub_strlen (var->value), + &len); + curvalue->datasize = len; + curvalue->data = data; + } + + 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, N_("Background image mode."), N_("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, N_("filename expected")); + + if (! grub_xnu_heap_size) + return grub_error (GRUB_ERR_BAD_OS, N_("you need to load the kernel first")); + + 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, N_("filename expected")); + + 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_lockdown ("xnu_mkext", grub_cmd_xnu_mkext, 0, + N_("Load XNU extension package.")); + cmd_kext = grub_register_command_lockdown ("xnu_kext", grub_cmd_xnu_kext, 0, + N_("Load XNU extension.")); + cmd_kextdir = grub_register_command_lockdown ("xnu_kextdir", grub_cmd_xnu_kextdir, + /* + * TRANSLATORS: OSBundleRequired is + * a variable name in xnu extensions + * manifests. It behaves mostly like + * GNU/Linux runlevels. + */ + N_("DIRECTORY [OSBundleRequired]"), + /* + * TRANSLATORS: There are many extensions + * in extension directory. + */ + N_("Load XNU extension directory.")); + cmd_ramdisk = grub_register_command ("xnu_ramdisk", grub_cmd_xnu_ramdisk, 0, + /* TRANSLATORS: ramdisk here isn't identifier. It can be translated. */ + N_("Load XNU ramdisk. " + "It will be available in OS 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 an image of hibernated" + " XNU.")); +#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 (); +} |