| /* |
| * GRUB -- GRand Unified Bootloader |
| * Copyright (C) 2009 Free Software Foundation, Inc. |
| * |
| * GRUB is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * GRUB is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with GRUB. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include <grub/env.h> |
| #include <grub/file.h> |
| #include <grub/disk.h> |
| #include <grub/xnu.h> |
| #include <grub/cpu/xnu.h> |
| #include <grub/mm.h> |
| #include <grub/loader.h> |
| #include <grub/autoefi.h> |
| #include <grub/i386/tsc.h> |
| #include <grub/i386/cpuid.h> |
| #include <grub/efi/api.h> |
| #include <grub/i386/pit.h> |
| #include <grub/misc.h> |
| #include <grub/charset.h> |
| #include <grub/term.h> |
| #include <grub/command.h> |
| #include <grub/i18n.h> |
| #include <grub/bitmap_scale.h> |
| #include <grub/cpu/io.h> |
| #include <grub/random.h> |
| |
| #define min(a,b) (((a) < (b)) ? (a) : (b)) |
| #define max(a,b) (((a) > (b)) ? (a) : (b)) |
| |
| #define DEFAULT_VIDEO_MODE "auto" |
| |
| char grub_xnu_cmdline[1024]; |
| grub_uint32_t grub_xnu_entry_point, grub_xnu_arg1, grub_xnu_stack; |
| |
| /* Aliases set for some tables. */ |
| struct tbl_alias |
| { |
| grub_efi_guid_t guid; |
| const char *name; |
| }; |
| |
| static struct tbl_alias table_aliases[] = |
| { |
| {GRUB_EFI_ACPI_20_TABLE_GUID, "ACPI_20"}, |
| {GRUB_EFI_ACPI_TABLE_GUID, "ACPI"}, |
| }; |
| |
| struct grub_xnu_devprop_device_descriptor |
| { |
| struct grub_xnu_devprop_device_descriptor *next; |
| struct grub_xnu_devprop_device_descriptor **prev; |
| struct property_descriptor *properties; |
| struct grub_efi_device_path *path; |
| int pathlen; |
| }; |
| |
| static int |
| utf16_strlen (grub_uint16_t *in) |
| { |
| int i; |
| for (i = 0; in[i]; i++); |
| return i; |
| } |
| |
| /* Read frequency from a string in MHz and return it in Hz. */ |
| static grub_uint64_t |
| readfrequency (const char *str) |
| { |
| grub_uint64_t num = 0; |
| int mul = 1000000; |
| int found = 0; |
| |
| while (*str) |
| { |
| unsigned long digit; |
| |
| digit = grub_tolower (*str) - '0'; |
| if (digit > 9) |
| break; |
| |
| found = 1; |
| |
| num = num * 10 + digit; |
| str++; |
| } |
| num *= 1000000; |
| if (*str == '.') |
| { |
| str++; |
| while (*str) |
| { |
| unsigned long digit; |
| |
| digit = grub_tolower (*str) - '0'; |
| if (digit > 9) |
| break; |
| |
| found = 1; |
| |
| mul /= 10; |
| num = num + mul * digit; |
| str++; |
| } |
| } |
| if (! found) |
| return 0; |
| |
| return num; |
| } |
| |
| /* Thanks to Kabyl for precious information about Intel architecture. */ |
| static grub_uint64_t |
| guessfsb (void) |
| { |
| const grub_uint64_t sane_value = 100000000; |
| grub_uint32_t manufacturer[3], max_cpuid, capabilities, msrlow; |
| grub_uint32_t a, b, d, divisor; |
| |
| if (! grub_cpu_is_cpuid_supported ()) |
| return sane_value; |
| |
| grub_cpuid (0, max_cpuid, manufacturer[0], manufacturer[2], manufacturer[1]); |
| |
| /* 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; |
| |
| grub_cpuid (1, a, b, capabilities, d); |
| |
| if (! (capabilities & (1 << 7))) |
| return sane_value; |
| |
| /* Read the multiplier. */ |
| asm volatile ("movl $0x198, %%ecx\n" |
| "rdmsr" |
| : "=d" (msrlow) |
| : |
| : "%ecx", "%eax"); |
| |
| grub_uint64_t v; |
| grub_uint32_t r; |
| |
| /* (2000ULL << 32) / grub_tsc_rate */ |
| /* Assumption: TSC frequency is over 2 MHz. */ |
| v = 0xffffffff / grub_tsc_rate; |
| v *= 2000; |
| /* v is at most 2000 off from (2000ULL << 32) / grub_tsc_rate. |
| Since grub_tsc_rate < 2^32/2^11=2^21, so no overflow. |
| */ |
| r = (2000ULL << 32) - v * grub_tsc_rate; |
| v += r / grub_tsc_rate; |
| |
| divisor = ((msrlow >> 7) & 0x3e) | ((msrlow >> 14) & 1); |
| if (divisor == 0) |
| return sane_value; |
| return grub_divmod64 (v, divisor, 0); |
| } |
| |
| struct property_descriptor |
| { |
| struct property_descriptor *next; |
| struct property_descriptor **prev; |
| grub_uint8_t *name; |
| grub_uint16_t *name16; |
| int name16len; |
| int length; |
| void *data; |
| }; |
| |
| static struct grub_xnu_devprop_device_descriptor *devices = 0; |
| |
| grub_err_t |
| grub_xnu_devprop_remove_property (struct grub_xnu_devprop_device_descriptor *dev, |
| char *name) |
| { |
| struct property_descriptor *prop; |
| prop = grub_named_list_find (GRUB_AS_NAMED_LIST (dev->properties), name); |
| if (!prop) |
| return GRUB_ERR_NONE; |
| |
| grub_free (prop->name); |
| grub_free (prop->name16); |
| grub_free (prop->data); |
| |
| grub_list_remove (GRUB_AS_LIST (prop)); |
| |
| return GRUB_ERR_NONE; |
| } |
| |
| grub_err_t |
| grub_xnu_devprop_remove_device (struct grub_xnu_devprop_device_descriptor *dev) |
| { |
| void *t; |
| struct property_descriptor *prop; |
| |
| grub_list_remove (GRUB_AS_LIST (dev)); |
| |
| for (prop = dev->properties; prop; ) |
| { |
| grub_free (prop->name); |
| grub_free (prop->name16); |
| grub_free (prop->data); |
| t = prop; |
| prop = prop->next; |
| grub_free (t); |
| } |
| |
| grub_free (dev->path); |
| grub_free (dev); |
| |
| return GRUB_ERR_NONE; |
| } |
| |
| struct grub_xnu_devprop_device_descriptor * |
| grub_xnu_devprop_add_device (struct grub_efi_device_path *path, int length) |
| { |
| struct grub_xnu_devprop_device_descriptor *ret; |
| |
| ret = grub_zalloc (sizeof (*ret)); |
| if (!ret) |
| return 0; |
| |
| ret->path = grub_malloc (length); |
| if (!ret->path) |
| { |
| grub_free (ret); |
| return 0; |
| } |
| ret->pathlen = length; |
| grub_memcpy (ret->path, path, length); |
| |
| grub_list_push (GRUB_AS_LIST_P (&devices), GRUB_AS_LIST (ret)); |
| |
| return ret; |
| } |
| |
| static grub_err_t |
| grub_xnu_devprop_add_property (struct grub_xnu_devprop_device_descriptor *dev, |
| grub_uint8_t *utf8, grub_uint16_t *utf16, |
| int utf16len, void *data, int datalen) |
| { |
| struct property_descriptor *prop; |
| |
| prop = grub_malloc (sizeof (*prop)); |
| if (!prop) |
| return grub_errno; |
| |
| prop->name = utf8; |
| prop->name16 = utf16; |
| prop->name16len = utf16len; |
| |
| prop->length = datalen; |
| prop->data = grub_malloc (prop->length); |
| if (!prop->data) |
| { |
| grub_free (prop->name); |
| grub_free (prop->name16); |
| grub_free (prop); |
| return grub_errno; |
| } |
| grub_memcpy (prop->data, data, prop->length); |
| grub_list_push (GRUB_AS_LIST_P (&dev->properties), |
| GRUB_AS_LIST (prop)); |
| return GRUB_ERR_NONE; |
| } |
| |
| grub_err_t |
| grub_xnu_devprop_add_property_utf8 (struct grub_xnu_devprop_device_descriptor *dev, |
| char *name, void *data, int datalen) |
| { |
| grub_uint8_t *utf8; |
| grub_uint16_t *utf16; |
| int len, utf16len; |
| grub_err_t err; |
| |
| utf8 = (grub_uint8_t *) grub_strdup (name); |
| if (!utf8) |
| return grub_errno; |
| |
| len = grub_strlen (name); |
| utf16 = grub_malloc (sizeof (grub_uint16_t) * len); |
| if (!utf16) |
| { |
| grub_free (utf8); |
| return grub_errno; |
| } |
| |
| utf16len = grub_utf8_to_utf16 (utf16, len, utf8, len, NULL); |
| if (utf16len < 0) |
| { |
| grub_free (utf8); |
| grub_free (utf16); |
| return grub_errno; |
| } |
| |
| err = grub_xnu_devprop_add_property (dev, utf8, utf16, |
| utf16len, data, datalen); |
| if (err) |
| { |
| grub_free (utf8); |
| grub_free (utf16); |
| return err; |
| } |
| |
| return GRUB_ERR_NONE; |
| } |
| |
| grub_err_t |
| grub_xnu_devprop_add_property_utf16 (struct grub_xnu_devprop_device_descriptor *dev, |
| grub_uint16_t *name, int namelen, |
| void *data, int datalen) |
| { |
| grub_uint8_t *utf8; |
| grub_uint16_t *utf16; |
| grub_err_t err; |
| |
| utf16 = grub_malloc (sizeof (grub_uint16_t) * namelen); |
| if (!utf16) |
| return grub_errno; |
| grub_memcpy (utf16, name, sizeof (grub_uint16_t) * namelen); |
| |
| utf8 = grub_malloc (namelen * 4 + 1); |
| if (!utf8) |
| { |
| grub_free (utf16); |
| return grub_errno; |
| } |
| |
| *grub_utf16_to_utf8 ((grub_uint8_t *) utf8, name, namelen) = '\0'; |
| |
| err = grub_xnu_devprop_add_property (dev, utf8, utf16, |
| namelen, data, datalen); |
| if (err) |
| { |
| grub_free (utf8); |
| grub_free (utf16); |
| return err; |
| } |
| |
| return GRUB_ERR_NONE; |
| } |
| |
| void |
| grub_cpu_xnu_unload (void) |
| { |
| struct grub_xnu_devprop_device_descriptor *dev1, *dev2; |
| |
| for (dev1 = devices; dev1; ) |
| { |
| dev2 = dev1->next; |
| grub_xnu_devprop_remove_device (dev1); |
| dev1 = dev2; |
| } |
| } |
| |
| static grub_err_t |
| grub_cpu_xnu_fill_devprop (void) |
| { |
| struct grub_xnu_devtree_key *efikey; |
| int total_length = sizeof (struct grub_xnu_devprop_header); |
| struct grub_xnu_devtree_key *devprop; |
| struct grub_xnu_devprop_device_descriptor *device; |
| void *ptr; |
| struct grub_xnu_devprop_header *head; |
| void *t; |
| int numdevs = 0; |
| |
| /* The key "efi". */ |
| efikey = grub_xnu_create_key (&grub_xnu_devtree_root, "efi"); |
| if (! efikey) |
| return grub_errno; |
| |
| for (device = devices; device; device = device->next) |
| { |
| struct property_descriptor *propdesc; |
| total_length += sizeof (struct grub_xnu_devprop_device_header); |
| total_length += device->pathlen; |
| |
| for (propdesc = device->properties; propdesc; propdesc = propdesc->next) |
| { |
| total_length += sizeof (grub_uint32_t); |
| total_length += sizeof (grub_uint16_t) |
| * (propdesc->name16len + 1); |
| total_length += sizeof (grub_uint32_t); |
| total_length += propdesc->length; |
| } |
| numdevs++; |
| } |
| |
| devprop = grub_xnu_create_value (&(efikey->first_child), "device-properties"); |
| if (!devprop) |
| return grub_errno; |
| |
| devprop->data = grub_malloc (total_length); |
| devprop->datasize = total_length; |
| |
| ptr = devprop->data; |
| head = ptr; |
| ptr = head + 1; |
| head->length = total_length; |
| head->alwaysone = 1; |
| head->num_devices = numdevs; |
| for (device = devices; device; ) |
| { |
| struct grub_xnu_devprop_device_header *devhead; |
| struct property_descriptor *propdesc; |
| devhead = ptr; |
| devhead->num_values = 0; |
| ptr = devhead + 1; |
| |
| grub_memcpy (ptr, device->path, device->pathlen); |
| ptr = (char *) ptr + device->pathlen; |
| |
| for (propdesc = device->properties; propdesc; ) |
| { |
| grub_uint32_t *len; |
| grub_uint16_t *name; |
| void *data; |
| |
| len = ptr; |
| *len = 2 * propdesc->name16len + sizeof (grub_uint16_t) |
| + sizeof (grub_uint32_t); |
| ptr = len + 1; |
| |
| name = ptr; |
| grub_memcpy (name, propdesc->name16, 2 * propdesc->name16len); |
| name += propdesc->name16len; |
| |
| /* NUL terminator. */ |
| *name = 0; |
| ptr = name + 1; |
| |
| len = ptr; |
| *len = propdesc->length + sizeof (grub_uint32_t); |
| data = len + 1; |
| ptr = data; |
| grub_memcpy (ptr, propdesc->data, propdesc->length); |
| ptr = (char *) ptr + propdesc->length; |
| |
| grub_free (propdesc->name); |
| grub_free (propdesc->name16); |
| grub_free (propdesc->data); |
| t = propdesc; |
| propdesc = propdesc->next; |
| grub_free (t); |
| devhead->num_values++; |
| } |
| |
| devhead->length = (char *) ptr - (char *) devhead; |
| t = device; |
| device = device->next; |
| grub_free (t); |
| } |
| |
| devices = 0; |
| |
| return GRUB_ERR_NONE; |
| } |
| |
| static grub_err_t |
| grub_cmd_devprop_load (grub_command_t cmd __attribute__ ((unused)), |
| int argc, char *args[]) |
| { |
| grub_file_t file; |
| void *buf, *bufstart, *bufend; |
| struct grub_xnu_devprop_header *head; |
| grub_size_t size; |
| unsigned i, j; |
| |
| if (argc != 1) |
| return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); |
| |
| file = grub_file_open (args[0], GRUB_FILE_XNU_DEVPROP); |
| if (! file) |
| return grub_errno; |
| size = grub_file_size (file); |
| buf = grub_malloc (size); |
| if (!buf) |
| { |
| grub_file_close (file); |
| return grub_errno; |
| } |
| if (grub_file_read (file, buf, size) != (grub_ssize_t) size) |
| { |
| grub_file_close (file); |
| return grub_errno; |
| } |
| grub_file_close (file); |
| |
| bufstart = buf; |
| bufend = (char *) buf + size; |
| head = buf; |
| buf = head + 1; |
| for (i = 0; i < grub_le_to_cpu32 (head->num_devices) && buf < bufend; i++) |
| { |
| struct grub_efi_device_path *dp, *dpstart; |
| struct grub_xnu_devprop_device_descriptor *dev; |
| struct grub_xnu_devprop_device_header *devhead; |
| |
| devhead = buf; |
| buf = devhead + 1; |
| dpstart = buf; |
| |
| do |
| { |
| dp = buf; |
| buf = (char *) buf + GRUB_EFI_DEVICE_PATH_LENGTH (dp); |
| } |
| while (!GRUB_EFI_END_ENTIRE_DEVICE_PATH (dp) && buf < bufend); |
| |
| dev = grub_xnu_devprop_add_device (dpstart, (char *) buf |
| - (char *) dpstart); |
| |
| for (j = 0; j < grub_le_to_cpu32 (devhead->num_values) && buf < bufend; |
| j++) |
| { |
| grub_uint32_t *namelen; |
| grub_uint32_t *datalen; |
| grub_uint16_t *utf16; |
| void *data; |
| grub_err_t err; |
| |
| namelen = buf; |
| buf = namelen + 1; |
| if (buf >= bufend) |
| break; |
| |
| utf16 = buf; |
| buf = (char *) buf + *namelen - sizeof (grub_uint32_t); |
| if (buf >= bufend) |
| break; |
| |
| datalen = buf; |
| buf = datalen + 1; |
| if (buf >= bufend) |
| break; |
| |
| data = buf; |
| buf = (char *) buf + *datalen - sizeof (grub_uint32_t); |
| if (buf >= bufend) |
| break; |
| err = grub_xnu_devprop_add_property_utf16 |
| (dev, utf16, (*namelen - sizeof (grub_uint32_t) |
| - sizeof (grub_uint16_t)) / sizeof (grub_uint16_t), |
| data, *datalen - sizeof (grub_uint32_t)); |
| if (err) |
| { |
| grub_free (bufstart); |
| return err; |
| } |
| } |
| } |
| |
| grub_free (bufstart); |
| return GRUB_ERR_NONE; |
| } |
| |
| /* Fill device tree. */ |
| /* FIXME: some entries may be platform-agnostic. Move them to loader/xnu.c. */ |
| static grub_err_t |
| grub_cpu_xnu_fill_devicetree (grub_uint64_t *fsbfreq_out) |
| { |
| struct grub_xnu_devtree_key *efikey; |
| struct grub_xnu_devtree_key *chosenkey; |
| 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; |
| grub_err_t err; |
| |
| chosenkey = grub_xnu_create_key (&grub_xnu_devtree_root, "chosen"); |
| if (! chosenkey) |
| return grub_errno; |
| |
| /* Random seed. */ |
| curval = grub_xnu_create_value (&(chosenkey->first_child), "random-seed"); |
| if (! curval) |
| return grub_errno; |
| curval->datasize = 64; |
| curval->data = grub_malloc (curval->datasize); |
| if (! curval->data) |
| return grub_errno; |
| /* Our random is not peer-reviewed but xnu uses this seed only for |
| ASLR in kernel. */ |
| err = grub_crypto_get_random (curval->data, curval->datasize); |
| if (err) |
| return err; |
| |
| /* The value "model". */ |
| /* FIXME: may this value be sometimes different? */ |
| curval = grub_xnu_create_value (&grub_xnu_devtree_root, "model"); |
| if (! curval) |
| return grub_errno; |
| curval->datasize = sizeof ("ACPI"); |
| curval->data = grub_strdup ("ACPI"); |
| curval = grub_xnu_create_value (&grub_xnu_devtree_root, "compatible"); |
| if (! curval) |
| return grub_errno; |
| curval->datasize = sizeof ("ACPI"); |
| curval->data = grub_strdup ("ACPI"); |
| |
| /* The key "efi". */ |
| efikey = grub_xnu_create_key (&grub_xnu_devtree_root, "efi"); |
| if (! efikey) |
| return grub_errno; |
| |
| /* Information about firmware. */ |
| curval = grub_xnu_create_value (&(efikey->first_child), "firmware-revision"); |
| if (! curval) |
| return grub_errno; |
| curval->datasize = (SYSTEM_TABLE_SIZEOF (firmware_revision)); |
| curval->data = grub_malloc (curval->datasize); |
| if (! curval->data) |
| return grub_errno; |
| grub_memcpy (curval->data, (SYSTEM_TABLE_VAR(firmware_revision)), |
| curval->datasize); |
| |
| curval = grub_xnu_create_value (&(efikey->first_child), "firmware-vendor"); |
| if (! curval) |
| return grub_errno; |
| curval->datasize = |
| 2 * (utf16_strlen (SYSTEM_TABLE_PTR (firmware_vendor)) + 1); |
| curval->data = grub_malloc (curval->datasize); |
| if (! curval->data) |
| return grub_errno; |
| grub_memcpy (curval->data, SYSTEM_TABLE_PTR (firmware_vendor), |
| curval->datasize); |
| |
| curval = grub_xnu_create_value (&(efikey->first_child), "firmware-abi"); |
| if (! curval) |
| return grub_errno; |
| curval->datasize = sizeof ("EFI32"); |
| curval->data = grub_malloc (curval->datasize); |
| if (! curval->data) |
| return grub_errno; |
| if (SIZEOF_OF_UINTN == 4) |
| grub_memcpy (curval->data, "EFI32", curval->datasize); |
| else |
| grub_memcpy (curval->data, "EFI64", curval->datasize); |
| |
| /* The key "platform". */ |
| platformkey = grub_xnu_create_key (&(efikey->first_child), |
| "platform"); |
| if (! platformkey) |
| return grub_errno; |
| |
| /* Pass FSB frequency to the kernel. */ |
| curval = grub_xnu_create_value (&(platformkey->first_child), "FSBFrequency"); |
| if (! curval) |
| return grub_errno; |
| curval->datasize = sizeof (grub_uint64_t); |
| curval->data = grub_malloc (curval->datasize); |
| if (!curval->data) |
| return grub_errno; |
| |
| /* First see if user supplies the value. */ |
| const char *fsbvar = grub_env_get ("fsb"); |
| grub_uint64_t fsbfreq = 0; |
| if (fsbvar) |
| fsbfreq = readfrequency (fsbvar); |
| /* Try autodetect. */ |
| if (! fsbfreq) |
| fsbfreq = guessfsb (); |
| *((grub_uint64_t *) curval->data) = fsbfreq; |
| *fsbfreq_out = fsbfreq; |
| 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_packed_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 = (void *) (grub_addr_t) ((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 = (void *) (grub_addr_t) ((grub_efiemu_configuration_table64_t *) |
| SYSTEM_TABLE_PTR (configuration_table))[i] |
| .vendor_table; |
| guid = |
| ((grub_efiemu_configuration_table64_t *) |
| SYSTEM_TABLE_PTR (configuration_table))[i].vendor_guid; |
| } |
| #endif |
| |
| /* The name of key for new table. */ |
| grub_snprintf (guidbuf, sizeof (guidbuf), "%08x-%04x-%04x-%02x%02x-", |
| guid.data1, guid.data2, guid.data3, guid.data4[0], |
| guid.data4[1]); |
| for (j = 2; j < 8; j++) |
| grub_snprintf (guidbuf + grub_strlen (guidbuf), |
| sizeof (guidbuf) - grub_strlen (guidbuf), |
| "%02x", guid.data4[j]); |
| /* For some reason GUID has to be in uppercase. */ |
| for (j = 0; guidbuf[j] ; j++) |
| if (guidbuf[j] >= 'a' && guidbuf[j] <= 'f') |
| guidbuf[j] += 'A' - 'a'; |
| curkey = grub_xnu_create_key (&(cfgtablekey->first_child), guidbuf); |
| if (! curkey) |
| return grub_errno; |
| |
| curval = grub_xnu_create_value (&(curkey->first_child), "guid"); |
| if (! curval) |
| return grub_errno; |
| curval->datasize = sizeof (guid); |
| curval->data = grub_malloc (curval->datasize); |
| if (! curval->data) |
| return grub_errno; |
| grub_memcpy (curval->data, &guid, curval->datasize); |
| |
| /* The value "table". */ |
| curval = grub_xnu_create_value (&(curkey->first_child), "table"); |
| if (! curval) |
| return grub_errno; |
| curval->datasize = SIZEOF_OF_UINTN; |
| curval->data = grub_malloc (curval->datasize); |
| if (! curval->data) |
| return grub_errno; |
| if (SIZEOF_OF_UINTN == 4) |
| *((grub_uint32_t *) curval->data) = (grub_addr_t) ptr; |
| else |
| *((grub_uint64_t *) curval->data) = (grub_addr_t) ptr; |
| |
| /* Create alias. */ |
| for (j = 0; j < ARRAY_SIZE(table_aliases); j++) |
| if (grub_memcmp (&table_aliases[j].guid, &guid, sizeof (guid)) == 0) |
| break; |
| if (j != ARRAY_SIZE(table_aliases)) |
| { |
| curval = grub_xnu_create_value (&(curkey->first_child), "alias"); |
| if (!curval) |
| return grub_errno; |
| curval->datasize = grub_strlen (table_aliases[j].name) + 1; |
| curval->data = grub_malloc (curval->datasize); |
| if (!curval->data) |
| return grub_errno; |
| grub_memcpy (curval->data, table_aliases[j].name, curval->datasize); |
| } |
| } |
| |
| /* Create and fill "runtime-services" key. */ |
| runtimesrvkey = grub_xnu_create_key (&(efikey->first_child), |
| "runtime-services"); |
| if (! runtimesrvkey) |
| return grub_errno; |
| curval = grub_xnu_create_value (&(runtimesrvkey->first_child), "table"); |
| if (! curval) |
| return grub_errno; |
| curval->datasize = SIZEOF_OF_UINTN; |
| curval->data = grub_malloc (curval->datasize); |
| if (! curval->data) |
| return grub_errno; |
| if (SIZEOF_OF_UINTN == 4) |
| *((grub_uint32_t *) curval->data) |
| = (grub_addr_t) SYSTEM_TABLE_PTR (runtime_services); |
| else |
| *((grub_uint64_t *) curval->data) |
| = (grub_addr_t) SYSTEM_TABLE_PTR (runtime_services); |
| |
| return GRUB_ERR_NONE; |
| } |
| |
| grub_err_t |
| grub_xnu_boot_resume (void) |
| { |
| struct grub_relocator32_state state; |
| |
| state.esp = grub_xnu_stack; |
| state.ebp = grub_xnu_stack; |
| state.eip = grub_xnu_entry_point; |
| state.eax = grub_xnu_arg1; |
| |
| return grub_relocator32_boot (grub_xnu_relocator, state, 0); |
| } |
| |
| /* Setup video for xnu. */ |
| static grub_err_t |
| grub_xnu_set_video (struct grub_xnu_boot_params_common *params) |
| { |
| struct grub_video_mode_info mode_info; |
| char *tmp; |
| const char *modevar; |
| void *framebuffer; |
| grub_err_t err; |
| struct grub_video_bitmap *bitmap = NULL; |
| |
| modevar = grub_env_get ("gfxpayload"); |
| /* Consider only graphical 32-bit deep modes. */ |
| if (! modevar || *modevar == 0) |
| err = grub_video_set_mode (DEFAULT_VIDEO_MODE, |
| GRUB_VIDEO_MODE_TYPE_PURE_TEXT |
| | GRUB_VIDEO_MODE_TYPE_DEPTH_MASK, |
| 32 << GRUB_VIDEO_MODE_TYPE_DEPTH_POS); |
| else |
| { |
| tmp = grub_xasprintf ("%s;" DEFAULT_VIDEO_MODE, modevar); |
| if (! tmp) |
| return grub_errno; |
| err = grub_video_set_mode (tmp, |
| GRUB_VIDEO_MODE_TYPE_PURE_TEXT |
| | GRUB_VIDEO_MODE_TYPE_DEPTH_MASK, |
| 32 << GRUB_VIDEO_MODE_TYPE_DEPTH_POS); |
| grub_free (tmp); |
| } |
| |
| if (err) |
| return err; |
| |
| err = grub_video_get_info (&mode_info); |
| if (err) |
| return err; |
| |
| if (grub_xnu_bitmap) |
| { |
| if (grub_xnu_bitmap_mode == GRUB_XNU_BITMAP_STRETCH) |
| err = grub_video_bitmap_create_scaled (&bitmap, |
| mode_info.width, |
| mode_info.height, |
| grub_xnu_bitmap, |
| GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST); |
| else |
| bitmap = grub_xnu_bitmap; |
| } |
| |
| if (bitmap) |
| { |
| if (grub_xnu_bitmap_mode == GRUB_XNU_BITMAP_STRETCH) |
| err = grub_video_bitmap_create_scaled (&bitmap, |
| mode_info.width, |
| mode_info.height, |
| grub_xnu_bitmap, |
| GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST); |
| else |
| bitmap = grub_xnu_bitmap; |
| } |
| |
| if (bitmap) |
| { |
| int x, y; |
| |
| x = mode_info.width - bitmap->mode_info.width; |
| x /= 2; |
| y = mode_info.height - bitmap->mode_info.height; |
| y /= 2; |
| err = grub_video_blit_bitmap (bitmap, |
| GRUB_VIDEO_BLIT_REPLACE, |
| x > 0 ? x : 0, |
| y > 0 ? y : 0, |
| x < 0 ? -x : 0, |
| y < 0 ? -y : 0, |
| min (bitmap->mode_info.width, |
| mode_info.width), |
| min (bitmap->mode_info.height, |
| mode_info.height)); |
| } |
| if (err) |
| { |
| grub_print_error (); |
| grub_errno = GRUB_ERR_NONE; |
| bitmap = 0; |
| } |
| |
| err = grub_video_get_info_and_fini (&mode_info, &framebuffer); |
| if (err) |
| return err; |
| |
| params->lfb_width = mode_info.width; |
| params->lfb_height = mode_info.height; |
| params->lfb_depth = mode_info.bpp; |
| params->lfb_line_len = mode_info.pitch; |
| |
| params->lfb_base = (grub_addr_t) framebuffer; |
| params->lfb_mode = bitmap ? GRUB_XNU_VIDEO_SPLASH |
| : GRUB_XNU_VIDEO_TEXT_IN_VIDEO; |
| |
| return GRUB_ERR_NONE; |
| } |
| |
| static int |
| total_ram_hook (grub_uint64_t addr __attribute__ ((unused)), grub_uint64_t size, |
| grub_memory_type_t type, |
| void *data) |
| { |
| grub_uint64_t *result = data; |
| |
| if (type != GRUB_MEMORY_AVAILABLE) |
| return 0; |
| *result += size; |
| return 0; |
| } |
| |
| static grub_uint64_t |
| get_total_ram (void) |
| { |
| grub_uint64_t result = 0; |
| |
| grub_mmap_iterate (total_ram_hook, &result); |
| return result; |
| } |
| |
| /* Boot xnu. */ |
| grub_err_t |
| grub_xnu_boot (void) |
| { |
| union grub_xnu_boot_params_any *bootparams; |
| struct grub_xnu_boot_params_common *bootparams_common; |
| void *bp_in; |
| grub_addr_t bootparams_target; |
| grub_err_t err; |
| grub_efi_uintn_t memory_map_size = 0; |
| void *memory_map; |
| grub_addr_t memory_map_target; |
| grub_efi_uintn_t map_key = 0; |
| grub_efi_uintn_t descriptor_size = 0; |
| grub_efi_uint32_t descriptor_version = 0; |
| grub_uint64_t firstruntimepage, lastruntimepage; |
| grub_uint64_t curruntimepage; |
| grub_addr_t devtree_target; |
| grub_size_t devtreelen; |
| int i; |
| struct grub_relocator32_state state; |
| grub_uint64_t fsbfreq = 100000000; |
| int v2 = (grub_xnu_darwin_version >= 11); |
| grub_uint32_t efi_system_table = 0; |
| |
| err = grub_autoefi_prepare (); |
| if (err) |
| return err; |
| |
| err = grub_cpu_xnu_fill_devprop (); |
| if (err) |
| return err; |
| |
| err = grub_cpu_xnu_fill_devicetree (&fsbfreq); |
| if (err) |
| return err; |
| |
| err = grub_xnu_fill_devicetree (); |
| if (err) |
| return err; |
| |
| /* Page-align to avoid following parts to be inadvertently freed. */ |
| err = grub_xnu_align_heap (GRUB_XNU_PAGESIZE); |
| if (err) |
| return err; |
| |
| /* Pass memory map to kernel. */ |
| memory_map_size = 0; |
| memory_map = 0; |
| map_key = 0; |
| descriptor_size = 0; |
| descriptor_version = 0; |
| |
| grub_dprintf ("xnu", "eip=%x, efi=%p\n", grub_xnu_entry_point, |
| grub_autoefi_system_table); |
| |
| const char *debug = grub_env_get ("debug"); |
| |
| if (debug && (grub_strword (debug, "all") || grub_strword (debug, "xnu"))) |
| { |
| grub_puts_ (N_("Press any key to launch xnu")); |
| grub_getkey (); |
| } |
| |
| /* Relocate the boot parameters to heap. */ |
| err = grub_xnu_heap_malloc (sizeof (*bootparams), |
| &bp_in, &bootparams_target); |
| if (err) |
| return err; |
| bootparams = bp_in; |
| |
| grub_memset (bootparams, 0, sizeof (*bootparams)); |
| if (v2) |
| { |
| bootparams_common = &bootparams->v2.common; |
| bootparams->v2.fsbfreq = fsbfreq; |
| bootparams->v2.ram_size = get_total_ram(); |
| } |
| else |
| bootparams_common = &bootparams->v1.common; |
| |
| /* Set video. */ |
| err = grub_xnu_set_video (bootparams_common); |
| if (err != GRUB_ERR_NONE) |
| { |
| grub_print_error (); |
| grub_errno = GRUB_ERR_NONE; |
| grub_puts_ (N_("Booting in blind mode")); |
| |
| bootparams_common->lfb_mode = 0; |
| bootparams_common->lfb_width = 0; |
| bootparams_common->lfb_height = 0; |
| bootparams_common->lfb_depth = 0; |
| bootparams_common->lfb_line_len = 0; |
| bootparams_common->lfb_base = 0; |
| } |
| |
| if (grub_autoefi_get_memory_map (&memory_map_size, memory_map, |
| &map_key, &descriptor_size, |
| &descriptor_version) < 0) |
| return grub_errno; |
| |
| /* We will do few allocations later. Reserve some space for possible |
| memory map growth. */ |
| memory_map_size += 20 * descriptor_size; |
| err = grub_xnu_heap_malloc (memory_map_size, |
| &memory_map, &memory_map_target); |
| if (err) |
| return err; |
| |
| err = grub_xnu_writetree_toheap (&devtree_target, &devtreelen); |
| if (err) |
| return err; |
| |
| grub_memcpy (bootparams_common->cmdline, grub_xnu_cmdline, |
| sizeof (bootparams_common->cmdline)); |
| |
| bootparams_common->devtree = devtree_target; |
| bootparams_common->devtreelen = devtreelen; |
| |
| err = grub_autoefi_finish_boot_services (&memory_map_size, memory_map, |
| &map_key, &descriptor_size, |
| &descriptor_version); |
| if (err) |
| return err; |
| |
| if (v2) |
| bootparams->v2.efi_system_table = (grub_addr_t) grub_autoefi_system_table; |
| else |
| bootparams->v1.efi_system_table = (grub_addr_t) grub_autoefi_system_table; |
| |
| firstruntimepage = (((grub_addr_t) grub_xnu_heap_target_start |
| + grub_xnu_heap_size + GRUB_XNU_PAGESIZE - 1) |
| / GRUB_XNU_PAGESIZE) + 20; |
| curruntimepage = firstruntimepage; |
| |
| for (i = 0; (unsigned) i < memory_map_size / descriptor_size; i++) |
| { |
| grub_efi_memory_descriptor_t *curdesc = (grub_efi_memory_descriptor_t *) |
| ((char *) memory_map + descriptor_size * i); |
| |
| curdesc->virtual_start = curdesc->physical_start; |
| |
| if (curdesc->type == GRUB_EFI_RUNTIME_SERVICES_DATA |
| || curdesc->type == GRUB_EFI_RUNTIME_SERVICES_CODE) |
| { |
| curdesc->virtual_start = curruntimepage << 12; |
| curruntimepage += curdesc->num_pages; |
| if (curdesc->physical_start |
| <= (grub_addr_t) grub_autoefi_system_table |
| && curdesc->physical_start + (curdesc->num_pages << 12) |
| > (grub_addr_t) grub_autoefi_system_table) |
| efi_system_table |
| = (grub_addr_t) grub_autoefi_system_table |
| - curdesc->physical_start + curdesc->virtual_start; |
| if (SIZEOF_OF_UINTN == 8 && grub_xnu_is_64bit) |
| curdesc->virtual_start |= 0xffffff8000000000ULL; |
| } |
| } |
| |
| lastruntimepage = curruntimepage; |
| |
| if (v2) |
| { |
| bootparams->v2.efi_uintnbits = SIZEOF_OF_UINTN * 8; |
| bootparams->v2.verminor = GRUB_XNU_BOOTARGSV2_VERMINOR; |
| bootparams->v2.vermajor = GRUB_XNU_BOOTARGSV2_VERMAJOR; |
| bootparams->v2.efi_system_table = efi_system_table; |
| } |
| else |
| { |
| bootparams->v1.efi_uintnbits = SIZEOF_OF_UINTN * 8; |
| bootparams->v1.verminor = GRUB_XNU_BOOTARGSV1_VERMINOR; |
| bootparams->v1.vermajor = GRUB_XNU_BOOTARGSV1_VERMAJOR; |
| bootparams->v1.efi_system_table = efi_system_table; |
| } |
| |
| bootparams_common->efi_runtime_first_page = firstruntimepage; |
| bootparams_common->efi_runtime_npages = lastruntimepage - firstruntimepage; |
| bootparams_common->efi_mem_desc_size = descriptor_size; |
| bootparams_common->efi_mem_desc_version = descriptor_version; |
| bootparams_common->efi_mmap = memory_map_target; |
| bootparams_common->efi_mmap_size = memory_map_size; |
| bootparams_common->heap_start = grub_xnu_heap_target_start; |
| bootparams_common->heap_size = curruntimepage * GRUB_XNU_PAGESIZE - grub_xnu_heap_target_start; |
| |
| /* Parameters for asm helper. */ |
| grub_xnu_stack = bootparams_common->heap_start |
| + bootparams_common->heap_size + GRUB_XNU_PAGESIZE; |
| grub_xnu_arg1 = bootparams_target; |
| |
| grub_autoefi_set_virtual_address_map (memory_map_size, descriptor_size, |
| descriptor_version, memory_map); |
| |
| state.eip = grub_xnu_entry_point; |
| state.eax = grub_xnu_arg1; |
| state.esp = grub_xnu_stack; |
| state.ebp = grub_xnu_stack; |
| |
| /* XNU uses only APIC. Disable PIC. */ |
| grub_outb (0xff, 0x21); |
| grub_outb (0xff, 0xa1); |
| |
| return grub_relocator32_boot (grub_xnu_relocator, state, 0); |
| } |
| |
| static grub_command_t cmd_devprop_load; |
| |
| void |
| grub_cpu_xnu_init (void) |
| { |
| cmd_devprop_load = grub_register_command ("xnu_devprop_load", |
| grub_cmd_devprop_load, |
| /* TRANSLATORS: `device-properties' |
| is a variable name, |
| not a program. */ |
| 0, N_("Load `device-properties' dump.")); |
| } |
| |
| void |
| grub_cpu_xnu_fini (void) |
| { |
| grub_unregister_command (cmd_devprop_load); |
| } |