diff --git a/conf/i386-efi.rmk b/conf/i386-efi.rmk index 09c8470..683dda3 100644 --- a/conf/i386-efi.rmk +++ b/conf/i386-efi.rmk @@ -98,7 +98,7 @@ kernel_mod_SOURCES = kern/i386/efi/startup.S kern/main.c kern/device.c \ kernel_mod_HEADERS = boot.h cache.h device.h disk.h dl.h elf.h elfload.h \ env.h err.h file.h fs.h kernel.h loader.h misc.h mm.h net.h parser.h \ partition.h pc_partition.h rescue.h symbol.h term.h time.h types.h \ - efi/efi.h efi/time.h efi/disk.h list.h handler.h command.h + efi/efi.h efi/time.h efi/disk.h i386/pit.h list.h handler.h command.h kernel_mod_CFLAGS = $(COMMON_CFLAGS) kernel_mod_ASFLAGS = $(COMMON_ASFLAGS) kernel_mod_LDFLAGS = $(COMMON_LDFLAGS) @@ -194,5 +194,12 @@ fixvideo_mod_SOURCES = commands/efi/fixvideo.c fixvideo_mod_CFLAGS = $(COMMON_CFLAGS) fixvideo_mod_LDFLAGS = $(COMMON_LDFLAGS) +pkglib_MODULES += xnu.mod +xnu_mod_SOURCES = loader/xnu_resume.c loader/i386/xnu.c loader/i386/efi/xnu.c\ + loader/macho.c loader/xnu.c loader/i386/xnu_helper.S +xnu_mod_CFLAGS = $(COMMON_CFLAGS) -Werror -Wall +xnu_mod_LDFLAGS = $(COMMON_LDFLAGS) +xnu_mod_ASFLAGS = $(COMMON_ASFLAGS) + include $(srcdir)/conf/i386.mk include $(srcdir)/conf/common.mk diff --git a/conf/i386-pc.rmk b/conf/i386-pc.rmk index 7a6d79a..2dadcc8 100644 --- a/conf/i386-pc.rmk +++ b/conf/i386-pc.rmk @@ -61,7 +61,7 @@ kernel_img_HEADERS = boot.h cache.h device.h disk.h dl.h elf.h elfload.h \ partition.h pc_partition.h rescue.h symbol.h term.h time.h types.h \ machine/biosdisk.h machine/boot.h machine/console.h machine/init.h \ machine/memory.h machine/loader.h machine/vga.h machine/vbe.h \ - machine/kernel.h machine/pxe.h list.h handler.h command.h + machine/kernel.h machine/pxe.h i386/pit.h list.h handler.h command.h kernel_img_CFLAGS = $(COMMON_CFLAGS) kernel_img_ASFLAGS = $(COMMON_ASFLAGS) kernel_img_LDFLAGS = $(COMMON_LDFLAGS) $(TARGET_IMG_LDFLAGS) -Wl,-Ttext,$(GRUB_MEMORY_MACHINE_LINK_ADDR) $(COMMON_CFLAGS) @@ -225,6 +225,13 @@ linux_mod_SOURCES = loader/i386/linux.c linux_mod_CFLAGS = $(COMMON_CFLAGS) linux_mod_LDFLAGS = $(COMMON_LDFLAGS) +pkglib_MODULES += xnu.mod +xnu_mod_SOURCES = loader/xnu_resume.c loader/i386/xnu.c loader/i386/pc/xnu.c\ + loader/macho.c loader/xnu.c loader/i386/xnu_helper.S +xnu_mod_CFLAGS = $(COMMON_CFLAGS) -Werror -Wall +xnu_mod_LDFLAGS = $(COMMON_LDFLAGS) +xnu_mod_ASFLAGS = $(COMMON_ASFLAGS) + # # Only arch dependant part of normal.mod will be here. Common part for # all architecures of normal.mod is at start and should be kept at sync diff --git a/conf/x86_64-efi.rmk b/conf/x86_64-efi.rmk index 59237c1..199aed7 100644 --- a/conf/x86_64-efi.rmk +++ b/conf/x86_64-efi.rmk @@ -100,7 +100,7 @@ kernel_mod_SOURCES = kern/x86_64/efi/startup.S kern/x86_64/efi/callwrap.S \ kernel_mod_HEADERS = boot.h cache.h device.h disk.h dl.h elf.h elfload.h \ env.h err.h file.h fs.h kernel.h loader.h misc.h mm.h net.h parser.h \ partition.h pc_partition.h rescue.h symbol.h term.h time.h types.h \ - efi/efi.h efi/time.h efi/disk.h machine/loader.h list.h handler.h \ + efi/efi.h efi/time.h efi/disk.h machine/loader.h i386/pit.h list.h handler.h \ command.h kernel_mod_CFLAGS = $(COMMON_CFLAGS) kernel_mod_ASFLAGS = $(COMMON_ASFLAGS) @@ -197,5 +197,12 @@ fixvideo_mod_SOURCES = commands/efi/fixvideo.c fixvideo_mod_CFLAGS = $(COMMON_CFLAGS) fixvideo_mod_LDFLAGS = $(COMMON_LDFLAGS) +pkglib_MODULES += xnu.mod +xnu_mod_SOURCES = loader/xnu_resume.c loader/i386/xnu.c loader/i386/efi/xnu.c\ + loader/macho.c loader/xnu.c loader/i386/xnu_helper.S +xnu_mod_CFLAGS = $(COMMON_CFLAGS) -Werror -Wall +xnu_mod_LDFLAGS = $(COMMON_LDFLAGS) +xnu_mod_ASFLAGS = $(COMMON_ASFLAGS) + include $(srcdir)/conf/i386.mk include $(srcdir)/conf/common.mk diff --git a/include/grub/efi/efi.h b/include/grub/efi/efi.h index 8c277c0..916f9d6 100644 --- a/include/grub/efi/efi.h +++ b/include/grub/efi/efi.h @@ -56,6 +56,7 @@ EXPORT_FUNC(grub_efi_get_device_path) (grub_efi_handle_t handle); int EXPORT_FUNC(grub_efi_exit_boot_services) (grub_efi_uintn_t map_key); void EXPORT_FUNC (grub_reboot) (void); void EXPORT_FUNC (grub_halt) (void); +int EXPORT_FUNC (grub_efi_finish_boot_services) (void); void grub_efi_mm_init (void); void grub_efi_mm_fini (void); diff --git a/include/grub/i386/macho.h b/include/grub/i386/macho.h new file mode 100644 index 0000000..61e72a7 --- /dev/null +++ b/include/grub/i386/macho.h @@ -0,0 +1,11 @@ +#define GRUB_MACHO_CPUTYPE_IS_HOST32(x) ((x)==0x00000007) +#define GRUB_MACHO_CPUTYPE_IS_HOST64(x) ((x)==0x01000007) + +struct grub_macho_thread32 +{ + grub_uint32_t cmd; + grub_uint32_t cmdsize; + grub_uint8_t unknown1[48]; + grub_uint32_t entry_point; + grub_uint8_t unknown2[20]; +} __attribute__ ((packed)); diff --git a/include/grub/i386/pit.h b/include/grub/i386/pit.h index 0da271d..ff9b9a6 100644 --- a/include/grub/i386/pit.h +++ b/include/grub/i386/pit.h @@ -20,7 +20,8 @@ #define KERNEL_CPU_PIT_HEADER 1 #include +#include -extern void grub_pit_wait (grub_uint16_t tics); +void EXPORT_FUNC(grub_pit_wait) (grub_uint16_t tics); #endif /* ! KERNEL_CPU_PIT_HEADER */ diff --git a/include/grub/i386/xnu.h b/include/grub/i386/xnu.h new file mode 100644 index 0000000..435b947 --- /dev/null +++ b/include/grub/i386/xnu.h @@ -0,0 +1,58 @@ +#ifndef GRUB_CPU_XNU_H +#define GRUB_CPU_XNU_H 1 + +#define GRUB_XNU_PAGESIZE 4096 +typedef grub_uint32_t grub_xnu_ptr_t; + +struct grub_xnu_boot_params +{ + grub_uint16_t verminor; + grub_uint16_t vermajor; + /* Command line passed to xnu. */ + grub_uint8_t cmdline[1024]; + + /* Later are the same as EFI's get_memory_map (). */ + grub_xnu_ptr_t efi_mmap; + grub_uint32_t efi_mmap_size; + grub_uint32_t efi_mem_desc_size; + grub_uint32_t efi_mem_desc_version; + + /* Later are video parameters. */ + grub_xnu_ptr_t lfb_base; +#define GRUB_XNU_VIDEO_SPLASH 1 +#define GRUB_XNU_VIDEO_TEXT_IN_VIDEO 2 + grub_uint32_t lfb_mode; + grub_uint32_t lfb_line_len; + grub_uint32_t lfb_width; + grub_uint32_t lfb_height; + grub_uint32_t lfb_depth; + + /* Pointer to device tree and its len. */ + grub_xnu_ptr_t devtree; + grub_uint32_t devtreelen; + + /* First used address by kernel or boot structures. */ + grub_xnu_ptr_t heap_start; + /* Last used address by kernel or boot structures minus previous value. */ + grub_uint32_t heap_size; + + /* First memory page containing runtime code or data. */ + grub_uint32_t efi_runtime_first_page; + /* First memory page containing runtime code or data minus previous value. */ + grub_uint32_t efi_runtime_npages; + grub_uint32_t efi_system_table; + /* Size of grub_efi_uintn_t in bits. */ + grub_uint8_t efi_uintnbits; +} __attribute__ ((packed)); +#define GRUB_XNU_BOOTARGS_VERMINOR 4 +#define GRUB_XNU_BOOTARGS_VERMAJOR 1 + +grub_err_t grub_xnu_launch (void); +extern grub_uint32_t grub_xnu_entry_point; +extern grub_uint32_t grub_xnu_stack; +extern grub_uint32_t grub_xnu_arg1; +extern char grub_xnu_cmdline[1024]; +grub_err_t grub_xnu_boot (void); +grub_err_t grub_cpu_xnu_fill_devicetree (void); +grub_err_t grub_xnu_set_video (struct grub_xnu_boot_params *bootparams_relloc); +#endif diff --git a/include/grub/macho.h b/include/grub/macho.h new file mode 100644 index 0000000..7dfd54e --- /dev/null +++ b/include/grub/macho.h @@ -0,0 +1,107 @@ +/* + * 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 . + */ + +#ifndef GRUB_MACHO_H +#define GRUB_MACHO_H 1 +#include + +/* Multi-architecture header. Always in big-endian. */ +struct grub_macho_fat_header +{ + grub_uint32_t magic; + grub_uint32_t nfat_arch; +} __attribute__ ((packed)); +#define GRUB_MACHO_FAT_MAGIC 0xcafebabe + +typedef grub_uint32_t grub_macho_cpu_type_t; +typedef grub_uint32_t grub_macho_cpu_subtype_t; + +/* Architecture descriptor. Always in big-endian. */ +struct grub_macho_fat_arch +{ + grub_macho_cpu_type_t cputype; + grub_macho_cpu_subtype_t cpusubtype; + grub_uint32_t offset; + grub_uint32_t size; + grub_uint32_t align; +} __attribute__ ((packed));; + +/* File header for 32-bit. Always in native-endian. */ +struct grub_macho_header32 +{ +#define GRUB_MACHO_MAGIC32 0xfeedface + grub_uint32_t magic; + grub_macho_cpu_type_t cputype; + grub_macho_cpu_subtype_t cpusubtype; + grub_uint32_t filetype; + grub_uint32_t ncmds; + grub_uint32_t sizeofcmds; + grub_uint32_t flags; +} __attribute__ ((packed)); + +/* File header for 64-bit. Always in native-endian. */ +struct grub_macho_header64 +{ +#define GRUB_MACHO_MAGIC64 0xfeedfacf + grub_uint32_t magic; + grub_macho_cpu_type_t cputype; + grub_macho_cpu_subtype_t cpusubtype; + grub_uint32_t filetype; + grub_uint32_t ncmds; + grub_uint32_t sizeofcmds; + grub_uint32_t flags; + grub_uint32_t reserved; +} __attribute__ ((packed)); + +/* Convenience union. What do we need to load to identify the file type. */ +union grub_macho_filestart +{ + struct grub_macho_fat_header fat; + struct grub_macho_header32 thin32; + struct grub_macho_header64 thin64; +} __attribute__ ((packed)); + +/* Common header of Mach-O commands. */ +struct grub_macho_cmd +{ + grub_uint32_t cmd; + grub_uint32_t cmdsize; +} __attribute__ ((packed)); + +typedef grub_uint32_t grub_macho_vmprot_t; + +/* 32-bit segment command. */ +struct grub_macho_segment32 +{ +#define GRUB_MACHO_CMD_SEGMENT32 1 + grub_uint32_t cmd; + grub_uint32_t cmdsize; + grub_uint8_t segname[16]; + grub_uint32_t vmaddr; + grub_uint32_t vmsize; + grub_uint32_t fileoff; + grub_uint32_t filesize; + grub_macho_vmprot_t maxprot; + grub_macho_vmprot_t initprot; + grub_uint32_t nsects; + grub_uint32_t flags; +} __attribute__ ((packed)); + +#define GRUB_MACHO_CMD_THREAD 5 + +#endif diff --git a/include/grub/machoload.h b/include/grub/machoload.h new file mode 100644 index 0000000..572496f --- /dev/null +++ b/include/grub/machoload.h @@ -0,0 +1,62 @@ +/* + * 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 . + */ + +#ifndef GRUB_MACHOLOAD_HEADER +#define GRUB_MACHOLOAD_HEADER 1 + +#include +#include +#include +#include +#include + +struct grub_macho_file +{ + grub_file_t file; + grub_ssize_t offset32; + grub_ssize_t end32; + int ncmds32; + grub_size_t cmdsize32; + grub_uint8_t *cmds32; + grub_ssize_t offset64; + grub_ssize_t end64; + int ncmds64; + grub_size_t cmdsize64; + grub_uint8_t *cmds64; +}; +typedef struct grub_macho_file *grub_macho_t; + +grub_macho_t grub_macho_open (const char *); +grub_macho_t grub_macho_file (grub_file_t); +grub_err_t grub_macho_close (grub_macho_t); + +int grub_macho_contains_macho32 (grub_macho_t); +grub_err_t grub_macho32_size (grub_macho_t macho, grub_addr_t *segments_start, + grub_addr_t *segments_end, int flags); +grub_uint32_t grub_macho32_get_entry_point (grub_macho_t macho); + +/* Ignore BSS segments when loading. */ +#define GRUB_MACHO_NOBSS 0x1 +grub_err_t grub_macho32_load (grub_macho_t macho, char *offset, int flags); + +/* Like filesize and file_read but take only 32-bit part + for current architecture. */ +grub_size_t grub_macho32_filesize (grub_macho_t macho); +grub_err_t grub_macho32_readfile (grub_macho_t macho, void *dest); + +#endif /* ! GRUB_MACHOLOAD_HEADER */ diff --git a/include/grub/x86_64/macho.h b/include/grub/x86_64/macho.h new file mode 100644 index 0000000..165b8da --- /dev/null +++ b/include/grub/x86_64/macho.h @@ -0,0 +1 @@ +#include diff --git a/include/grub/x86_64/xnu.h b/include/grub/x86_64/xnu.h new file mode 100644 index 0000000..ae61733 --- /dev/null +++ b/include/grub/x86_64/xnu.h @@ -0,0 +1 @@ +#include diff --git a/include/grub/xnu.h b/include/grub/xnu.h new file mode 100644 index 0000000..08ae49b --- /dev/null +++ b/include/grub/xnu.h @@ -0,0 +1,105 @@ +/* + * 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 . + */ + +#ifndef GRUB_XNU_H +#define GRUB_XNU_H 1 + +/* Header of a hibernation image. */ +struct grub_xnu_hibernate_header +{ + /* Size of the image. Notice that file containing image is usually bigger. */ + grub_uint64_t image_size; + grub_uint8_t unknown1[8]; + /* Where to copy lauchcode?*/ + grub_uint32_t launchcode_target_page; + /* How many pages of launchcode? */ + grub_uint32_t launchcode_numpages; + /* Where to jump? */ + grub_uint32_t entry_point; + /* %esp at start. */ + grub_uint32_t stack; + grub_uint8_t unknown2[44]; +#define GRUB_XNU_HIBERNATE_MAGIC 0x73696d65 + grub_uint32_t magic; + grub_uint8_t unknown3[28]; + /* This value is non-zero if page is encrypted. Unsupported. */ + grub_uint64_t encoffset; + grub_uint8_t unknown4[360]; + /* The size of additional header used to locate image without parsing FS. + Used only to skip it. + */ + grub_uint32_t extmapsize; +} __attribute__ ((packed)); + +/* In-memory structure for temporary keeping device tree. */ +struct grub_xnu_devtree_key +{ + char *name; + int datasize; /* -1 for not leaves. */ + union + { + struct grub_xnu_devtree_key *first_child; + void *data; + }; + struct grub_xnu_devtree_key *next; +}; + +/* A structure used in memory-map values. */ +struct +grub_xnu_extdesc +{ + grub_uint32_t addr; + grub_uint32_t size; +} __attribute__ ((packed)); + +/* Header describing extension in the memory. */ +struct grub_xnu_extheader +{ + grub_uint32_t infoplistaddr; + grub_uint32_t infoplistsize; + grub_uint32_t binaryaddr; + grub_uint32_t binarysize; +} __attribute__ ((packed)); + +struct grub_xnu_devtree_key *grub_xnu_create_key (struct grub_xnu_devtree_key **parent, + char *name); + +extern struct grub_xnu_devtree_key *grub_xnu_devtree_root; + +void grub_xnu_free_devtree (struct grub_xnu_devtree_key *cur); + +grub_err_t grub_xnu_writetree_toheap (void **start, grub_size_t *size); +struct grub_xnu_devtree_key *grub_xnu_create_value (struct grub_xnu_devtree_key **parent, + char *name); + +void grub_xnu_lock (void); +void grub_xnu_unlock (void); +grub_err_t grub_xnu_resume (char *imagename); +struct grub_xnu_devtree_key *grub_xnu_find_key (struct grub_xnu_devtree_key *parent, + char *name); +grub_err_t grub_xnu_align_heap (int align); +grub_err_t grub_xnu_scan_dir_for_kexts (char *dirname, char *osbundlerequired, + int maxrecursion); +grub_err_t grub_xnu_load_kext_from_dir (char *dirname, char *osbundlerequired, + int maxrecursion); +void *grub_xnu_heap_malloc (int size); +extern grub_uint32_t grub_xnu_heap_real_start; +extern grub_size_t grub_xnu_heap_size; +extern char *grub_xnu_heap_start; +extern grub_addr_t grub_xnu_heap_will_be_at; +#endif diff --git a/kern/efi/efi.c b/kern/efi/efi.c index 9c9a400..25007e3 100644 --- a/kern/efi/efi.c +++ b/kern/efi/efi.c @@ -734,3 +734,26 @@ grub_efi_print_device_path (grub_efi_device_path_t *dp) dp = (grub_efi_device_path_t *) ((char *) dp + len); } } + +int +grub_efi_finish_boot_services (void) +{ + grub_efi_uintn_t mmap_size = 0; + grub_efi_uintn_t map_key; + grub_efi_uintn_t desc_size; + grub_efi_uint32_t desc_version; + void *mmap_buf = 0; + + if (grub_efi_get_memory_map (&mmap_size, mmap_buf, &map_key, + &desc_size, &desc_version) < 0) + return 0; + + mmap_buf = grub_malloc (mmap_size); + + if (grub_efi_get_memory_map (&mmap_size, mmap_buf, &map_key, + &desc_size, &desc_version) <= 0) + return 0; + + return grub_efi_exit_boot_services (map_key); +} + diff --git a/kern/efi/mm.c b/kern/efi/mm.c index 35b12ab..4635776 100644 --- a/kern/efi/mm.c +++ b/kern/efi/mm.c @@ -47,7 +47,7 @@ static struct allocated_page *allocated_pages = 0; /* The minimum and maximum heap size for GRUB itself. */ #define MIN_HEAP_SIZE 0x100000 -#define MAX_HEAP_SIZE (16 * 0x100000) +#define MAX_HEAP_SIZE (1600 * 0x100000) /* Allocate pages. Return the pointer to the first of allocated pages. */ diff --git a/loader/i386/efi/xnu.c b/loader/i386/efi/xnu.c new file mode 100644 index 0000000..5085cdb --- /dev/null +++ b/loader/i386/efi/xnu.c @@ -0,0 +1,177 @@ +/* + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Setup video for xnu. Big parts are copied from linux.c. */ + +static grub_efi_guid_t uga_draw_guid = GRUB_EFI_UGA_DRAW_GUID; + +#define RGB_MASK 0xffffff +#define RGB_MAGIC 0x121314 +#define LINE_MIN 800 +#define LINE_MAX 4096 +#define FBTEST_STEP (0x10000 >> 2) +#define FBTEST_COUNT 8 + +static int +find_line_len (grub_uint32_t *fb_base, grub_uint32_t *line_len) +{ + grub_uint32_t *base = (grub_uint32_t *) (grub_target_addr_t) *fb_base; + int i; + + for (i = 0; i < FBTEST_COUNT; i++, base += FBTEST_STEP) + { + if ((*base & RGB_MASK) == RGB_MAGIC) + { + int j; + + for (j = LINE_MIN; j <= LINE_MAX; j++) + { + if ((base[j] & RGB_MASK) == RGB_MAGIC) + { + *fb_base = (grub_uint32_t) (grub_target_addr_t) base; + *line_len = j << 2; + + return 1; + } + } + + break; + } + } + + return 0; +} + +static int +find_framebuf (grub_uint32_t *fb_base, grub_uint32_t *line_len) +{ + int found = 0; + + auto int NESTED_FUNC_ATTR find_card (int bus, int dev, int func, + grub_pci_id_t pciid); + + int NESTED_FUNC_ATTR find_card (int bus, int dev, int func, + grub_pci_id_t pciid) + { + grub_pci_address_t addr; + + addr = grub_pci_make_address (bus, dev, func, 2); + if (grub_pci_read (addr) >> 24 == 0x3) + { + int i; + + grub_printf ("Display controller: %d:%d.%d\nDevice id: %x\n", + bus, dev, func, pciid); + addr += 8; + for (i = 0; i < 6; i++, addr += 4) + { + grub_uint32_t old_bar1, old_bar2, type; + grub_uint64_t base64; + + old_bar1 = grub_pci_read (addr); + if ((! old_bar1) || (old_bar1 & GRUB_PCI_ADDR_SPACE_IO)) + continue; + + type = old_bar1 & GRUB_PCI_ADDR_MEM_TYPE_MASK; + if (type == GRUB_PCI_ADDR_MEM_TYPE_64) + { + if (i == 5) + break; + + old_bar2 = grub_pci_read (addr + 4); + } + else + old_bar2 = 0; + + base64 = old_bar2; + base64 <<= 32; + base64 |= (old_bar1 & GRUB_PCI_ADDR_MEM_MASK); + + grub_printf ("%s(%d): 0x%llx\n", + ((old_bar1 & GRUB_PCI_ADDR_MEM_PREFETCH) ? + "VMEM" : "MMIO"), i, + (unsigned long long) base64); + + if ((old_bar1 & GRUB_PCI_ADDR_MEM_PREFETCH) && (! found)) + { + *fb_base = base64; + if (find_line_len (fb_base, line_len)) + found++; + } + + if (type == GRUB_PCI_ADDR_MEM_TYPE_64) + { + i++; + addr += 4; + } + } + } + + return found; + } + + grub_pci_iterate (find_card); + return found; +} + +grub_err_t +grub_xnu_set_video (struct grub_xnu_boot_params *params) +{ + grub_efi_uga_draw_protocol_t *c; + grub_uint32_t width, height, depth, rate, pixel, fb_base, line_len; + int ret; + + c = grub_efi_locate_protocol (&uga_draw_guid, 0); + if (! c) + return grub_error (GRUB_ERR_IO, "Couldn't find UGADraw"); + + if (efi_call_5 (c->get_mode, c, &width, &height, &depth, &rate)) + return grub_error (GRUB_ERR_IO, "Couldn't retrieve video mode"); + + grub_printf ("Video mode: address@hidden", width, height, depth, rate); + + grub_efi_set_text_mode (0); + pixel = RGB_MAGIC; + efi_call_10 (c->blt, c, (struct grub_efi_uga_pixel *) &pixel, + GRUB_EFI_UGA_VIDEO_FILL, 0, 0, 0, 0, 1, height, 0); + ret = find_framebuf (&fb_base, &line_len); + grub_efi_set_text_mode (1); + + if (! ret) + return grub_error (GRUB_ERR_IO, "Can\'t find frame buffer address\n"); + + grub_printf ("Frame buffer base: 0x%x\n", fb_base); + grub_printf ("Video line length: %d\n", line_len); + + params->lfb_width = width; + params->lfb_height = height; + params->lfb_depth = depth; + params->lfb_line_len = line_len; + params->lfb_mode = GRUB_XNU_VIDEO_TEXT_IN_VIDEO; + params->lfb_base = fb_base; + return GRUB_ERR_NONE; +} diff --git a/loader/i386/pc/xnu.c b/loader/i386/pc/xnu.c new file mode 100644 index 0000000..2c78b2b --- /dev/null +++ b/loader/i386/pc/xnu.c @@ -0,0 +1,67 @@ +/* + * 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 . + */ + +#include +#include +#include +#include +#include +#include + +/* Setup video for xnu. */ + +grub_err_t +grub_xnu_set_video (struct grub_xnu_boot_params *bootparams_relloc) +{ + grub_uint32_t use_mode = GRUB_VBE_DEFAULT_VIDEO_MODE; + char *modevar; + grub_err_t err; + struct grub_vbe_mode_info_block mode_info; + + /* Check existence of vbe_mode environment variable. */ + modevar = grub_env_get ("vbe_mode"); + if (modevar != 0) + { + unsigned long value; + + value = grub_strtoul (modevar, 0, 0); + if (grub_errno == GRUB_ERR_NONE) + use_mode = value; + else + grub_errno = GRUB_ERR_NONE; + } + + if (use_mode < 0x100) + return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, + "booting xnu in text mode isn't supported yet"); + err = grub_vbe_set_video_mode (use_mode, &mode_info); + if (err != GRUB_ERR_NONE) + return err; + + /* FIXME: setting non-32bit color depth results in strange screens. */ + if (use_mode >= 0x100) + { + bootparams_relloc->lfb_base = mode_info.phys_base_addr; + bootparams_relloc->lfb_mode = GRUB_XNU_VIDEO_TEXT_IN_VIDEO; + bootparams_relloc->lfb_line_len = mode_info.bytes_per_scan_line; + bootparams_relloc->lfb_width = mode_info.x_resolution; + bootparams_relloc->lfb_height = mode_info.y_resolution; + bootparams_relloc->lfb_depth = mode_info.bits_per_pixel; + } + return GRUB_ERR_NONE; +} diff --git a/loader/i386/xnu.c b/loader/i386/xnu.c new file mode 100644 index 0000000..ba82c50 --- /dev/null +++ b/loader/i386/xnu.c @@ -0,0 +1,516 @@ +/* + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +char grub_xnu_cmdline[1024]; + +/* Aliases set for some tables. */ +struct tbl_alias +{ + grub_efi_guid_t guid; + char *name; +}; + +struct tbl_alias table_aliases[] = + { + {GRUB_EFI_ACPI_20_TABLE_GUID, "ACPI_20"}, + {GRUB_EFI_ACPI_TABLE_GUID, "ACPI"}, + }; + +/* The following function is used to be able to debug xnu loader + with grub-emu. */ +#ifdef GRUB_UTIL +grub_err_t +grub_xnu_launch (void) +{ + grub_printf ("Fake launch %x:%p:%p", grub_xnu_entry_point, grub_xnu_arg1, + grub_xnu_stack); + grub_getkey (); + return 0; +} +#endif + +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; + 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; + + /* Check Speedstep. */ + if (max_cpuid < 1) + return sane_value; + + asm volatile ("movl $1, %%eax\n" + "cpuid" + : "=c" (capabilities): + : "%eax", "%ebx", "%edx"); + + 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); +} + +/* 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_error (GRUB_ERR_OUT_OF_MEMORY, "couldn't create device tree"); + 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_error (GRUB_ERR_OUT_OF_MEMORY, "couldn't create device tree"); + 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_error (GRUB_ERR_OUT_OF_MEMORY, "couldn't create device tree"); + 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_error (GRUB_ERR_OUT_OF_MEMORY, "couldn't create device tree"); + + /* 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_sprintf (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_sprintf (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_error (GRUB_ERR_OUT_OF_MEMORY, + "couldn't create device tree"); + 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_error (GRUB_ERR_OUT_OF_MEMORY, + "couldn't create device tree"); + 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_error (GRUB_ERR_OUT_OF_MEMORY, + "couldn't create device tree"); + 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_error (GRUB_ERR_OUT_OF_MEMORY, + "couldn't create device tree"); + 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; +} + +/* Boot xnu. */ +grub_err_t +grub_xnu_boot (void) +{ + struct grub_xnu_boot_params *bootparams_relloc; + grub_off_t bootparams_relloc_off; + grub_off_t mmap_relloc_off; + grub_err_t err; + grub_efi_uintn_t memory_map_size = 0; + grub_efi_memory_descriptor_t *memory_map; + grub_efi_uintn_t map_key = 0; + grub_efi_uintn_t descriptor_size = 0; + grub_efi_uint32_t descriptor_version = 0; + grub_uint64_t firstruntimeaddr, lastruntimeaddr; + void *devtree; + grub_size_t devtreelen; + int i; + + /* 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; + + if (grub_autoefi_get_memory_map (&memory_map_size, memory_map, + &map_key, &descriptor_size, + &descriptor_version) < 0) + return grub_errno; + + memory_map = grub_xnu_heap_malloc (memory_map_size); + if (! memory_map) + return grub_errno; + + if (grub_autoefi_get_memory_map (&memory_map_size, memory_map, + &map_key, &descriptor_size, + &descriptor_version) <= 0) + return grub_errno; + mmap_relloc_off = (grub_uint8_t *) memory_map + - (grub_uint8_t *) grub_xnu_heap_start; + + firstruntimeaddr = (grub_uint64_t) (-1); + lastruntimeaddr = 0; + 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); + + /* Some EFI implementations set physical_start to 0 which + causes XNU crash. */ + curdesc->virtual_start = curdesc->physical_start; + + if (curdesc->type == GRUB_EFI_RUNTIME_SERVICES_DATA + || curdesc->type == GRUB_EFI_RUNTIME_SERVICES_CODE) + { + if (firstruntimeaddr > curdesc->physical_start) + firstruntimeaddr = curdesc->physical_start; + if (lastruntimeaddr < curdesc->physical_start + + curdesc->num_pages * 4096) + lastruntimeaddr = curdesc->physical_start + + curdesc->num_pages * 4096; + } + } + + /* Relocate the boot parameters to heap. */ + bootparams_relloc = grub_xnu_heap_malloc (sizeof (*bootparams_relloc)); + if (! bootparams_relloc) + return grub_errno; + bootparams_relloc_off = (grub_uint8_t *) bootparams_relloc + - (grub_uint8_t *) grub_xnu_heap_start; + err = grub_xnu_writetree_toheap (&devtree, &devtreelen); + if (err) + return err; + bootparams_relloc = (struct grub_xnu_boot_params *) + (bootparams_relloc_off + (grub_uint8_t *) grub_xnu_heap_start); + + grub_memcpy (bootparams_relloc->cmdline, grub_xnu_cmdline, + sizeof (bootparams_relloc->cmdline)); + + bootparams_relloc->devtree = PTR_TO_UINT32 (devtree); + bootparams_relloc->devtreelen = devtreelen; + + bootparams_relloc->heap_start = PTR_TO_UINT32 (grub_xnu_heap_start); + bootparams_relloc->heap_size = grub_xnu_heap_size; + + bootparams_relloc->efi_mmap + = PTR_TO_UINT32 ((grub_uint8_t *)grub_xnu_heap_start + mmap_relloc_off); + bootparams_relloc->efi_mmap_size = memory_map_size; + bootparams_relloc->efi_mem_desc_size = descriptor_size; + bootparams_relloc->efi_mem_desc_version = descriptor_version; + + bootparams_relloc->efi_runtime_first_page = firstruntimeaddr + / GRUB_XNU_PAGESIZE; + bootparams_relloc->efi_runtime_npages + = ((lastruntimeaddr + GRUB_XNU_PAGESIZE - 1) / GRUB_XNU_PAGESIZE) + - (firstruntimeaddr / GRUB_XNU_PAGESIZE); + bootparams_relloc->efi_uintnbits = SIZEOF_OF_UINTN * 8; + bootparams_relloc->efi_system_table + = PTR_TO_UINT32 (grub_autoefi_system_table); + + bootparams_relloc->verminor = GRUB_XNU_BOOTARGS_VERMINOR; + bootparams_relloc->vermajor = GRUB_XNU_BOOTARGS_VERMAJOR; + + /* Set video. */ + err = grub_xnu_set_video (bootparams_relloc); + if (err != GRUB_ERR_NONE) + { + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + grub_printf ("Booting in blind mode\n"); + + bootparams_relloc->lfb_mode = 0; + bootparams_relloc->lfb_width = 0; + bootparams_relloc->lfb_height = 0; + bootparams_relloc->lfb_depth = 0; + bootparams_relloc->lfb_line_len = 0; + bootparams_relloc->lfb_base = 0; + } + + /* Parameters for asm helper. */ + grub_xnu_stack = bootparams_relloc->heap_start + + bootparams_relloc->heap_size + GRUB_XNU_PAGESIZE; + grub_xnu_arg1 = (long) bootparams_relloc; + grub_xnu_entry_point = PTR_TO_UINT32 + (grub_xnu_heap_start + grub_xnu_entry_point); + grub_dprintf ("xnu", "eip=%x\n", grub_xnu_entry_point); + grub_dprintf ("xnu", "launch=%p\n", grub_xnu_launch); + if (! grub_autoefi_finish_boot_services ()) + return grub_error (GRUB_ERR_IO, "can't exit boot services"); + + grub_xnu_launch (); + + /* Never reaches here. */ + return 0; +} diff --git a/loader/i386/xnu_helper.S b/loader/i386/xnu_helper.S new file mode 100644 index 0000000..1504227 --- /dev/null +++ b/loader/i386/xnu_helper.S @@ -0,0 +1,107 @@ +/* + * 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 . + */ + +#include + + .p2align 2 /* force 4-byte alignment */ + +FUNCTION(grub_xnu_launch) + cli + +#ifdef __x86_64__ + /* Switch to compatibility mode. */ + + lgdt gdtdesc + + /* Update %cs. Thanks to David Miller for pointing this mistake out. */ + ljmp *jump_vector +cont1: + .code32 + + /* Update other registers. */ + mov $0x18, %eax + mov %eax, %ds + mov %eax, %es + mov %eax, %fs + mov %eax, %gs + mov %eax, %ss + + /* Disable paging. */ + mov %cr0, %eax + and $0x7fffffff, %eax + mov %eax, %cr0 + + /* Disable amd64. */ + mov $0xc0000080, %ecx + rdmsr + and $0xfffffeff, %eax + wrmsr + + /* Turn off PAE. */ + movl %cr4, %eax + and $0xffffffcf, %eax + mov %eax, %cr4 + + jmp cont2 +cont2: +#endif + .code32 + + /* Registers on XNU boot: eip, esp and eax. */ + /* mov imm32, %ecx */ + .byte 0xb9 +VARIABLE (grub_xnu_entry_point) + .long 0 + /* mov imm32, %eax */ + .byte 0xb8 +VARIABLE (grub_xnu_arg1) + .long 0 + /* mov imm32, %ebx */ + .byte 0xbb +VARIABLE (grub_xnu_stack) + .long 0 + + movl %ebx, %esp + + jmp *%ecx + +#ifdef __x86_64__ + /* GDT. Copied from loader/i386/linux.c. */ + .p2align 4 +gdt: + /* NULL. */ + .byte 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + + /* Reserved. */ + .byte 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + + /* Code segment. */ + .byte 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x9A, 0xCF, 0x00 + + /* Data segment. */ + .byte 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x92, 0xCF, 0x00 + +gdtdesc: + .word 31 + .quad gdt + + .p2align 4 +jump_vector: + .long cont1 + .long 0x10 +#endif \ No newline at end of file diff --git a/loader/macho.c b/loader/macho.c new file mode 100644 index 0000000..da081a2 --- /dev/null +++ b/loader/macho.c @@ -0,0 +1,395 @@ +/* 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 . + */ + +/* 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 +#include +#include +#include +#include +#include +#include +#include + +#define min(a,b) (((a) < (b)) ? (a) : (b)) + +/* 32-bit. */ + +int +grub_macho_contains_macho32 (grub_macho_t macho) +{ + return macho->offset32 != -1; +} + +static void +grub_macho_parse32 (grub_macho_t macho) +{ + struct grub_macho_header32 head; + + /* Is there any candidate at all? */ + if (macho->offset32 == -1) + return; + + /* Read header and check magic*/ + if (grub_file_seek (macho->file, macho->offset32) == (grub_off_t) -1 + || grub_file_read (macho->file, (char *) &head, sizeof (head)) + != sizeof(head)) + { + grub_error (GRUB_ERR_READ_ERROR, "Cannot read Mach-O header."); + macho->offset32 = -1; + return; + } + if (head.magic != GRUB_MACHO_MAGIC32) + { + grub_error (GRUB_ERR_BAD_OS, "Invalid Mach-O 32-bit header."); + macho->offset32 = -1; + return; + } + + /* Read commands. */ + macho->ncmds32 = head.ncmds; + macho->cmdsize32 = head.sizeofcmds; + macho->cmds32 = grub_malloc(macho->cmdsize32); + if (! macho->cmds32) + { + grub_error (GRUB_ERR_OUT_OF_MEMORY, "not enough memory to read commands"); + return; + } + if (grub_file_read (macho->file, (char *) macho->cmds32, + (grub_size_t) macho->cmdsize32) + != (grub_ssize_t) macho->cmdsize32) + { + grub_error (GRUB_ERR_READ_ERROR, "Cannot read Mach-O header."); + macho->offset32 = -1; + } +} + +typedef int NESTED_FUNC_ATTR (*grub_macho_iter_hook_t) +(grub_macho_t , struct grub_macho_cmd *, + void *); + +static grub_err_t +grub_macho32_cmds_iterate (grub_macho_t macho, + grub_macho_iter_hook_t hook, + void *hook_arg) +{ + grub_uint8_t *hdrs = macho->cmds32; + int i; + if (! macho->cmds32) + return grub_error (GRUB_ERR_BAD_OS, "Couldn't find 32-bit Mach-O"); + for (i = 0; i < macho->ncmds32; 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 +grub_macho32_filesize (grub_macho_t macho) +{ + if (grub_macho_contains_macho32 (macho)) + return macho->end32 - macho->offset32; + return 0; +} + +grub_err_t +grub_macho32_readfile (grub_macho_t macho, void *dest) +{ + grub_ssize_t read; + if (! grub_macho_contains_macho32 (macho)) + return grub_error (GRUB_ERR_BAD_OS, + "Couldn't read arcitecture-specific part"); + + if (grub_file_seek (macho->file, macho->offset32) == (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->end32 - macho->offset32); + if (read != (grub_ssize_t) (macho->end32 - macho->offset32)) + { + grub_error_push (); + return grub_error (GRUB_ERR_BAD_OS, + "Couldn't read arcitecture-specific part"); + } + return GRUB_ERR_NONE; +} + +/* Calculate the amount of memory spanned by the segments. */ +grub_err_t +grub_macho32_size (grub_macho_t macho, grub_addr_t *segments_start, + grub_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 UNUSED _macho, + struct grub_macho_cmd *hdr0, void UNUSED *_arg) + { + struct grub_macho_segment32 *hdr = (struct grub_macho_segment32 *) hdr0; + if (hdr->cmd != GRUB_MACHO_CMD_SEGMENT32) + 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_uint32_t) -1; + *segments_end = 0; + + grub_macho32_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 +grub_macho32_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 UNUSED *_arg); + int NESTED_FUNC_ATTR do_load(grub_macho_t _macho, + struct grub_macho_cmd *hdr0, + void UNUSED *_arg) + { + struct grub_macho_segment32 *hdr = (struct grub_macho_segment32 *) hdr0; + + if (hdr->cmd != GRUB_MACHO_CMD_SEGMENT32) + 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->offset32) == (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_macho32_cmds_iterate (macho, do_load, 0); + + return err; +} + +grub_uint32_t +grub_macho32_get_entry_point (grub_macho_t macho) +{ + grub_uint32_t entry_point = 0; + auto int NESTED_FUNC_ATTR hook(grub_macho_t _macho, + struct grub_macho_cmd *hdr, + void UNUSED *_arg); + int NESTED_FUNC_ATTR hook(grub_macho_t UNUSED _macho, + struct grub_macho_cmd *hdr, + void UNUSED *_arg) + { + if (hdr->cmd == GRUB_MACHO_CMD_THREAD) + entry_point = ((struct grub_macho_thread32 *) hdr)->entry_point; + return 0; + } + grub_macho32_cmds_iterate (macho, hook, 0); + return entry_point; +} + + +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, (char *) &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, (char *) 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); + /* FIXME: implement 64-bit.*/ + /* 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_gzfile_open (name, 1); + if (! file) + return 0; + + macho = grub_macho_file (file); + if (! macho) + grub_file_close (file); + + return macho; +} diff --git a/loader/xnu.c b/loader/xnu.c new file mode 100644 index 0000000..12c45db --- /dev/null +++ b/loader/xnu.c @@ -0,0 +1,1377 @@ +/* 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct grub_xnu_devtree_key *grub_xnu_devtree_root = 0; +static int driverspackagenum = 0; +static int driversnum = 0; +char *grub_xnu_heap_start = 0; +grub_addr_t grub_xnu_heap_will_be_at = 0; +grub_size_t grub_xnu_heap_size = 0; + +/* Allocate heap by 32MB-blocks. */ +#define GRUB_XNU_HEAP_ALLOC_BLOCK 0x2000000 + +static grub_err_t +grub_xnu_register_memory (char *prefix, int *suffix, + void *addr, grub_size_t size); +void * +grub_xnu_heap_malloc (int size) +{ + void *val; + +#if 0 + /* This way booting is faster but less reliable. + Once we have advanced mm second way will be as fast as this one. */ + val = grub_xnu_heap_start = (char *) 0x100000; +#else + int oldblknum, newblknum; + + /* The page after the heap is used for stack. Ensure it's usable. */ + if (grub_xnu_heap_size) + oldblknum = (grub_xnu_heap_size + GRUB_XNU_PAGESIZE + + GRUB_XNU_HEAP_ALLOC_BLOCK - 1) / GRUB_XNU_HEAP_ALLOC_BLOCK; + else + oldblknum = 0; + newblknum = (grub_xnu_heap_size + size + GRUB_XNU_PAGESIZE + + GRUB_XNU_HEAP_ALLOC_BLOCK - 1) / GRUB_XNU_HEAP_ALLOC_BLOCK; + if (oldblknum != newblknum) + /* FIXME: instruct realloc to allocate at 1MB if possible once + advanced mm is ready. */ + val = grub_realloc (grub_xnu_heap_start, + newblknum * GRUB_XNU_HEAP_ALLOC_BLOCK); + else + val = grub_xnu_heap_start; + if (! val) + { + grub_error (GRUB_ERR_OUT_OF_MEMORY, + "not enough space on xnu memory heap"); + return 0; + } + grub_xnu_heap_start = val; +#endif + + val = (char *) grub_xnu_heap_start + grub_xnu_heap_size; + grub_xnu_heap_size += size; + grub_dprintf ("xnu", "val=%p\n", val); + return (char *) val; +} + +/* 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) +{ + int align_overhead = align - grub_xnu_heap_size % align; + if (align_overhead == align) + return GRUB_ERR_NONE; + if (! grub_xnu_heap_malloc (align_overhead)) + return grub_errno; + 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 (void **start, grub_size_t *size) +{ + struct grub_xnu_devtree_key *chosen, *cur; + struct grub_xnu_devtree_key *memorymap; + struct grub_xnu_devtree_key *driverkey; + struct grub_xnu_extdesc *extdesc; + grub_err_t err; + + 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, "/"); + *start = grub_xnu_heap_malloc (*size); + + /* Put real data in the dummy. */ + extdesc->addr = PTR_TO_UINT32 (*start); + extdesc->size = (grub_uint32_t) *size; + + /* Relocate memory map. */ + for (cur = memorymap->first_child; cur; cur = cur->next) + { + void *payload; + extdesc = cur->data; + if (cur->datasize != sizeof (*extdesc)) + continue; + /* RAMDisk is announced by its relocated and not original address. */ + if (grub_strcmp (cur->name, "RAMDisk") == 0) + { + extdesc->addr = extdesc->addr + grub_xnu_heap_will_be_at; + continue; + } + payload = extdesc->addr + grub_xnu_heap_start; + extdesc->addr = PTR_TO_UINT32(payload); + + /* If it's an extension also relocate the header. */ + if (grub_memcmp (cur->name, "Driver-", sizeof ("Driver-") - 1) == 0) + { + struct grub_xnu_extheader *exthead + = (struct grub_xnu_extheader *) payload; + if (exthead->infoplistaddr) + exthead->infoplistaddr = PTR_TO_UINT32 (exthead->infoplistaddr + + grub_xnu_heap_start); + if (exthead->binaryaddr) + exthead->binaryaddr = PTR_TO_UINT32 (exthead->binaryaddr + + grub_xnu_heap_start); + } + } + + /* Write the tree to heap. */ + grub_xnu_writetree_toheap_real (*start, 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_malloc (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->first_child = 0; + 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_malloc (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->datasize = 0; + ret->data = 0; + ret->next = *parent; + *parent = ret; + return ret; +} + +static grub_err_t +grub_xnu_unload (void) +{ + grub_xnu_free_devtree (grub_xnu_devtree_root); + grub_xnu_devtree_root = 0; + + /* Free loaded image. */ + driversnum = 0; + driverspackagenum = 0; + grub_free (grub_xnu_heap_start); + grub_xnu_heap_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_addr_t startcode, endcode; + int i; + char *ptr, *loadaddr; + + 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 architecture"); + } + + err = grub_macho32_size (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); + + loadaddr = grub_xnu_heap_malloc (endcode - startcode); + grub_xnu_heap_will_be_at = startcode; + + if (! loadaddr) + { + grub_macho_close (macho); + grub_xnu_unload (); + return grub_error (GRUB_ERR_OUT_OF_MEMORY, + "not enough memory to load kernel"); + } + + /* Load kernel. */ + err = grub_macho32_load (macho, loadaddr - startcode, GRUB_MACHO_NOBSS); + if (err) + { + grub_macho_close (macho); + grub_xnu_unload (); + return err; + } + + grub_xnu_entry_point = grub_macho32_get_entry_point (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_xnu_entry_point -= startcode; + + 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_cpu_xnu_fill_devicetree (); + if (err) + return err; + + grub_loader_set (grub_xnu_boot, grub_xnu_unload, 0); + + grub_xnu_lock (); + 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, + void *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_malloc (grub_strlen (prefix) + 10); + if (!driverkey->name) + return grub_error (GRUB_ERR_OUT_OF_MEMORY, "can't register memory"); + grub_sprintf (driverkey->name, "%s%d", prefix, (*suffix)++); + } + 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 = ((grub_uint8_t *) addr + - (grub_uint8_t *) grub_xnu_heap_start); + extdesc->size = (grub_uint32_t) size; + return GRUB_ERR_NONE; +} + +/* 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); + char *buf; + grub_size_t infoplistsize = 0, machosize = 0; + + 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"); + } + machosize = grub_macho32_filesize (macho); + neededspace += machosize; + } + else + macho = 0; + + if (infoplistname) + infoplist = grub_gzfile_open (infoplistname, 1); + 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; + buf = grub_xnu_heap_malloc (neededspace); + + exthead = (struct grub_xnu_extheader *) buf; + grub_memset (exthead, 0, sizeof (*exthead)); + buf += sizeof (*exthead); + + /* Load the binary. */ + if (macho) + { + exthead->binaryaddr = (buf - grub_xnu_heap_start); + exthead->binarysize = machosize; + if ((err = grub_macho32_readfile (macho, buf))) + { + 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 - grub_xnu_heap_start); + 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; + } + grub_errno = GRUB_ERR_NONE; + + /* Announce to kernel */ + return grub_xnu_register_memory ("Driver-", &driversnum, exthead, + 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_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_gzfile_open (args[0], 1); + 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, (char *) &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, (char *) 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_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); + } + } + 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; + } + + loadto = grub_xnu_heap_malloc (readlen); + if (! loadto) + { + grub_file_close (file); + return grub_errno; + } + + /* 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, 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_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_gzfile_open (args[0], 1); + 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); + + loadto = grub_xnu_heap_malloc (size); + if (! loadto) + return grub_errno; + 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, size); +} + +/* Parse a devtree file. It uses the following format: + valuename:valuedata; + keyname{ + contents + } + keyname, valuename and valuedata are in hex. + */ +static char * +grub_xnu_parse_devtree (struct grub_xnu_devtree_key **parent, + char *start, char *end) +{ + char *ptr, *ptr2; + char *name, *data; + int namelen, datalen, i; + for (ptr = start; ptr && ptr < end; ) + { + if (grub_isspace (*ptr)) + { + ptr++; + continue; + } + if (*ptr == '}') + return ptr + 1; + namelen = 0; + + /* Parse the name. */ + for (ptr2 = ptr; ptr2 < end && (grub_isspace (*ptr2) + || (*ptr2 >= '0' && *ptr2 <= '9') + || (*ptr2 >= 'a' && *ptr2 <= 'f') + || (*ptr2 >= 'A' && *ptr2 <= 'F')); + ptr2++) + if (! grub_isspace (*ptr2)) + namelen++; + if (ptr2 == end) + return 0; + namelen /= 2; + name = grub_malloc (namelen + 1); + if (!name) + return 0; + for (i = 0; i < 2 * namelen; i++) + { + int hex = 0; + while (grub_isspace (*ptr)) + ptr++; + if (*ptr >= '0' && *ptr <= '9') + hex = *ptr - '0'; + if (*ptr >= 'a' && *ptr <= 'f') + hex = *ptr - 'a' + 10; + if (*ptr >= 'A' && *ptr <= 'F') + hex = *ptr - 'A' + 10; + + if (i % 2 == 0) + name[i / 2] = hex << 4; + else + name[i / 2] |= hex; + ptr++; + } + name [namelen] = 0; + while (grub_isspace (*ptr)) + ptr++; + + /* If it describes a key recursively invoke the function. */ + if (*ptr == '{') + { + struct grub_xnu_devtree_key *newkey + = grub_xnu_create_key (parent, name); + grub_free (name); + if (! newkey) + return 0; + ptr = grub_xnu_parse_devtree (&(newkey->first_child), ptr + 1, end); + continue; + } + + /* Parse the data. */ + if (*ptr != ':') + return 0; + ptr++; + datalen = 0; + for (ptr2 = ptr; ptr2 < end && (grub_isspace (*ptr2) + || (*ptr2 >= '0' && *ptr2 <= '9') + || (*ptr2 >= 'a' && *ptr2 <= 'f') + || (*ptr2 >= 'A' && *ptr2 <= 'F')); + ptr2++) + if (! grub_isspace (*ptr2)) + datalen++; + if (ptr2 == end) + return 0; + datalen /= 2; + data = grub_malloc (datalen); + if (! data) + return 0; + for (i = 0; i < 2 * datalen; i++) + { + int hex = 0; + while (grub_isspace (*ptr)) + ptr++; + if (*ptr >= '0' && *ptr <= '9') + hex = *ptr - '0'; + if (*ptr >= 'a' && *ptr <= 'f') + hex = *ptr - 'a' + 10; + if (*ptr >= 'A' && *ptr <= 'F') + hex = *ptr - 'A' + 10; + + if (i % 2 == 0) + data[i / 2] = hex << 4; + else + data[i / 2] |= hex; + ptr++; + } + while (ptr < end && grub_isspace (*ptr)) + ptr++; + { + struct grub_xnu_devtree_key *newkey + = grub_xnu_create_value (parent, name); + grub_free (name); + if (! newkey) + return 0; + newkey->datasize = datalen; + newkey->data = data; + } + if (*ptr != ';') + return 0; + ptr++; + } + if (ptr >= end && *parent != grub_xnu_devtree_root) + return 0; + return ptr; +} + +/* 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_gzfile_open (plistname, 1); + 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 directoris 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_gzfile_open (binname, 1); + 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; +} + +/* Load devtree file. */ +static grub_err_t +grub_cmd_xnu_devtree (grub_command_t cmd __attribute__ ((unused)), + int argc, char *args[]) +{ + grub_file_t file; + char *data, *endret; + grub_size_t datalen; + + if (argc != 1) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "Filename required"); + + if (! grub_xnu_heap_size) + return grub_error (GRUB_ERR_BAD_OS, "no xnu kernel loaded"); + + /* Load the file. */ + file = grub_gzfile_open (args[0], 1); + if (! file) + return grub_error (GRUB_ERR_FILE_NOT_FOUND, "Couldn't load device tree"); + datalen = grub_file_size (file); + data = grub_malloc (datalen + 1); + if (! data) + { + grub_file_close (file); + return grub_error (GRUB_ERR_OUT_OF_MEMORY, + "Could load device tree into memory"); + } + if (grub_file_read (file, data, datalen) != (grub_ssize_t) datalen) + { + grub_file_close (file); + grub_free (data); + grub_error_push (); + return grub_error (GRUB_ERR_BAD_OS, "Couldn't read file %s", args[0]); + } + grub_file_close (file); + data[datalen] = 0; + + /* Parse the file. */ + endret = grub_xnu_parse_devtree (&grub_xnu_devtree_root, + data, data + datalen); + grub_free (data); + + if (! endret) + return grub_error (GRUB_ERR_BAD_OS, "Couldn't parse devtree"); + + 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 (argc == 2) + { + /* User explicitely specified plist and binary. */ + if (grub_strcmp (args[1], "-") != 0) + { + binfile = grub_gzfile_open (args[1], 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 (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; + } +} + +#ifndef GRUB_UTIL +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 () +{ +#ifndef GRUB_UTIL + if (!locked) + grub_dl_ref (my_mod); +#endif + locked = 1; +} + +void +grub_xnu_unlock () +{ +#ifndef GRUB_UTIL + if (locked) + grub_dl_unref (my_mod); +#endif + locked = 0; +} + +static grub_command_t cmd_kernel, cmd_mkext, cmd_kext, cmd_kextdir, + cmd_ramdisk, cmd_devtree, cmd_resume; + +GRUB_MOD_INIT(xnu) +{ + (void) mod; /* To stop warning. */ + cmd_kernel = grub_register_command ("xnu_kernel", grub_cmd_xnu_kernel, 0, + "load a xnu kernel"); + cmd_mkext = grub_register_command ("xnu_mkext", grub_cmd_xnu_mkext, 0, + "Load XNU extension package."); + cmd_kext = grub_register_command ("xnu_kext", grub_cmd_xnu_kext, 0, + "Load XNU extension."); + cmd_kextdir = grub_register_command ("xnu_kextdir", grub_cmd_xnu_kextdir, + "xnu_kextdir DIRECTORY [OSBundleRequired]", + "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_devtree = grub_register_command ("xnu_devtree", grub_cmd_xnu_devtree, 0, + "Load XNU devtree"); +#ifndef GRUB_UTIL + cmd_resume = grub_register_command ("xnu_resume", grub_cmd_xnu_resume, + 0, "Load XNU hibernate image."); +#endif + my_mod=mod; +} + +GRUB_MOD_FINI(xnu) +{ +#ifndef GRUB_UTIL + 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_devtree); + grub_unregister_command (cmd_ramdisk); + grub_unregister_command (cmd_kernel); +} diff --git a/loader/xnu_resume.c b/loader/xnu_resume.c new file mode 100644 index 0000000..24dd77b --- /dev/null +++ b/loader/xnu_resume.c @@ -0,0 +1,134 @@ +/* + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 *buf; + +#if GRUB_CPU_SIZEOF_VOID_P == 8 + grub_uint64_t codedest; + grub_uint64_t codesize; +#else + grub_uint32_t codedest; + grub_uint32_t codesize; +#endif + + file = grub_file_open (imagename); + if (! file) + return 0; + + /* Read the header. */ + if (grub_file_read (file, (char *) &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_hibernate_image = buf = grub_malloc (hibhead.image_size); + if (! buf) + { + grub_file_close (file); + return grub_error (GRUB_ERR_OUT_OF_MEMORY, + "not enough memory to load image"); + } + + /* Read image. */ + if (grub_file_seek (file, 0) == (grub_off_t)-1 + || grub_file_read (file, buf, 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); + + /* Move starting pages to appropriate location. */ + memcpy ((void *) codedest, ((grub_uint8_t *) buf) + total_header_size, + codesize); + + /* Setup variables needed by asm helper. */ + grub_xnu_stack = (codedest + hibhead.stack); + grub_xnu_entry_point = (codedest + hibhead.entry_point); + grub_xnu_arg1 = (long) buf; + + /* We're ready now. */ + grub_loader_set (grub_xnu_launch, grub_xnu_resume_unload, 0); + /* Prevent module from unloading. */ + grub_xnu_lock (); + return GRUB_ERR_NONE; +}