acpi.c 22.4 KB
Newer Older
1 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
/* acpi.c - modify acpi tables. */
/*
 *  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/dl.h>
#include <grub/extcmd.h>
#include <grub/file.h>
#include <grub/disk.h>
#include <grub/term.h>
#include <grub/misc.h>
#include <grub/acpi.h>
#include <grub/mm.h>
#include <grub/memory.h>
29
#include <grub/i18n.h>
30 31 32 33 34 35

#ifdef GRUB_MACHINE_EFI
#include <grub/efi/efi.h>
#include <grub/efi/api.h>
#endif

36 37
#pragma GCC diagnostic ignored "-Wcast-align"

38 39
GRUB_MOD_LICENSE ("GPLv3+");

40
static const struct grub_arg_option options[] = {
41
  {"exclude", 'x', 0,
42
   N_("Don't load host tables specified by comma-separated list."),
43
   0, ARG_TYPE_STRING},
44
  {"load-only", 'n', 0,
45
   N_("Load only tables specified by comma-separated list."), 0, ARG_TYPE_STRING},
46 47
  {"v1", '1', 0, N_("Export version 1 tables to the OS."), 0, ARG_TYPE_NONE},
  {"v2", '2', 0, N_("Export version 2 and version 3 tables to the OS."), 0, ARG_TYPE_NONE},
48
  {"oemid", 'o', 0, N_("Set OEMID of RSDP, XSDT and RSDT."), 0, ARG_TYPE_STRING},
49
  {"oemtable", 't', 0,
50
   N_("Set OEMTABLE ID of RSDP, XSDT and RSDT."), 0, ARG_TYPE_STRING},
51
  {"oemtablerev", 'r', 0,
52
   N_("Set OEMTABLE revision of RSDP, XSDT and RSDT."), 0, ARG_TYPE_INT},
53
  {"oemtablecreator", 'c', 0,
54
   N_("Set creator field of RSDP, XSDT and RSDT."), 0, ARG_TYPE_STRING},
55
  {"oemtablecreatorrev", 'd', 0,
56
   N_("Set creator revision of RSDP, XSDT and RSDT."), 0, ARG_TYPE_INT},
57
  /* TRANSLATORS: "hangs" here is a noun, not a verb.  */
58 59
  {"no-ebda", 'e', 0, N_("Don't update EBDA. May fix failures or hangs on some "
   "BIOSes but makes it ineffective with OS not receiving RSDP from GRUB."),
60 61 62 63
   0, ARG_TYPE_NONE},
  {0, 0, 0, 0, 0, 0}
};

64
/* rev1 is 1 if ACPIv1 is to be generated, 0 otherwise.
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
   rev2 contains the revision of ACPIv2+ to generate or 0 if none. */
static int rev1, rev2;
/* OEMID of RSDP, RSDT and XSDT. */
static char root_oemid[6];
/* OEMTABLE of the same tables. */
static char root_oemtable[8];
/* OEMREVISION of the same tables. */
static grub_uint32_t root_oemrev;
/* CreatorID of the same tables. */
static char root_creator_id[4];
/* CreatorRevision of the same tables. */
static grub_uint32_t root_creator_rev;
static struct grub_acpi_rsdp_v10 *rsdpv1_new = 0;
static struct grub_acpi_rsdp_v20 *rsdpv2_new = 0;
static char *playground = 0, *playground_ptr = 0;
static int playground_size = 0;

/* Linked list of ACPI tables. */
struct efiemu_acpi_table
{
  void *addr;
  grub_size_t size;
  struct efiemu_acpi_table *next;
};
static struct efiemu_acpi_table *acpi_tables = 0;

/* DSDT isn't in RSDT. So treat it specially. */
static void *table_dsdt = 0;
/* Pointer to recreated RSDT. */
static void *rsdt_addr = 0;

/* Allocation handles for different tables. */
static grub_size_t dsdt_size = 0;

/* Address of original FACS. */
static grub_uint32_t facs_addr = 0;

