xnu.c 38.3 KB
Newer Older
1
/* xnu.c - load xnu kernel. Thanks to Florian Idelberger for all the
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
   time he spent testing this
 */
/*
 *  GRUB  --  GRand Unified Bootloader
 *  Copyright (C) 2009  Free Software Foundation, Inc.
 *
 *  GRUB is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  GRUB is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <grub/file.h>
#include <grub/xnu.h>
#include <grub/cpu/xnu.h>
#include <grub/mm.h>
#include <grub/dl.h>
#include <grub/loader.h>
#include <grub/machoload.h>
#include <grub/macho.h>
#include <grub/cpu/macho.h>
#include <grub/command.h>
#include <grub/misc.h>
33
#include <grub/extcmd.h>
34
#include <grub/env.h>
35
#include <grub/i18n.h>
36

37 38
GRUB_MOD_LICENSE ("GPLv3+");

39 40 41 42
#if defined (__i386) && !defined (GRUB_MACHINE_EFI)
#include <grub/autoefi.h>
#endif

43 44 45
struct grub_xnu_devtree_key *grub_xnu_devtree_root = 0;
static int driverspackagenum = 0;
static int driversnum = 0;
phcoder's avatar
phcoder committed
46
int grub_xnu_is_64bit = 0;
47
int grub_xnu_darwin_version = 0;
48

49
grub_addr_t grub_xnu_heap_target_start = 0;
50
grub_size_t grub_xnu_heap_size = 0;
51
struct grub_relocator *grub_xnu_relocator;
52 53

static grub_err_t
54
grub_xnu_register_memory (const char *prefix, int *suffix,
55 56 57
			  grub_addr_t addr, grub_size_t size);
grub_err_t
grub_xnu_heap_malloc (int size, void **src, grub_addr_t *target)
58
{
59
  grub_err_t err;
60
  grub_relocator_chunk_t ch;
61
  
62
  err = grub_relocator_alloc_chunk_addr (grub_xnu_relocator, &ch,
63 64 65 66
					 grub_xnu_heap_target_start
					 + grub_xnu_heap_size, size);
  if (err)
    return err;
67

68
  *src = get_virtual_current_address (ch);
69
  *target = grub_xnu_heap_target_start + grub_xnu_heap_size;
70
  grub_xnu_heap_size += size;
71 72
  grub_dprintf ("xnu", "val=%p\n", *src);
  return GRUB_ERR_NONE;
73 74
}

75 76
/* Make sure next block of the heap will be aligned.
   Please notice: aligned are pointers AFTER relocation
77 78 79 80
   and not the current ones. */
grub_err_t
grub_xnu_align_heap (int align)
{
81 82 83
  grub_xnu_heap_size
    = ALIGN_UP (grub_xnu_heap_target_start+ grub_xnu_heap_size, align)
    - grub_xnu_heap_target_start;
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
  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;
100
      grub_free (cur);
101
      cur = d;
102
    }
103 104 105 106
}

/* Compute the size of device tree in xnu format. */
static grub_size_t
107 108
grub_xnu_writetree_get_size (struct grub_xnu_devtree_key *start,
			     const char *name)
