grub/grub-core/loader/i386/xnu.c
Peter Jones d2cf823d0e efi: Fix some malformed device path arithmetic errors
Several places we take the length of a device path and subtract 4 from
it, without ever checking that it's >= 4. There are also cases where
this kind of malformation will result in unpredictable iteration,
including treating the length from one dp node as the type in the next
node. These are all errors, no matter where the data comes from.

This patch adds a checking macro, GRUB_EFI_DEVICE_PATH_VALID(), which
can be used in several places, and makes GRUB_EFI_NEXT_DEVICE_PATH()
return NULL and GRUB_EFI_END_ENTIRE_DEVICE_PATH() evaluate as true when
the length is too small. Additionally, it makes several places in the
code check for and return errors in these cases.

Signed-off-by: Peter Jones <pjones@redhat.com>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
2020-07-29 16:55:48 +02:00

1166 lines
30 KiB
C

/*
* 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);
}