struct grub_acpi_rsdp_v20 *
grub_acpi_get_rsdpv2 (void)
{
  if (rsdpv2_new)
    return rsdpv2_new;
  if (rsdpv1_new)
    return 0;
  return grub_machine_acpi_get_rsdpv2 ();
}

struct grub_acpi_rsdp_v10 *
grub_acpi_get_rsdpv1 (void)
{
  if (rsdpv1_new)
    return rsdpv1_new;
  if (rsdpv2_new)
    return 0;
  return grub_machine_acpi_get_rsdpv1 ();
}

122 123
#if defined (__i386__) || defined (__x86_64__)

124
static inline int
125 126 127 128 129 130 131 132 133
iszero (grub_uint8_t *reg, int size)
{
  int i;
  for (i = 0; i < size; i++)
    if (reg[i])
      return 0;
  return 1;
}

134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
/* Context for grub_acpi_create_ebda.  */
struct grub_acpi_create_ebda_ctx {
  int ebda_len;
  grub_uint64_t highestlow;
};

/* Helper for grub_acpi_create_ebda.  */
static int
find_hook (grub_uint64_t start, grub_uint64_t size, grub_memory_type_t type,
	   void *data)
{
  struct grub_acpi_create_ebda_ctx *ctx = data;
  grub_uint64_t end = start + size;
  if (type != GRUB_MEMORY_AVAILABLE)
    return 0;
  if (end > 0x100000)
    end = 0x100000;
  if (end > start + ctx->ebda_len
      && ctx->highestlow < ((end - ctx->ebda_len) & (~0xf)) )
    ctx->highestlow = (end - ctx->ebda_len) & (~0xf);
  return 0;
}