109 110 111
{
  grub_size_t ret;
  struct grub_xnu_devtree_key *cur;
112

113 114 115 116 117
  /* Key header. */
  ret = 2 * sizeof (grub_uint32_t);

  /* "name" value. */
  ret += 32 + sizeof (grub_uint32_t)
118
    + grub_strlen (name) + 4
119 120 121 122 123 124
    - (grub_strlen (name) % 4);

  for (cur = start; cur; cur = cur->next)
    if (cur->datasize != -1)
      {
	int align_overhead;
125

126 127 128 129 130 131 132 133 134 135 136 137
	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 *
138
grub_xnu_writetree_toheap_real (void *curptr,
139 140
				struct grub_xnu_devtree_key *start,
				const char *name)
141 142 143 144 145 146 147 148 149 150 151
{
  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. */
152 153
  nvals++;

154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
  *((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;
175

176 177 178 179 180 181 182 183 184 185 186 187 188
	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;
      }
189

190 191 192
  /* And then the keys. Recursively use this function. */
  for (cur = start; cur; cur = cur->next)
    if (cur->datasize == -1)
193 194 195 196 197 198 199
      {
	curptr = grub_xnu_writetree_toheap_real (curptr,
						 cur->first_child,
						 cur->name);
	if (!curptr)
	  return 0;
      }
200 201 202 203
  return curptr;
}

grub_err_t
204
grub_xnu_writetree_toheap (grub_addr_t *target, grub_size_t *size)
205 206 207 208 209 210
{
  struct grub_xnu_devtree_key *chosen;
  struct grub_xnu_devtree_key *memorymap;
  struct grub_xnu_devtree_key *driverkey;
  struct grub_xnu_extdesc *extdesc;
  grub_err_t err;
211
  void *src;
212

213 214 215
  err = grub_xnu_align_heap (GRUB_XNU_PAGESIZE);
  if (err)
    return err;
216

217 218 219 220 221 222 223 224 225 226 227
  /* 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)
228
    return grub_errno;
229 230
  driverkey->name = grub_strdup ("DeviceTree");
  if (! driverkey->name)
231
    return grub_errno;
232 233 234
  driverkey->datasize = sizeof (*extdesc);
  driverkey->next = memorymap->first_child;
  memorymap->first_child = driverkey;
235
  driverkey->data = extdesc
236 237
    = (struct grub_xnu_extdesc *) grub_malloc (sizeof (*extdesc));
  if (! driverkey->data)
238
    return grub_errno;
239 240 241

  /* Allocate the space based on the size with dummy value. */
  *size = grub_xnu_writetree_get_size (grub_xnu_devtree_root, "/");
242 243 244 245
  err = grub_xnu_heap_malloc (ALIGN_UP (*size + 1, GRUB_XNU_PAGESIZE),
			      &src, target);
  if (err)
    return err;
246 247

  /* Put real data in the dummy. */
248
  extdesc->addr = *target;
249 250 251
  extdesc->size = (grub_uint32_t) *size;

  /* Write the tree to heap. */
252
  grub_xnu_writetree_toheap_real (src, grub_xnu_devtree_root, "/");
253 254 255 256 257
  return GRUB_ERR_NONE;
}

/* Find a key or value in parent key. */
struct grub_xnu_devtree_key *
258
grub_xnu_find_key (struct grub_xnu_devtree_key *parent, const char *name)
259 260 261 262 263 264 265 266 267
{
  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 *
268
grub_xnu_create_key (struct grub_xnu_devtree_key **parent, const char *name)
269 270 271 272 273
{
  struct grub_xnu_devtree_key *ret;
  ret = grub_xnu_find_key (*parent, name);
  if (ret)
    return ret;
274
  ret = (struct grub_xnu_devtree_key *) grub_zalloc (sizeof (*ret));
275
  if (! ret)
276
    return 0;
277 278 279 280 281 282 283 284 285 286 287 288 289
  ret->name = grub_strdup (name);
  if (! ret->name)
    {
      grub_free (ret);
      return 0;
    }
  ret->datasize = -1;
  ret->next = *parent;
  *parent = ret;
  return ret;
}

struct grub_xnu_devtree_key *
290
grub_xnu_create_value (struct grub_xnu_devtree_key **parent, const char *name)
291 292 293 294 295 296 297 298 299 300 301 302 303
{
  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;
    }
304
  ret = (struct grub_xnu_devtree_key *) grub_zalloc (sizeof (*ret));
305
  if (! ret)
306
    return 0;
307 308 309 310 311 312 313 314 315 316 317 318 319 320
  ret->name = grub_strdup (name);
  if (! ret->name)
    {
      grub_free (ret);
      return 0;
    }
  ret->next = *parent;
  *parent = ret;
  return ret;
}

static grub_err_t
grub_xnu_unload (void)
{
321 322
  grub_cpu_xnu_unload ();

323 324 325 326 327 328
  grub_xnu_free_devtree (grub_xnu_devtree_root);
  grub_xnu_devtree_root = 0;

  /* Free loaded image. */
  driversnum = 0;
  driverspackagenum = 0;
329 330 331
  grub_relocator_unload (grub_xnu_relocator);
  grub_xnu_relocator = NULL;
  grub_xnu_heap_target_start = 0;
332 333 334 335 336 337 338 339 340 341 342
  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;
phcoder's avatar
phcoder committed
343
  grub_uint32_t startcode, endcode;
344
  int i;
345 346
  char *ptr;
  void *loadaddr;
347
  grub_addr_t loadaddr_target;
348 349

  if (argc < 1)
350
    return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
351 352 353

  grub_xnu_unload ();

354
  macho = grub_macho_open (args[0], 0);
355 356 357
  if (! macho)
    return grub_errno;

358 359
  err = grub_macho_size32 (macho, &startcode, &endcode, GRUB_MACHO_NOBSS,
			   args[0]);
360 361 362 363 364 365 366
  if (err)
    {
      grub_macho_close (macho);
      grub_xnu_unload ();
      return err;
    }

367
  grub_dprintf ("xnu", "endcode = %lx, startcode = %lx\n",
368 369
		(unsigned long) endcode, (unsigned long) startcode);

370 371 372 373
  grub_xnu_relocator = grub_relocator_new ();
  if (!grub_xnu_relocator)
    return grub_errno;
  grub_xnu_heap_target_start = startcode;
374
  err = grub_xnu_heap_malloc (endcode - startcode, &loadaddr,
375
			      &loadaddr_target);
376

377
  if (err)
378 379 380
    {
      grub_macho_close (macho);
      grub_xnu_unload ();
381
      return err;
382 383 384
    }

  /* Load kernel. */
385
  err = grub_macho_load32 (macho, args[0], (char *) loadaddr - startcode,
386
			   GRUB_MACHO_NOBSS, &grub_xnu_darwin_version);
387 388 389 390 391 392 393
  if (err)
    {
      grub_macho_close (macho);
      grub_xnu_unload ();
      return err;
    }

394
  grub_xnu_entry_point = grub_macho_get_entry_point32 (macho, args[0]);
395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414
  if (! grub_xnu_entry_point)
    {
      grub_macho_close (macho);
      grub_xnu_unload ();
      return grub_error (GRUB_ERR_BAD_OS, "couldn't find entry point");
    }

  grub_macho_close (macho);

  err = grub_xnu_align_heap (GRUB_XNU_PAGESIZE);
  if (err)
    {
      grub_xnu_unload ();
      return err;
    }

  /* Copy parameters to kernel command line. */
  ptr = grub_xnu_cmdline;
  for (i = 1; i < argc; i++)
    {
415
      if (ptr + grub_strlen (args[i]) + 1
416 417 418 419 420 421 422 423 424 425
	  >= 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)
426
    *(ptr - 1) = 0;
427

428 429 430 431 432 433
#if defined (__i386) && !defined (GRUB_MACHINE_EFI)
  err = grub_efiemu_autocore ();
  if (err)
    return err;
#endif

434
  grub_loader_set (grub_xnu_boot, grub_xnu_unload, 0);
435 436

  grub_xnu_lock ();
phcoder's avatar
phcoder committed
437
  grub_xnu_is_64bit = 0;
phcoder's avatar
phcoder committed
438 439 440 441 442 443 444 445 446 447 448 449

  return 0;
}

static grub_err_t
grub_cmd_xnu_kernel64 (grub_command_t cmd __attribute__ ((unused)),
		       int argc, char *args[])
{
  grub_err_t err;
  grub_macho_t macho;
  grub_uint64_t startcode, endcode;
  int i;
450 451
  char *ptr;
  void *loadaddr;
452
  grub_addr_t loadaddr_target;
phcoder's avatar
phcoder committed
453 454

  if (argc < 1)
455
    return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
phcoder's avatar
phcoder committed
456 457 458

  grub_xnu_unload ();

459
  macho = grub_macho_open (args[0], 1);
phcoder's avatar
phcoder committed
460 461 462
  if (! macho)
    return grub_errno;

463 464
  err = grub_macho_size64 (macho, &startcode, &endcode, GRUB_MACHO_NOBSS,
			   args[0]);
465
  if (err)
phcoder's avatar
phcoder committed
466 467 468 469 470 471 472 473 474 475 476 477
    {
      grub_macho_close (macho);
      grub_xnu_unload ();
      return err;
    }

  startcode &= 0x0fffffff;
  endcode &= 0x0fffffff;

  grub_dprintf ("xnu", "endcode = %lx, startcode = %lx\n",
		(unsigned long) endcode, (unsigned long) startcode);

478 479 480 481
  grub_xnu_relocator = grub_relocator_new ();
  if (!grub_xnu_relocator)
    return grub_errno;
  grub_xnu_heap_target_start = startcode;
482
  err = grub_xnu_heap_malloc (endcode - startcode, &loadaddr,
483
			      &loadaddr_target);
phcoder's avatar
phcoder committed
484

485
  if (err)
phcoder's avatar
phcoder committed
486 487 488
    {
      grub_macho_close (macho);
      grub_xnu_unload ();
489
      return err;
phcoder's avatar
phcoder committed
490 491 492
    }

  /* Load kernel. */
493
  err = grub_macho_load64 (macho, args[0], (char *) loadaddr - startcode,
494
			   GRUB_MACHO_NOBSS, &grub_xnu_darwin_version);
phcoder's avatar
phcoder committed
495 496 497 498 499 500 501
  if (err)
    {
      grub_macho_close (macho);
      grub_xnu_unload ();
      return err;
    }

502 503
  grub_xnu_entry_point = grub_macho_get_entry_point64 (macho, args[0])
    & 0x0fffffff;
phcoder's avatar
phcoder committed
504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535
  if (! grub_xnu_entry_point)
    {
      grub_macho_close (macho);
      grub_xnu_unload ();
      return grub_error (GRUB_ERR_BAD_OS, "couldn't find entry point");
    }

  grub_macho_close (macho);

  err = grub_xnu_align_heap (GRUB_XNU_PAGESIZE);
  if (err)
    {
      grub_xnu_unload ();
      return err;
    }

  /* Copy parameters to kernel command line. */
  ptr = grub_xnu_cmdline;
  for (i = 1; i < argc; i++)
    {
      if (ptr + grub_strlen (args[i]) + 1
	  >= grub_xnu_cmdline + sizeof (grub_xnu_cmdline))
	break;
      grub_memcpy (ptr, args[i], grub_strlen (args[i]));
      ptr += grub_strlen (args[i]);
      *ptr = ' ';
      ptr++;
    }

  /* Replace last space by '\0'. */
  if (ptr != grub_xnu_cmdline)
    *(ptr - 1) = 0;
536

537 538 539 540 541 542
#if defined (__i386) && !defined (GRUB_MACHINE_EFI)
  err = grub_efiemu_autocore ();
  if (err)
    return err;
#endif

543
  grub_loader_set (grub_xnu_boot, grub_xnu_unload, 0);
544 545

  grub_xnu_lock ();
phcoder's avatar
phcoder committed
546
  grub_xnu_is_64bit = 1;
phcoder's avatar
phcoder committed
547

548 549 550
  return 0;
}

551
/* Register a memory in a memory map under name PREFIXSUFFIX
552 553
   and increment SUFFIX. */
static grub_err_t
554
grub_xnu_register_memory (const char *prefix, int *suffix,
555
			  grub_addr_t addr, grub_size_t size)
556 557 558 559 560 561 562
{
  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)
563
    return grub_error (GRUB_ERR_BAD_OS, N_("you need to load the kernel first"));
564 565 566 567 568 569 570 571 572 573

  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)
574
    return grub_errno;
575
  if (suffix)
576
    driverkey->name = grub_xasprintf ("%s%d", prefix, (*suffix)++);
577 578
  else
    driverkey->name = grub_strdup (prefix);
579 580 581 582 583
  if (!driverkey->name)
    {
      grub_free (driverkey);
      return grub_errno;
    }
584 585
  driverkey->datasize = sizeof (*extdesc);
  driverkey->next = memorymap->first_child;
586
  driverkey->data = extdesc
587 588
    = (struct grub_xnu_extdesc *) grub_malloc (sizeof (*extdesc));
  if (! driverkey->data)
589 590 591 592 593 594
    {
      grub_free (driverkey->name);
      grub_free (driverkey);
      return grub_errno;
    }
  memorymap->first_child = driverkey;
595
  extdesc->addr = addr;
596 597 598 599
  extdesc->size = (grub_uint32_t) size;
  return GRUB_ERR_NONE;
}

phcoder's avatar
phcoder committed
600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627
static inline char *
get_name_ptr (char *name)
{
  char *p = name, *p2;
  /* Skip Info.plist.  */
  p2 = grub_strrchr (p, '/');
  if (!p2)
    return name;
  if (p2 == name)
    return name + 1;
  p = p2 - 1;

  p2 = grub_strrchr (p, '/');
  if (!p2)
    return name;
  if (p2 == name)
    return name + 1;
  if (grub_memcmp (p2, "/Contents/", sizeof ("/Contents/") - 1) != 0)
    return p2 + 1;

  p = p2 - 1;

  p2 = grub_strrchr (p, '/');
  if (!p2)
    return name;
  return p2 + 1;
}

628 629
/* Load .kext. */
static grub_err_t
630 631
grub_xnu_load_driver (char *infoplistname, grub_file_t binaryfile,
		      const char *filename)
632 633 634 635 636 637
{
  grub_macho_t macho;
  grub_err_t err;
  grub_file_t infoplist;
  struct grub_xnu_extheader *exthead;
  int neededspace = sizeof (*exthead);
638 639
  grub_uint8_t *buf;
  void *buf0;
640
  grub_addr_t buf_target;
641
  grub_size_t infoplistsize = 0, machosize = 0;
phcoder's avatar
phcoder committed
642 643 644 645 646 647 648 649 650 651 652 653
  char *name, *nameend;
  int namelen;

  name = get_name_ptr (infoplistname);
  nameend = grub_strchr (name, '/');

  if (nameend)
    namelen = nameend - name;
  else
    namelen = grub_strlen (name);

  neededspace += namelen + 1;
654 655

  if (! grub_xnu_heap_size)
656
    return grub_error (GRUB_ERR_BAD_OS, N_("you need to load the kernel first"));
657

658 659 660
  /* Compute the needed space. */
  if (binaryfile)
    {
661 662 663 664
      macho = grub_macho_file (binaryfile, filename, grub_xnu_is_64bit);
      if (!macho)
	grub_file_close (binaryfile);
      else
665
	{
666 667 668 669
	  if (grub_xnu_is_64bit)
	    machosize = grub_macho_filesize64 (macho);
	  else
	    machosize = grub_macho_filesize32 (macho);
670 671 672 673 674 675 676
	}
      neededspace += machosize;
    }
  else
    macho = 0;

  if (infoplistname)
677
    infoplist = grub_file_open (infoplistname);
678 679 680 681 682 683 684 685 686 687 688 689 690 691
  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)
692
    goto fail;
693
  err = grub_xnu_heap_malloc (neededspace, &buf0, &buf_target);
694
  if (err)
695
    goto fail;
696
  buf = buf0;
697 698 699 700 701 702 703 704

  exthead = (struct grub_xnu_extheader *) buf;
  grub_memset (exthead, 0, sizeof (*exthead));
  buf += sizeof (*exthead);

  /* Load the binary. */
  if (macho)
    {
705
      exthead->binaryaddr = buf_target + (buf - (grub_uint8_t *) buf0);
706
      exthead->binarysize = machosize;
phcoder's avatar
phcoder committed
707
      if (grub_xnu_is_64bit)
708
	err = grub_macho_readfile64 (macho, filename, buf);
phcoder's avatar
phcoder committed
709
      else
710
	err = grub_macho_readfile32 (macho, filename, buf);
phcoder's avatar
phcoder committed
711
      if (err)
712
	goto fail;
713 714 715 716 717 718 719 720
      grub_macho_close (macho);
      buf += machosize;
    }
  grub_errno = GRUB_ERR_NONE;

  /* Load the plist. */
  if (infoplist)
    {
721
      exthead->infoplistaddr = buf_target + (buf - (grub_uint8_t *) buf0);
722 723 724 725 726
      exthead->infoplistsize = infoplistsize + 1;
      if (grub_file_read (infoplist, buf, infoplistsize)
	  != (grub_ssize_t) (infoplistsize))
	{
	  grub_file_close (infoplist);
727
	  if (!grub_errno)
728
	    grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"),
729 730
			infoplistname);
	  return grub_errno;
731 732 733
	}
      grub_file_close (infoplist);
      buf[infoplistsize] = 0;
phcoder's avatar
phcoder committed
734
      buf += infoplistsize + 1;
735 736 737
    }
  grub_errno = GRUB_ERR_NONE;

738
  exthead->nameaddr = (buf - (grub_uint8_t *) buf0) + buf_target;
phcoder's avatar
phcoder committed
739 740 741 742 743
  exthead->namesize = namelen + 1;
  grub_memcpy (buf, name, namelen);
  buf[namelen] = 0;
  buf += namelen + 1;

744
  /* Announce to kernel */
745
  return grub_xnu_register_memory ("Driver-", &driversnum, buf_target,
746
				   neededspace);
747 748 749 750
fail:
  if (macho)
    grub_macho_close (macho);
  return err;
751 752 753 754 755 756 757 758 759
}

