diff options
Diffstat (limited to 'grub-core/loader/i386/xnu.c')
-rw-r--r-- | grub-core/loader/i386/xnu.c | 1166 |
1 files changed, 1166 insertions, 0 deletions
diff --git a/grub-core/loader/i386/xnu.c b/grub-core/loader/i386/xnu.c new file mode 100644 index 0000000..a700936 --- /dev/null +++ b/grub-core/loader/i386/xnu.c @@ -0,0 +1,1166 @@ +/* + * 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->data = grub_malloc (datalen); + if (!prop->data) + { + grub_free (prop); + return grub_errno; + } + grub_memcpy (prop->data, data, datalen); + + prop->name = utf8; + prop->name16 = utf16; + prop->name16len = utf16len; + prop->length = datalen; + + 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_calloc (len, sizeof (grub_uint16_t)); + 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_calloc (namelen, sizeof (grub_uint16_t)); + 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; + dp = dpstart = buf; + + while (GRUB_EFI_DEVICE_PATH_VALID (dp) && buf < bufend) + { + buf = (char *) buf + GRUB_EFI_DEVICE_PATH_LENGTH (dp); + if (GRUB_EFI_END_ENTIRE_DEVICE_PATH (dp)) + break; + dp = buf; + } + + 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); +} |