157
grub_err_t
158 159
grub_acpi_create_ebda (void)
{
160 161 162
  struct grub_acpi_create_ebda_ctx ctx = {
    .highestlow = 0
  };
163
  int ebda_kb_len = 0;
164 165 166 167 168
  int mmapregion = 0;
  grub_uint8_t *ebda, *v1inebda = 0, *v2inebda = 0;
  grub_uint8_t *targetebda, *target;
  struct grub_acpi_rsdp_v10 *v1;
  struct grub_acpi_rsdp_v20 *v2;
169

170
  ebda = (grub_uint8_t *) (grub_addr_t) ((*((grub_uint16_t *)0x40e)) << 4);
171
  grub_dprintf ("acpi", "EBDA @%p\n", ebda);
172 173
  if (ebda)
    ebda_kb_len = *(grub_uint16_t *) ebda;
174
  grub_dprintf ("acpi", "EBDA length 0x%x\n", ebda_kb_len);
175
  if (ebda_kb_len > 16)
176
    ebda_kb_len = 0;
177
  ctx.ebda_len = (ebda_kb_len + 1) << 10;
178 179

  /* FIXME: use low-memory mm allocation once it's available. */
180 181
  grub_mmap_iterate (find_hook, &ctx);
  targetebda = (grub_uint8_t *) (grub_addr_t) ctx.highestlow;
182
  grub_dprintf ("acpi", "creating ebda @%llx\n",
183 184
		(unsigned long long) ctx.highestlow);
  if (! ctx.highestlow)
185
    return grub_error (GRUB_ERR_OUT_OF_MEMORY,
186 187
		       "couldn't find space for the new EBDA");

188
  mmapregion = grub_mmap_register ((grub_addr_t) targetebda, ctx.ebda_len,
189
				   GRUB_MEMORY_RESERVED);
190 191 192
  if (! mmapregion)
    return grub_errno;

193
  /* XXX: EBDA is unstandardized, so this implementation is heuristical. */
194 195 196 197 198 199 200 201 202 203 204 205
  if (ebda_kb_len)
    grub_memcpy (targetebda, ebda, 0x400);
  else
    grub_memset (targetebda, 0, 0x400);
  *((grub_uint16_t *) targetebda) = ebda_kb_len + 1;
  target = targetebda;

  v1 = grub_acpi_get_rsdpv1 ();
  v2 = grub_acpi_get_rsdpv2 ();
  if (v2 && v2->length > 40)
    v2 = 0;

206
  /* First try to replace already existing rsdp. */
207 208 209 210
  if (v2)
    {
      grub_dprintf ("acpi", "Scanning EBDA for old rsdpv2\n");
      for (; target < targetebda + 0x400 - v2->length; target += 0x10)
211
	if (grub_memcmp (target, GRUB_RSDP_SIGNATURE, GRUB_RSDP_SIGNATURE_SIZE) == 0
212
	    && grub_byte_checksum (target,
213 214 215 216 217 218 219 220
				   sizeof (struct grub_acpi_rsdp_v10)) == 0
	    && ((struct grub_acpi_rsdp_v10 *) target)->revision != 0
	    && ((struct grub_acpi_rsdp_v20 *) target)->length <= v2->length)
	  {
	    grub_memcpy (target, v2, v2->length);
	    grub_dprintf ("acpi", "Copying rsdpv2 to %p\n", target);
	    v2inebda = target;
	    target += v2->length;
221
	    target = (grub_uint8_t *) ALIGN_UP((grub_addr_t) target, 16);
222 223 224 225 226 227 228 229
	    v2 = 0;
	    break;
	  }
    }

  if (v1)
    {
      grub_dprintf ("acpi", "Scanning EBDA for old rsdpv1\n");
230
      for (; target < targetebda + 0x400 - sizeof (struct grub_acpi_rsdp_v10);
231
	   target += 0x10)
232
	if (grub_memcmp (target, GRUB_RSDP_SIGNATURE, GRUB_RSDP_SIGNATURE_SIZE) == 0
233
	    && grub_byte_checksum (target,
234 235 236
				   sizeof (struct grub_acpi_rsdp_v10)) == 0)
	  {
	    grub_memcpy (target, v1, sizeof (struct grub_acpi_rsdp_v10));
237
	    grub_dprintf ("acpi", "Copying rsdpv1 to %p\n", target);
238 239
	    v1inebda = target;
	    target += sizeof (struct grub_acpi_rsdp_v10);
240
	    target = (grub_uint8_t *) ALIGN_UP((grub_addr_t) target, 16);
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
	    v1 = 0;
	    break;
	  }
    }

  target = targetebda + 0x100;

  /* Try contiguous zeros. */
  if (v2)
    {
      grub_dprintf ("acpi", "Scanning EBDA for block of zeros\n");
      for (; target < targetebda + 0x400 - v2->length; target += 0x10)
	if (iszero (target, v2->length))
	  {
	    grub_dprintf ("acpi", "Copying rsdpv2 to %p\n", target);
	    grub_memcpy (target, v2, v2->length);
	    v2inebda = target;
	    target += v2->length;
259
	    target = (grub_uint8_t *) ALIGN_UP((grub_addr_t) target, 16);
260 261 262 263 264 265 266 267
	    v2 = 0;
	    break;
	  }
    }

  if (v1)
    {
      grub_dprintf ("acpi", "Scanning EBDA for block of zeros\n");
268
      for (; target < targetebda + 0x400 - sizeof (struct grub_acpi_rsdp_v10);
269 270 271 272 273 274 275
	   target += 0x10)
	if (iszero (target, sizeof (struct grub_acpi_rsdp_v10)))
	  {
	    grub_dprintf ("acpi", "Copying rsdpv1 to %p\n", target);
	    grub_memcpy (target, v1, sizeof (struct grub_acpi_rsdp_v10));
	    v1inebda = target;
	    target += sizeof (struct grub_acpi_rsdp_v10);
276
	    target = (grub_uint8_t *) ALIGN_UP((grub_addr_t) target, 16);
277 278 279 280 281 282 283 284
	    v1 = 0;
	    break;
	  }
    }

  if (v1 || v2)
    {
      grub_mmap_unregister (mmapregion);
285
      return grub_error (GRUB_ERR_OUT_OF_MEMORY,
286
			 "couldn't find suitable spot in EBDA");
287 288 289
    }

  /* Remove any other RSDT. */
290 291
  for (target = targetebda;
       target < targetebda + 0x400 - sizeof (struct grub_acpi_rsdp_v10);
292
       target += 0x10)
293
    if (grub_memcmp (target, GRUB_RSDP_SIGNATURE, GRUB_RSDP_SIGNATURE_SIZE) == 0
294
	&& grub_byte_checksum (target,
295 296 297 298 299
			       sizeof (struct grub_acpi_rsdp_v10)) == 0
	&& target != v1inebda && target != v2inebda)
      *target = 0;

  grub_dprintf ("acpi", "Switching EBDA\n");
300
  (*((grub_uint16_t *) 0x40e)) = ((grub_addr_t) targetebda) >> 4;
301 302 303 304
  grub_dprintf ("acpi", "EBDA switched\n");

  return GRUB_ERR_NONE;
}
305
#endif
306 307 308 309 310 311 312 313 314 315 316 317 318 319 320