/* 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;
760
  grub_addr_t loadto_target;
761 762 763 764 765 766 767 768
  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)
769
    return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
770 771

  if (! grub_xnu_heap_size)
772
    return grub_error (GRUB_ERR_BAD_OS, N_("you need to load the kernel first"));
773

774
  file = grub_file_open (args[0]);
775
  if (! file)
776
    return grub_errno;
777 778

  /* Sometimes caches are fat binary. Errgh. */
779
  if (grub_file_read (file, &head, sizeof (head))
780 781
      != (grub_ssize_t) (sizeof (head)))
    {
782
      /* I don't know the internal structure of package but
783 784
	 can hardly imagine a valid package shorter than 20 bytes. */
      grub_file_close (file);
785 786 787
      if (!grub_errno)
	grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), args[0]);
      return grub_errno;
788 789 790 791 792 793 794 795 796 797
    }

  /* 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);
798
	  return grub_errno;
799

800
	}
801
      if (grub_file_read (file, archs,
802 803 804 805
			  sizeof (struct grub_macho_fat_arch) * narchs)
	  != (grub_ssize_t) sizeof(struct grub_macho_fat_arch) * narchs)
	{
	  grub_free (archs);
806 807 808 809
	  if (!grub_errno)
	    grub_error (GRUB_ERR_READ_ERROR, N_("premature end of file %s"),
			args[0]);
	  return grub_errno;
810 811 812
	}
      for (i = 0; i < narchs; i++)
	{
phcoder's avatar
phcoder committed
813
	  if (!grub_xnu_is_64bit && GRUB_MACHO_CPUTYPE_IS_HOST32
phcoder's avatar
phcoder committed
814 815 816 817 818
	      (grub_be_to_cpu32 (archs[i].cputype)))
	    {
	      readoff = grub_be_to_cpu32 (archs[i].offset);
	      readlen = grub_be_to_cpu32 (archs[i].size);
	    }
phcoder's avatar
phcoder committed
819
	  if (grub_xnu_is_64bit && GRUB_MACHO_CPUTYPE_IS_HOST64
820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848
	      (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;
    }

849 850
  err = grub_xnu_heap_malloc (readlen, &loadto, &loadto_target);
  if (err)
851 852
    {
      grub_file_close (file);
853
      return err;
854 855 856 857 858 859 860
    }

  /* Read the file. */
  grub_file_seek (file, readoff);
  if (grub_file_read (file, loadto, readlen) != (grub_ssize_t) (readlen))
    {
      grub_file_close (file);
861 862 863
      if (!grub_errno)
	grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), args[0]);
      return grub_errno;
864 865 866 867 868
    }
  grub_file_close (file);

  /* Pass it to kernel. */
  return grub_xnu_register_memory ("DriversPackage-", &driverspackagenum,
869
				   loadto_target, readlen);
870 871 872 873 874 875 876 877
}

static grub_err_t
grub_cmd_xnu_ramdisk (grub_command_t cmd __attribute__ ((unused)),
		      int argc, char *args[])
{
  grub_file_t file;
  void *loadto;
878
  grub_addr_t loadto_target;
879 880 881 882
  grub_err_t err;
  grub_size_t size;

  if (argc != 1)
883
    return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
884 885

  if (! grub_xnu_heap_size)
886
    return grub_error (GRUB_ERR_BAD_OS, N_("you need to load the kernel first"));
887

888
  file = grub_file_open (args[0]);
889
  if (! file)
890
    return grub_errno;
891 892 893 894 895 896

  err = grub_xnu_align_heap (GRUB_XNU_PAGESIZE);
  if (err)
    return err;

  size = grub_file_size (file);
897

898 899 900
  err = grub_xnu_heap_malloc (size, &loadto, &loadto_target);
  if (err)
    return err;
901
  if (grub_file_read (file, loadto, size) != (grub_ssize_t) (size))
902 903
    {
      grub_file_close (file);
904 905 906
      if (!grub_errno)
	grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), args[0]);
      return grub_errno;