/* Create tables common to ACPIv1 and ACPIv2+ */
static void
setup_common_tables (void)
{
  struct efiemu_acpi_table *cur;
  struct grub_acpi_table_header *rsdt;
  grub_uint32_t *rsdt_entry;
  int numoftables;

  /* Treat DSDT. */
  grub_memcpy (playground_ptr, table_dsdt, dsdt_size);
  grub_free (table_dsdt);
  table_dsdt = playground_ptr;
  playground_ptr += dsdt_size;
321

322 323 324 325 326 327 328 329 330 331 332 333
  /* Treat other tables. */
  for (cur = acpi_tables; cur; cur = cur->next)
    {
      struct grub_acpi_fadt *fadt;

      grub_memcpy (playground_ptr, cur->addr, cur->size);
      grub_free (cur->addr);
      cur->addr = playground_ptr;
      playground_ptr += cur->size;

      /* If it's FADT correct DSDT and FACS addresses. */
      fadt = (struct grub_acpi_fadt *) cur->addr;
334
      if (grub_memcmp (fadt->hdr.signature, GRUB_ACPI_FADT_SIGNATURE,
335
		       sizeof (fadt->hdr.signature)) == 0)
336
	{
337
	  fadt->dsdt_addr = (grub_addr_t) table_dsdt;
338 339 340 341 342
	  fadt->facs_addr = facs_addr;

	  /* Does a revision 2 exist at all? */
	  if (fadt->hdr.revision >= 3)
	    {
343
	      fadt->dsdt_xaddr = (grub_addr_t) table_dsdt;
344 345 346 347 348 349 350 351
	      fadt->facs_xaddr = facs_addr;
	    }

	  /* Recompute checksum. */
	  fadt->hdr.checksum = 0;
	  fadt->hdr.checksum = 1 + ~grub_byte_checksum (fadt, fadt->hdr.length);
	}
    }
352

353 354 355 356 357 358
  /* Fill RSDT entries. */
  numoftables = 0;
  for (cur = acpi_tables; cur; cur = cur->next)
    numoftables++;

  rsdt_addr = rsdt = (struct grub_acpi_table_header *) playground_ptr;
359
  playground_ptr += sizeof (struct grub_acpi_table_header) + sizeof (grub_uint32_t) * numoftables;
360

361
  rsdt_entry = (grub_uint32_t *) (rsdt + 1);
362 363 364

  /* Fill RSDT header. */
  grub_memcpy (&(rsdt->signature), "RSDT", 4);
365
  rsdt->length = sizeof (struct grub_acpi_table_header) + sizeof (grub_uint32_t) * numoftables;
366
  rsdt->revision = 1;
367 368
  grub_memcpy (&(rsdt->oemid), root_oemid, sizeof (rsdt->oemid));
  grub_memcpy (&(rsdt->oemtable), root_oemtable, sizeof (rsdt->oemtable));
369
  rsdt->oemrev = root_oemrev;
370
  grub_memcpy (&(rsdt->creator_id), root_creator_id, sizeof (rsdt->creator_id));
371 372 373
  rsdt->creator_rev = root_creator_rev;

  for (cur = acpi_tables; cur; cur = cur->next)
374
    *(rsdt_entry++) = (grub_addr_t) cur->addr;
375

376 377 378 379 380 381 382 383 384 385 386 387
  /* Recompute checksum. */
  rsdt->checksum = 0;
  rsdt->checksum = 1 + ~grub_byte_checksum (rsdt, rsdt->length);
}