907
    }
908
  return grub_xnu_register_memory ("RAMDisk", 0, loadto_target, size);
909 910
}

911
/* Returns true if the kext should be loaded according to plist
912 913
   and osbundlereq. Also fill BINNAME. */
static int
914 915
grub_xnu_check_os_bundle_required (char *plistname,
				   const char *osbundlereq,
916 917 918 919 920 921 922 923 924 925 926 927
				   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;

928
  file = grub_file_open (plistname);
929
  if (! file)
930
    return 0;
931 932 933 934 935

  size = grub_file_size (file);
  buf = grub_malloc (size);
  if (! buf)
    {
936
      grub_file_close (file);
937 938 939 940
      return 0;
    }
  if (grub_file_read (file, buf, size) != (grub_ssize_t) (size))
    {
941
      grub_file_close (file);
942 943
      if (!grub_errno)
	grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), plistname);
944 945
      return 0;
    }
946
  grub_file_close (file);
947 948 949 950 951 952

  /* 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;
953

954 955 956 957 958 959 960
  /* 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;
961
	if (keyptr && depth == 4
962 963
	    && grub_strcmp (keyptr, "OSBundleRequired") == 0)
	  osbundlekeyfound = 1;
964
	if (keyptr && depth == 4 &&
965 966 967 968 969 970
	    grub_strcmp (keyptr, "CFBundleExecutable") == 0)
	  binnamekeyfound = 1;
	if (stringptr && osbundlekeyfound && osbundlereq && depth == 4)
	  {
	    for (ptr2 = stringptr; *ptr2; ptr2++)
	      *ptr2 = grub_tolower (*ptr2);
971
	    ret = grub_strword (osbundlereq, stringptr)
972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001
	      || 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)
1002
	  stringptr = ptr1 + 1;
1003 1004 1005 1006 1007 1008
	else if (grub_strcmp (tagstart + 1, "/key") != 0)
	  {
	    osbundlekeyfound = 0;
	    binnamekeyfound = 0;
	  }
	*ptr1 = '>';
1009

1010 1011 1012 1013 1014 1015 1016 1017
	if (tagstart[1] == '/')
	  depth--;
	else
	  depth++;
	break;
      }
  grub_free (buf);

1018
  return ret;
1019 1020
}

1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065
/* Context for grub_xnu_scan_dir_for_kexts.  */
struct grub_xnu_scan_dir_for_kexts_ctx
{
  char *dirname;
  const char *osbundlerequired;
  int maxrecursion;
};

/* Helper for grub_xnu_scan_dir_for_kexts.  */
static int
grub_xnu_scan_dir_for_kexts_load (const char *filename,
				  const struct grub_dirhook_info *info,
				  void *data)
{
  struct grub_xnu_scan_dir_for_kexts_ctx *ctx = data;
  char *newdirname;

  if (! info->dir)
    return 0;
  if (filename[0] == '.')
    return 0;

  if (grub_strlen (filename) < 5 ||
      grub_memcmp (filename + grub_strlen (filename) - 5, ".kext", 5) != 0)
    return 0;

  newdirname
    = grub_malloc (grub_strlen (ctx->dirname) + grub_strlen (filename) + 2);

  /* It's a .kext. Try to load it. */
  if (newdirname)
    {
      grub_strcpy (newdirname, ctx->dirname);
      newdirname[grub_strlen (newdirname) + 1] = 0;
      newdirname[grub_strlen (newdirname)] = '/';
      grub_strcpy (newdirname + grub_strlen (newdirname), filename);
      grub_xnu_load_kext_from_dir (newdirname, ctx->osbundlerequired,
				   ctx->maxrecursion);
      if (grub_errno == GRUB_ERR_BAD_OS)
	grub_errno = GRUB_ERR_NONE;
      grub_free (newdirname);
    }
  return 0;
}

1066 1067
/* Load all loadable kexts placed under DIRNAME and matching OSBUNDLEREQUIRED */
grub_err_t
1068
grub_xnu_scan_dir_for_kexts (char *dirname, const char *osbundlerequired,
1069 1070
			     int maxrecursion)
{
1071 1072 1073 1074 1075
  struct grub_xnu_scan_dir_for_kexts_ctx ctx = {
    .dirname = dirname,
    .osbundlerequired = osbundlerequired,
    .maxrecursion = maxrecursion
  };
1076 1077 1078 1079 1080 1081
  grub_device_t dev;
  char *device_name;
  grub_fs_t fs;
  const char *path;

  if (! grub_xnu_heap_size)
1082
    return grub_error (GRUB_ERR_BAD_OS, N_("you need to load the kernel first"));
1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095

  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)
1096
	(fs->dir) (dev, path, grub_xnu_scan_dir_for_kexts_load, &ctx);
1097 1098 1099 1100 1101 1102 1103
      grub_device_close (dev);
    }
  grub_free (device_name);

  return GRUB_ERR_NONE;
}

1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149
/* Context for grub_xnu_load_kext_from_dir.  */
struct grub_xnu_load_kext_from_dir_ctx
{
  char *dirname;
  const char *osbundlerequired;
  int maxrecursion;
  char *plistname;
  char *newdirname;
  int usemacos;
};

/* Helper for grub_xnu_load_kext_from_dir.  */
static int
grub_xnu_load_kext_from_dir_load (const char *filename,
				  const struct grub_dirhook_info *info,
				  void *data)
{
  struct grub_xnu_load_kext_from_dir_ctx *ctx = data;

  if (grub_strlen (filename) > 15)
    return 0;
  grub_strcpy (ctx->newdirname + grub_strlen (ctx->dirname) + 1, filename);

  /* If the kext contains directory "Contents" all real stuff is in
     this directory. */
  if (info->dir && grub_strcasecmp (filename, "Contents") == 0)
    grub_xnu_load_kext_from_dir (ctx->newdirname, ctx->osbundlerequired,
				 ctx->maxrecursion - 1);

  /* Directory "Plugins" contains nested kexts. */
  if (info->dir && grub_strcasecmp (filename, "Plugins") == 0)
    grub_xnu_scan_dir_for_kexts (ctx->newdirname, ctx->osbundlerequired,
				 ctx->maxrecursion - 1);

  /* Directory "MacOS" contains executable, otherwise executable is
     on the top. */
  if (info->dir && grub_strcasecmp (filename, "MacOS") == 0)
    ctx->usemacos = 1;

  /* Info.plist is the file which governs our future actions. */
  if (! info->dir && grub_strcasecmp (filename, "Info.plist") == 0
      && ! ctx->plistname)
    ctx->plistname = grub_strdup (ctx->newdirname);
  return 0;
}

1150
/* Load extension DIRNAME. (extensions are directories in xnu) */
1151
grub_err_t
1152
grub_xnu_load_kext_from_dir (char *dirname, const char *osbundlerequired,
1153 1154
			     int maxrecursion)
{
1155 1156 1157 1158 1159 1160 1161
  struct grub_xnu_load_kext_from_dir_ctx ctx = {
    .dirname = dirname,
    .osbundlerequired = osbundlerequired,
    .maxrecursion = maxrecursion,
    .plistname = 0,
    .usemacos = 0
  };
1162 1163 1164 1165 1166 1167 1168 1169
  grub_device_t dev;
  char *newpath;
  char *device_name;
  grub_fs_t fs;
  const char *path;
  char *binsuffix;
  grub_file_t binfile;

1170 1171
  ctx.newdirname = grub_malloc (grub_strlen (dirname) + 20);
  if (! ctx.newdirname)
1172
    return grub_errno;
1173 1174 1175
  grub_strcpy (ctx.newdirname, dirname);
  ctx.newdirname[grub_strlen (dirname)] = '/';
  ctx.newdirname[grub_strlen (dirname) + 1] = 0;
1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186
  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++;

1187
      newpath = grub_strchr (ctx.newdirname, ')');
1188
      if (! newpath)
1189
	newpath = ctx.newdirname;
1190 1191
      else
	newpath++;
1192

1193 1194
      /* Look at the directory. */
      if (fs)
1195
	(fs->dir) (dev, path, grub_xnu_load_kext_from_dir_load, &ctx);
1196

1197 1198
      if (ctx.plistname && grub_xnu_check_os_bundle_required
	  (ctx.plistname, osbundlerequired, &binsuffix))
1199 1200 1201 1202
	{
	  if (binsuffix)
	    {
	      /* Open the binary. */
1203 1204
	      char *binname = grub_malloc (grub_strlen (dirname)
					   + grub_strlen (binsuffix)
1205 1206
					   + sizeof ("/MacOS/"));
	      grub_strcpy (binname, dirname);
1207
	      if (ctx.usemacos)
1208 1209 1210 1211
		grub_strcpy (binname + grub_strlen (binname), "/MacOS/");
	      else
		grub_strcpy (binname + grub_strlen (binname), "/");
	      grub_strcpy (binname + grub_strlen (binname), binsuffix);
1212
	      grub_dprintf ("xnu", "%s:%s\n", ctx.plistname, binname);
1213
	      binfile = grub_file_open (binname);
1214
	      if (! binfile)
1215 1216
		grub_errno = GRUB_ERR_NONE;

1217
	      /* Load the extension. */
1218
	      grub_xnu_load_driver (ctx.plistname, binfile,
1219
				    binname);
1220 1221 1222 1223 1224
	      grub_free (binname);
	      grub_free (binsuffix);
	    }
	  else
	    {
1225 1226
	      grub_dprintf ("xnu", "%s:0\n", ctx.plistname);
	      grub_xnu_load_driver (ctx.plistname, 0, 0);
1227 1228
	    }
	}
1229
      grub_free (ctx.plistname);
1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246
      grub_device_close (dev);
    }
  grub_free (device_name);

  return GRUB_ERR_NONE;
}