/* Regenerate ACPIv1 RSDP */
static void
setv1table (void)
{
  /* Create RSDP. */
  rsdpv1_new = (struct grub_acpi_rsdp_v10 *) playground_ptr;
  playground_ptr += sizeof (struct grub_acpi_rsdp_v10);
388
  grub_memcpy (&(rsdpv1_new->signature), GRUB_RSDP_SIGNATURE,
389
	       sizeof (rsdpv1_new->signature));
390 391
  grub_memcpy (&(rsdpv1_new->oemid), root_oemid, sizeof  (rsdpv1_new->oemid));
  rsdpv1_new->revision = 0;
392
  rsdpv1_new->rsdt_addr = (grub_addr_t) rsdt_addr;
393
  rsdpv1_new->checksum = 0;
394 395
  rsdpv1_new->checksum = 1 + ~grub_byte_checksum (rsdpv1_new,
						  sizeof (*rsdpv1_new));
396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412
  grub_dprintf ("acpi", "Generated ACPIv1 tables\n");
}

static void
setv2table (void)
{
  struct grub_acpi_table_header *xsdt;
  struct efiemu_acpi_table *cur;
  grub_uint64_t *xsdt_entry;
  int numoftables;

  numoftables = 0;
  for (cur = acpi_tables; cur; cur = cur->next)
    numoftables++;

  /* Create XSDT. */
  xsdt = (struct grub_acpi_table_header *) playground_ptr;
413
  playground_ptr += sizeof (struct grub_acpi_table_header) + sizeof (grub_uint64_t) * numoftables;
414 415 416

  xsdt_entry = (grub_uint64_t *)(xsdt + 1);
  for (cur = acpi_tables; cur; cur = cur->next)
417
    *(xsdt_entry++) = (grub_addr_t) cur->addr;
418
  grub_memcpy (&(xsdt->signature), "XSDT", 4);
419
  xsdt->length = sizeof (struct grub_acpi_table_header) + sizeof (grub_uint64_t) * numoftables;
420 421 422 423 424 425 426 427 428 429 430 431
  xsdt->revision = 1;
  grub_memcpy (&(xsdt->oemid), root_oemid, sizeof (xsdt->oemid));
  grub_memcpy (&(xsdt->oemtable), root_oemtable, sizeof (xsdt->oemtable));
  xsdt->oemrev = root_oemrev;
  grub_memcpy (&(xsdt->creator_id), root_creator_id, sizeof (xsdt->creator_id));
  xsdt->creator_rev = root_creator_rev;
  xsdt->checksum = 0;
  xsdt->checksum = 1 + ~grub_byte_checksum (xsdt, xsdt->length);

  /* Create RSDPv2. */
  rsdpv2_new = (struct grub_acpi_rsdp_v20 *) playground_ptr;
  playground_ptr += sizeof (struct grub_acpi_rsdp_v20);
432
  grub_memcpy (&(rsdpv2_new->rsdpv1.signature), GRUB_RSDP_SIGNATURE,
433
	       sizeof (rsdpv2_new->rsdpv1.signature));
434
  grub_memcpy (&(rsdpv2_new->rsdpv1.oemid), root_oemid,
435 436
	       sizeof (rsdpv2_new->rsdpv1.oemid));
  rsdpv2_new->rsdpv1.revision = rev2;
437
  rsdpv2_new->rsdpv1.rsdt_addr = (grub_addr_t) rsdt_addr;
438
  rsdpv2_new->rsdpv1.checksum = 0;
439
  rsdpv2_new->rsdpv1.checksum = 1 + ~grub_byte_checksum
440 441
    (&(rsdpv2_new->rsdpv1), sizeof (rsdpv2_new->rsdpv1));
  rsdpv2_new->length = sizeof (*rsdpv2_new);
442
  rsdpv2_new->xsdt_addr = (grub_addr_t) xsdt;
443
  rsdpv2_new->checksum = 0;
444
  rsdpv2_new->checksum = 1 + ~grub_byte_checksum (rsdpv2_new,
445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466
						  rsdpv2_new->length);
  grub_dprintf ("acpi", "Generated ACPIv2 tables\n");
}

static void
free_tables (void)
{
  struct efiemu_acpi_table *cur, *t;
  if (table_dsdt)
    grub_free (table_dsdt);
  for (cur = acpi_tables; cur;)
    {
      t = cur;
      grub_free (cur->addr);
      cur = cur->next;
      grub_free (t);
    }
  acpi_tables = 0;
  table_dsdt = 0;
}

static grub_err_t
467
grub_cmd_acpi (struct grub_extcmd_context *ctxt, int argc, char **args)
468
{
469
  struct grub_arg_list *state = ctxt->state;
470 471 472 473
  struct grub_acpi_rsdp_v10 *rsdp;
  struct efiemu_acpi_table *cur, *t;
  int i, mmapregion;
  int numoftables;
474

475 476 477 478 479 480 481
  /* Default values if no RSDP is found. */
  rev1 = 1;
  rev2 = 3;

  facs_addr = 0;
  playground = playground_ptr = 0;
  playground_size = 0;
482

483 484 485 486 487
  rsdp = (struct grub_acpi_rsdp_v10 *) grub_machine_acpi_get_rsdpv2 ();

  if (! rsdp)
    rsdp = grub_machine_acpi_get_rsdpv1 ();

488 489
  grub_dprintf ("acpi", "RSDP @%p\n", rsdp);

490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512
  if (rsdp)
    {
      grub_uint32_t *entry_ptr;
      char *exclude = 0;
      char *load_only = 0;
      char *ptr;
      /* RSDT consists of header and an array of 32-bit pointers. */
      struct grub_acpi_table_header *rsdt;

      exclude = state[0].set ? grub_strdup (state[0].arg) : 0;
      if (exclude)
	{
	  for (ptr = exclude; *ptr; ptr++)
	    *ptr = grub_tolower (*ptr);
	}

      load_only = state[1].set ? grub_strdup (state[1].arg) : 0;
      if (load_only)
	{
	  for (ptr = load_only; *ptr; ptr++)
	    *ptr = grub_tolower (*ptr);
	}

513
      /* Set revision variables to replicate the same version as host. */
514 515
      rev1 = ! rsdp->revision;
      rev2 = rsdp->revision;
516
      rsdt = (struct grub_acpi_table_header *) (grub_addr_t) rsdp->rsdt_addr;
517 518
      /* Load host tables. */
      for (entry_ptr = (grub_uint32_t *) (rsdt + 1);
519
	   entry_ptr < (grub_uint32_t *) (((grub_uint8_t *) rsdt)
520 521 522 523 524
					  + rsdt->length);
	   entry_ptr++)
	{
	  char signature[5];
	  struct efiemu_acpi_table *table;
525
	  struct grub_acpi_table_header *curtable
526
	    = (struct grub_acpi_table_header *) (grub_addr_t) *entry_ptr;
527 528 529
	  signature[4] = 0;
	  for (i = 0; i < 4;i++)
	    signature[i] = grub_tolower (curtable->signature[i]);
530

531 532 533 534 535 536
	  /* If it's FADT it contains addresses of DSDT and FACS. */
	  if (grub_strcmp (signature, "facp") == 0)
	    {
	      struct grub_acpi_table_header *dsdt;
	      struct grub_acpi_fadt *fadt = (struct grub_acpi_fadt *) curtable;

537
	      /* Set root header variables to the same values
538
		 as FADT by default. */
539
	      grub_memcpy (&root_oemid, &(fadt->hdr.oemid),
540
			   sizeof (root_oemid));
541
	      grub_memcpy (&root_oemtable, &(fadt->hdr.oemtable),
542 543
			   sizeof (root_oemtable));
	      root_oemrev = fadt->hdr.oemrev;
544
	      grub_memcpy (&root_creator_id, &(fadt->hdr.creator_id),
545 546 547 548
			   sizeof (root_creator_id));
	      root_creator_rev = fadt->hdr.creator_rev;

	      /* Load DSDT if not excluded. */
549
	      dsdt = (struct grub_acpi_table_header *)
550
		(grub_addr_t) fadt->dsdt_addr;
551 552 553 554 555 556 557 558 559 560 561
	      if (dsdt && (! exclude || ! grub_strword (exclude, "dsdt"))
		  && (! load_only || grub_strword (load_only, "dsdt"))
		  && dsdt->length >= sizeof (*dsdt))
		{
		  dsdt_size = dsdt->length;
		  table_dsdt = grub_malloc (dsdt->length);
		  if (! table_dsdt)
		    {
		      free_tables ();
		      grub_free (exclude);
		      grub_free (load_only);
562
		      return grub_errno;
563 564 565 566
		    }
		  grub_memcpy (table_dsdt, dsdt, dsdt->length);
		}

567
	      /* Save FACS address. FACS shouldn't be overridden. */
568 569
	      facs_addr = fadt->facs_addr;
	    }
570

571 572 573 574 575 576 577 578 579
	  /* Skip excluded tables. */
	  if (exclude && grub_strword (exclude, signature))
	    continue;
	  if (load_only && ! grub_strword (load_only, signature))
	    continue;

	  /* Sanity check. */
	  if (curtable->length < sizeof (*curtable))
	    continue;
580 581

	  table = (struct efiemu_acpi_table *) grub_malloc
582 583 584 585 586 587
	    (sizeof (struct efiemu_acpi_table));
	  if (! table)
	    {
	      free_tables ();
	      grub_free (exclude);
	      grub_free (load_only);
588
	      return grub_errno;
589 590 591 592 593 594 595
	    }
	  table->size = curtable->length;
	  table->addr = grub_malloc (table->size);
	  playground_size += table->size;
	  if (! table->addr)
	    {
	      free_tables ();
Andrei Borzenkov's avatar
Andrei Borzenkov committed
596 597 598
	      grub_free (exclude);
	      grub_free (load_only);
	      grub_free (table);
599
	      return grub_errno;
600 601 602 603 604 605
	    }
	  table->next = acpi_tables;
	  acpi_tables = table;
	  grub_memcpy (table->addr, curtable, table->size);
	}
      grub_free (exclude);
606
      grub_free (load_only);
607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637
    }

  /* Does user specify versions to generate? */
  if (state[2].set || state[3].set)
    {
      rev1 = state[2].set;
      if (state[3].set)
	rev2 = rev2 ? : 2;
      else
	rev2 = 0;
    }

  /* Does user override root header information? */
  if (state[4].set)
    grub_strncpy (root_oemid, state[4].arg, sizeof (root_oemid));
  if (state[5].set)
    grub_strncpy (root_oemtable, state[5].arg, sizeof (root_oemtable));
  if (state[6].set)
    root_oemrev = grub_strtoul (state[6].arg, 0, 0);
  if (state[7].set)
    grub_strncpy (root_creator_id, state[7].arg, sizeof (root_creator_id));
  if (state[8].set)
    root_creator_rev = grub_strtoul (state[8].arg, 0, 0);

  /* Load user tables */
  for (i = 0; i < argc; i++)
    {
      grub_file_t file;
      grub_size_t size;
      char *buf;

638
      file = grub_file_open (args[i]);
639 640 641
      if (! file)
	{
	  free_tables ();
642
	  return grub_errno;
643 644 645 646 647 648 649
	}

      size = grub_file_size (file);
      if (size < sizeof (struct grub_acpi_table_header))
	{
	  grub_file_close (file);
	  free_tables ();
650 651
	  return grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"),
			     args[i]);
652 653 654 655 656 657 658
	}

      buf = (char *) grub_malloc (size);
      if (! buf)
	{
	  grub_file_close (file);
	  free_tables ();
659
	  return grub_errno;
660 661 662 663 664 665
	}

      if (grub_file_read (file, buf, size) != (int) size)
	{
	  grub_file_close (file);
	  free_tables ();
666 667 668 669
	  if (!grub_errno)
	    grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"),
			args[i]);
	  return grub_errno;
670
	}