static int locked=0;
static grub_dl_t my_mod;

/* Load the kext. */
static grub_err_t
grub_cmd_xnu_kext (grub_command_t cmd __attribute__ ((unused)),
		   int argc, char *args[])
{
  grub_file_t binfile = 0;
1247 1248

  if (! grub_xnu_heap_size)
1249
    return grub_error (GRUB_ERR_BAD_OS, N_("you need to load the kernel first"));
1250

1251 1252
  if (argc == 2)
    {
1253
      /* User explicitly specified plist and binary. */
1254 1255
      if (grub_strcmp (args[1], "-") != 0)
	{
1256
	  binfile = grub_file_open (args[1]);
1257
	  if (! binfile)
1258
	    return grub_errno;
1259 1260
	}
      return grub_xnu_load_driver (grub_strcmp (args[0], "-") ? args[0] : 0,
1261
				   binfile, args[1]);
1262 1263 1264 1265 1266 1267
    }

  /* load kext normally. */
  if (argc == 1)
    return grub_xnu_load_kext_from_dir (args[0], 0, 10);

1268
  return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
1269 1270 1271 1272 1273 1274 1275 1276 1277 1278
}

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

1279
  if (! grub_xnu_heap_size)
1280
    return grub_error (GRUB_ERR_BAD_OS, N_("you need to load the kernel first"));
1281

1282
  if (argc == 1)
1283
    return grub_xnu_scan_dir_for_kexts (args[0],
1284 1285 1286 1287 1288 1289 1290
					"console,root,local-root,network-root",
					10);
  else
    {
      char *osbundlerequired = grub_strdup (args[1]), *ptr;
      grub_err_t err;
      if (! osbundlerequired)
1291
	return grub_errno;
1292 1293 1294 1295 1296 1297 1298 1299
      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;
    }
}

1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335
static inline int
hextoval (char c)
{
  if (c >= '0' && c <= '9')
    return c - '0';
  if (c >= 'a' && c <= 'z')
    return c - 'a' + 10;
  if (c >= 'A' && c <= 'Z')
    return c - 'A' + 10;
  return 0;
}

static inline void
unescape (char *name, char *curdot, char *nextdot, int *len)
{
  char *ptr, *dptr;
  dptr = name;
  for (ptr = curdot; ptr < nextdot;)
    if (ptr + 2 < nextdot && *ptr == '%')
      {
	*dptr = (hextoval (ptr[1]) << 4) | (hextoval (ptr[2]));
	ptr += 3;
	dptr++;
      }
    else
      {
	*dptr = *ptr;
	ptr++;
	dptr++;
      }
  *len = dptr - name;
}

grub_err_t
grub_xnu_fill_devicetree (void)
{
1336 1337
  struct grub_env_var *var;
  FOR_SORTED_ENV (var)
1338 1339 1340 1341 1342 1343 1344 1345 1346
  {
    char *nextdot = 0, *curdot;
    struct grub_xnu_devtree_key **curkey = &grub_xnu_devtree_root;
    struct grub_xnu_devtree_key *curvalue;
    char *name = 0, *data;
    int len;

    if (grub_memcmp (var->name, "XNU.DeviceTree.",
		     sizeof ("XNU.DeviceTree.") - 1) != 0)
1347
      continue;
1348 1349 1350 1351 1352 1353 1354 1355 1356 1357

    curdot = var->name + sizeof ("XNU.DeviceTree.") - 1;
    nextdot = grub_strchr (curdot, '.');
    if (nextdot)
      nextdot++;
    while (nextdot)
      {
	name = grub_realloc (name, nextdot - curdot + 1);

	if (!name)
1358
	  return grub_errno;
1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373

	unescape (name, curdot, nextdot, &len);
	name[len - 1] = 0;

	curkey = &(grub_xnu_create_key (curkey, name)->first_child);

	curdot = nextdot;
	nextdot = grub_strchr (nextdot, '.');
	if (nextdot)
	  nextdot++;
      }

    nextdot = curdot + grub_strlen (curdot) + 1;

    name = grub_realloc (name, nextdot - curdot + 1);
1374
   
1375
    if (!name)
1376
      return grub_errno;
1377
   
1378 1379 1380 1381
    unescape (name, curdot, nextdot, &len);
    name[len] = 0;

    curvalue = grub_xnu_create_value (curkey, name);
1382 1383
    if (!curvalue)
      return grub_errno;
1384
    grub_free (name);
1385
   
1386 1387
    data = grub_malloc (grub_strlen (var->value) + 1);
    if (!data)
1388
      return grub_errno;
1389
   
1390 1391 1392 1393 1394 1395 1396 1397 1398
    unescape (data, var->value, var->value + grub_strlen (var->value),
	      &len);
    curvalue->datasize = len;
    curvalue->data = data;
  }

  return grub_errno;
}

1399
struct grub_video_bitmap *grub_xnu_bitmap = 0;
1400 1401 1402 1403 1404 1405 1406
grub_xnu_bitmap_mode_t grub_xnu_bitmap_mode;

/* Option array indices.  */
#define XNU_SPLASH_CMD_ARGINDEX_MODE 0

static const struct grub_arg_option xnu_splash_cmd_options[] =
  {
1407
    {"mode", 'm', 0, N_("Background image mode."), N_("stretch|normal"),
1408 1409 1410
     ARG_TYPE_STRING},
    {0, 0, 0, 0, 0, 0}
  };
1411 1412

static grub_err_t
1413
grub_cmd_xnu_splash (grub_extcmd_context_t ctxt,
1414 1415 1416 1417
		     int argc, char *args[])
{
  grub_err_t err;
  if (argc != 1)
1418
    return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
1419

1420
  if (! grub_xnu_heap_size)
1421
    return grub_error (GRUB_ERR_BAD_OS, N_("you need to load the kernel first"));
1422

1423 1424
  if (ctxt->state[XNU_SPLASH_CMD_ARGINDEX_MODE].set &&
      grub_strcmp (ctxt->state[XNU_SPLASH_CMD_ARGINDEX_MODE].arg,
1425 1426 1427 1428 1429
		   "stretch") == 0)
    grub_xnu_bitmap_mode = GRUB_XNU_BITMAP_STRETCH;
  else
    grub_xnu_bitmap_mode = GRUB_XNU_BITMAP_CENTER;

1430 1431 1432
  err = grub_video_bitmap_load (&grub_xnu_bitmap, args[0]);
  if (err)
    grub_xnu_bitmap = 0;
1433

1434 1435 1436 1437
  return err;
}


1438
#ifndef GRUB_MACHINE_EMU
1439 1440 1441 1442 1443
static grub_err_t
grub_cmd_xnu_resume (grub_command_t cmd __attribute__ ((unused)),
		     int argc, char *args[])
{
  if (argc != 1)
1444
    return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
1445

1446 1447 1448 1449 1450
  return grub_xnu_resume (args[0]);
}
#endif

void
1451
grub_xnu_lock (void)
1452 1453 1454 1455 1456 1457 1458
{
  if (!locked)
    grub_dl_ref (my_mod);
  locked = 1;
}

void
1459
grub_xnu_unlock (void)
1460 1461 1462 1463 1464 1465
{
  if (locked)
    grub_dl_unref (my_mod);
  locked = 0;
}

phcoder's avatar
phcoder committed
1466
static grub_command_t cmd_kernel64, cmd_kernel, cmd_mkext, cmd_kext;
1467 1468
static grub_command_t cmd_kextdir, cmd_ramdisk, cmd_resume;
static grub_extcmd_t cmd_splash;
1469 1470 1471 1472

GRUB_MOD_INIT(xnu)
{
  cmd_kernel = grub_register_command ("xnu_kernel", grub_cmd_xnu_kernel, 0,
1473
				      N_("Load XNU image."));
phcoder's avatar
phcoder committed
1474
  cmd_kernel64 = grub_register_command ("xnu_kernel64", grub_cmd_xnu_kernel64,
1475
					0, N_("Load 64-bit XNU image."));
1476
  cmd_mkext = grub_register_command ("xnu_mkext", grub_cmd_xnu_mkext, 0,
1477
				     N_("Load XNU extension package."));
1478
  cmd_kext = grub_register_command ("xnu_kext", grub_cmd_xnu_kext, 0,
1479
				    N_("Load XNU extension."));
1480
  cmd_kextdir = grub_register_command ("xnu_kextdir", grub_cmd_xnu_kextdir,
1481 1482 1483 1484 1485
				       /* TRANSLATORS: OSBundleRequired is a
					  variable name in xnu extensions
					  manifests. It behaves mostly like
					  GNU/Linux runlevels.
				       */
1486
				       N_("DIRECTORY [OSBundleRequired]"),
1487 1488
				       /* TRANSLATORS: There are many extensions
					  in extension directory.  */
1489
				       N_("Load XNU extension directory."));
1490
  cmd_ramdisk = grub_register_command ("xnu_ramdisk", grub_cmd_xnu_ramdisk, 0,
1491
   /* TRANSLATORS: ramdisk here isn't identifier. It can be translated.  */
1492
				       N_("Load XNU ramdisk. "
1493
					  "It will be available in OS as md0."));
1494
  cmd_splash = grub_register_extcmd ("xnu_splash",
1495
				     grub_cmd_xnu_splash, 0, 0,
1496
				     N_("Load a splash image for XNU."),
1497
				     xnu_splash_cmd_options);
1498

1499
#ifndef GRUB_MACHINE_EMU
1500
  cmd_resume = grub_register_command ("xnu_resume", grub_cmd_xnu_resume,
1501
				      0, N_("Load an image of hibernated"
1502
					    " XNU."));
1503
#endif
1504 1505 1506 1507

  grub_cpu_xnu_init ();

  my_mod = mod;
1508 1509 1510 1511
}

GRUB_MOD_FINI(xnu)
{
1512
#ifndef GRUB_MACHINE_EMU
1513 1514 1515 1516 1517 1518 1519
  grub_unregister_command (cmd_resume);
#endif
  grub_unregister_command (cmd_mkext);
  grub_unregister_command (cmd_kext);
  grub_unregister_command (cmd_kextdir);
  grub_unregister_command (cmd_ramdisk);
  grub_unregister_command (cmd_kernel);
1520
  grub_unregister_extcmd (cmd_splash);
phcoder's avatar
phcoder committed
1521
  grub_unregister_command (cmd_kernel64);
1522 1523

  grub_cpu_xnu_fini ();
1524
}