671
      grub_file_close (file);
672

673
      if (grub_memcmp (((struct grub_acpi_table_header *) buf)->signature,
674 675 676 677 678 679 680 681 682
		       "DSDT", 4) == 0)
	{
	  grub_free (table_dsdt);
	  table_dsdt = buf;
	  dsdt_size = size;
	}
      else
	{
	  struct efiemu_acpi_table *table;
683
	  table = (struct efiemu_acpi_table *) grub_malloc
684 685 686 687
	    (sizeof (struct efiemu_acpi_table));
	  if (! table)
	    {
	      free_tables ();
688
	      return grub_errno;
689 690 691 692 693
	    }

	  table->size = size;
	  table->addr = buf;
	  playground_size += table->size;
694 695 696

	  table->next = acpi_tables;
	  acpi_tables = table;
697 698 699 700 701 702 703 704 705 706
	}
    }

  numoftables = 0;
  for (cur = acpi_tables; cur; cur = cur->next)
    numoftables++;

  /* DSDT. */
  playground_size += dsdt_size;
  /* RSDT. */
707
  playground_size += sizeof (struct grub_acpi_table_header) + sizeof (grub_uint32_t) * numoftables;
708 709 710
  /* RSDPv1. */
  playground_size += sizeof (struct grub_acpi_rsdp_v10);
  /* XSDT. */
711
  playground_size += sizeof (struct grub_acpi_table_header) + sizeof (grub_uint64_t) * numoftables;
712 713
  /* RSDPv2. */
  playground_size += sizeof (struct grub_acpi_rsdp_v20);
714 715

  playground = playground_ptr
716
    = grub_mmap_malign_and_register (1, playground_size, &mmapregion,
717
				     GRUB_MEMORY_ACPI, 0);
718 719 720 721

  if (! playground)
    {
      free_tables ();
722
      return grub_error (GRUB_ERR_OUT_OF_MEMORY,
723
			 "couldn't allocate space for ACPI tables");
724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743
    }

  setup_common_tables ();

  /* Request space for RSDPv1. */
  if (rev1)
    setv1table ();

  /* Request space for RSDPv2+ and XSDT. */
  if (rev2)
    setv2table ();

  for (cur = acpi_tables; cur;)
    {
      t = cur;
      cur = cur->next;
      grub_free (t);
    }
  acpi_tables = 0;

744 745
#if defined (__i386__) || defined (__x86_64__)
  if (! state[9].set)
746
    {
747 748 749 750 751 752 753 754 755
      grub_err_t err;
      err = grub_acpi_create_ebda ();
      if (err)
	{
	  rsdpv1_new = 0;
	  rsdpv2_new = 0;
	  grub_mmap_free_and_unregister (mmapregion);
	  return err;
	}
756
    }
757
#endif
758 759 760 761 762 763

#ifdef GRUB_MACHINE_EFI
  {
    struct grub_efi_guid acpi = GRUB_EFI_ACPI_TABLE_GUID;
    struct grub_efi_guid acpi20 = GRUB_EFI_ACPI_20_TABLE_GUID;

764
    grub_efi_system_table->boot_services->install_configuration_table
765
      (&acpi20, grub_acpi_get_rsdpv2 ());
766
    grub_efi_system_table->boot_services->install_configuration_table
767 768 769 770 771 772 773 774 775 776 777
      (&acpi, grub_acpi_get_rsdpv1 ());
  }
#endif

  return GRUB_ERR_NONE;
}

static grub_extcmd_t cmd;

GRUB_MOD_INIT(acpi)
{
778
  cmd = grub_register_extcmd ("acpi", grub_cmd_acpi, 0,
779
			      N_("[-1|-2] [--exclude=TABLE1,TABLE2|"
780
			      "--load-only=TABLE1,TABLE2] FILE1"
781 782
			      " [FILE2] [...]"),
			      N_("Load host ACPI tables and tables "
783
			      "specified by arguments."),
784 785 786 787 788 789 790
			      options);
}

GRUB_MOD_FINI(acpi)
{
  grub_unregister_extcmd (cmd);
}