acpihalt.c 10.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/*
 *  GRUB  --  GRand Unified Bootloader
 *  Copyright (C) 2010  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/>.
 */

19 20 21 22 23 24
#ifdef GRUB_DSDT_TEST
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
25
#include <errno.h>
26 27 28

#define grub_dprintf(cond, args...) printf ( args )
#define grub_printf printf
29 30
#define grub_util_fopen fopen
#define grub_memcmp memcmp
31 32 33 34 35 36 37
typedef uint64_t grub_uint64_t;
typedef uint32_t grub_uint32_t;
typedef uint16_t grub_uint16_t;
typedef uint8_t grub_uint8_t;

#endif

38
#include <grub/acpi.h>
39
#ifndef GRUB_DSDT_TEST
40
#include <grub/i18n.h>
41 42 43 44
#else
#define _(x) x
#define N_(x) x
#endif
45 46

#ifndef GRUB_DSDT_TEST
47
#include <grub/mm.h>
48
#include <grub/misc.h>
49
#include <grub/time.h>
50
#include <grub/cpu/io.h>
51
#endif
52 53 54 55 56 57 58 59 60 61 62 63 64 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

static inline grub_uint32_t
decode_length (const grub_uint8_t *ptr, int *numlen)
{
  int num_bytes, i;
  grub_uint32_t ret;
  if (*ptr < 64)
    {
      if (numlen)
	*numlen = 1;
      return *ptr;
    }
  num_bytes = *ptr >> 6;
  if (numlen)
    *numlen = num_bytes + 1;
  ret = *ptr & 0xf;
  ptr++;
  for (i = 0; i < num_bytes; i++)
    {
      ret |= *ptr << (8 * i + 4);
      ptr++;
    }
  return ret;
}

static inline grub_uint32_t
skip_name_string (const grub_uint8_t *ptr, const grub_uint8_t *end)
{
  const grub_uint8_t *ptr0 = ptr;
  
  while (ptr < end && (*ptr == '^' || *ptr == '\\'))
    ptr++;
  switch (*ptr)
    {
    case '.':
      ptr++;
      ptr += 8;
      break;
    case '/':
      ptr++;
      ptr += 1 + (*ptr) * 4;
      break;
    case 0:
      ptr++;
      break;
    default:
      ptr += 4;
      break;
    }
  return ptr - ptr0;
}

static inline grub_uint32_t
skip_data_ref_object (const grub_uint8_t *ptr, const grub_uint8_t *end)
{
  grub_dprintf ("acpi", "data type = 0x%x\n", *ptr);
  switch (*ptr)
    {
    case GRUB_ACPI_OPCODE_PACKAGE:
111
    case GRUB_ACPI_OPCODE_BUFFER:
112 113 114 115 116 117 118 119 120 121 122
      return 1 + decode_length (ptr + 1, 0);
    case GRUB_ACPI_OPCODE_ZERO:
    case GRUB_ACPI_OPCODE_ONES:
    case GRUB_ACPI_OPCODE_ONE:
      return 1;
    case GRUB_ACPI_OPCODE_BYTE_CONST:
      return 2;
    case GRUB_ACPI_OPCODE_WORD_CONST:
      return 3;
    case GRUB_ACPI_OPCODE_DWORD_CONST:
      return 5;
123 124 125 126 127 128 129 130
    case GRUB_ACPI_OPCODE_STRING_CONST:
      {
	const grub_uint8_t *ptr0 = ptr;
	for (ptr++; ptr < end && *ptr; ptr++);
	if (ptr == end)
	  return 0;
	return ptr - ptr0 + 1;
      }
131 132 133 134 135 136 137 138 139
    default:
      if (*ptr == '^' || *ptr == '\\' || *ptr == '_'
	  || (*ptr >= 'A' && *ptr <= 'Z'))
	return skip_name_string (ptr, end);
      grub_printf ("Unknown opcode 0x%x\n", *ptr);
      return 0;
    }
}

140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
static inline grub_uint32_t
skip_term (const grub_uint8_t *ptr, const grub_uint8_t *end)
{
  grub_uint32_t add;
  const grub_uint8_t *ptr0 = ptr;

  switch(*ptr)
  {
    case GRUB_ACPI_OPCODE_ADD:
    case GRUB_ACPI_OPCODE_AND:
    case GRUB_ACPI_OPCODE_CONCAT:
    case GRUB_ACPI_OPCODE_CONCATRES:
    case GRUB_ACPI_OPCODE_DIVIDE:
    case GRUB_ACPI_OPCODE_INDEX:
    case GRUB_ACPI_OPCODE_LSHIFT:
    case GRUB_ACPI_OPCODE_MOD:
    case GRUB_ACPI_OPCODE_MULTIPLY:
    case GRUB_ACPI_OPCODE_NAND:
    case GRUB_ACPI_OPCODE_NOR:
    case GRUB_ACPI_OPCODE_OR:
    case GRUB_ACPI_OPCODE_RSHIFT:
    case GRUB_ACPI_OPCODE_SUBTRACT:
    case GRUB_ACPI_OPCODE_TOSTRING:
    case GRUB_ACPI_OPCODE_XOR:
      /*
       * Parameters for these opcodes: TermArg, TermArg Target, see ACPI
       * spec r5.0, page 828f.
       */
      ptr++;
      ptr += add = skip_term (ptr, end);
      if (!add)
        return 0;
      ptr += add = skip_term (ptr, end);
      if (!add)
        return 0;
      ptr += skip_name_string (ptr, end);
      break;
    default:
      return skip_data_ref_object (ptr, end);
  }
  return ptr - ptr0;
}

183 184 185 186 187 188 189 190 191 192 193 194 195
static inline grub_uint32_t
skip_ext_op (const grub_uint8_t *ptr, const grub_uint8_t *end)
{
  const grub_uint8_t *ptr0 = ptr;
  int add;
  grub_dprintf ("acpi", "Extended opcode: 0x%x\n", *ptr);
  switch (*ptr)
    {
    case GRUB_ACPI_EXTOPCODE_MUTEX:
      ptr++;
      ptr += skip_name_string (ptr, end);
      ptr++;
      break;
196 197 198 199
    case GRUB_ACPI_EXTOPCODE_EVENT_OP:
      ptr++;
      ptr += skip_name_string (ptr, end);
      break;
200 201 202 203
    case GRUB_ACPI_EXTOPCODE_OPERATION_REGION:
      ptr++;
      ptr += skip_name_string (ptr, end);
      ptr++;
204
      ptr += add = skip_term (ptr, end);
205 206
      if (!add)
	return 0;
207
      ptr += add = skip_term (ptr, end);
208 209 210 211
      if (!add)
	return 0;
      break;
    case GRUB_ACPI_EXTOPCODE_FIELD_OP:
212 213 214 215
    case GRUB_ACPI_EXTOPCODE_DEVICE_OP:
    case GRUB_ACPI_EXTOPCODE_PROCESSOR_OP:
    case GRUB_ACPI_EXTOPCODE_POWER_RES_OP:
    case GRUB_ACPI_EXTOPCODE_THERMAL_ZONE_OP:
216
    case GRUB_ACPI_EXTOPCODE_INDEX_FIELD_OP:
217
    case GRUB_ACPI_EXTOPCODE_BANK_FIELD_OP:
218 219 220 221 222 223 224 225 226 227
      ptr++;
      ptr += decode_length (ptr, 0);
      break;
    default:
      grub_printf ("Unexpected extended opcode: 0x%x\n", *ptr);
      return 0;
    }
  return ptr - ptr0;
}

228

229
static int
230 231
get_sleep_type (grub_uint8_t *table, grub_uint8_t *ptr, grub_uint8_t *end,
		grub_uint8_t *scope, int scope_len)
232
{
233
  grub_uint8_t *prev = table;
234
  
235 236
  if (!ptr)
    ptr = table + sizeof (struct grub_acpi_table_header);
237 238 239 240
  while (ptr < end && prev < ptr)
    {
      int add;
      prev = ptr;
241 242
      grub_dprintf ("acpi", "Opcode 0x%x\n", *ptr);
      grub_dprintf ("acpi", "Tell %x\n", (unsigned) (ptr - table));
243 244 245 246 247 248 249 250
      switch (*ptr)
	{
	case GRUB_ACPI_OPCODE_EXTOP:
	  ptr++;
	  ptr += add = skip_ext_op (ptr, end);
	  if (!add)
	    return -1;
	  break;
251
	case GRUB_ACPI_OPCODE_CREATE_DWORD_FIELD:
252 253 254 255 256 257 258 259 260 261
	case GRUB_ACPI_OPCODE_CREATE_WORD_FIELD:
	case GRUB_ACPI_OPCODE_CREATE_BYTE_FIELD:
	  {
	    ptr += 5;
	    ptr += add = skip_data_ref_object (ptr, end);
	    if (!add)
	      return -1;
	    ptr += 4;
	    break;
	  }
262 263
	case GRUB_ACPI_OPCODE_NAME:
	  ptr++;
264 265
	  if ((!scope || grub_memcmp (scope, "\\", scope_len) == 0) &&
	      (grub_memcmp (ptr, "_S5_", 4) == 0 || grub_memcmp (ptr, "\\_S5_", 4) == 0))
266 267 268
	    {
	      int ll;
	      grub_uint8_t *ptr2 = ptr;
269 270
	      grub_dprintf ("acpi", "S5 found\n");
	      ptr2 += skip_name_string (ptr, end);
271 272 273 274 275 276 277 278 279 280 281 282
	      if (*ptr2 != 0x12)
		{
		  grub_printf ("Unknown opcode in _S5: 0x%x\n", *ptr2);
		  return -1;
		}
	      ptr2++;
	      decode_length (ptr2, &ll);
	      ptr2 += ll;
	      ptr2++;
	      switch (*ptr2)
		{
		case GRUB_ACPI_OPCODE_ZERO:
283
		  return 0;
284
		case GRUB_ACPI_OPCODE_ONE:
285
		  return 1;
286
		case GRUB_ACPI_OPCODE_BYTE_CONST:
287
		  return ptr2[1];
288 289 290 291 292 293 294 295 296 297 298 299
		default:
		  grub_printf ("Unknown data type in _S5: 0x%x\n", *ptr2);
		  return -1;
		}
	    }
	  ptr += add = skip_name_string (ptr, end);
	  if (!add)
	    return -1;
	  ptr += add = skip_data_ref_object (ptr, end);
	  if (!add)
	    return -1;
	  break;
300 301 302 303 304 305 306 307 308 309 310
	case GRUB_ACPI_OPCODE_ALIAS:
	  ptr++;
	  /* We need to skip two name strings */
	  ptr += add = skip_name_string (ptr, end);
	  if (!add)
	    return -1;
	  ptr += add = skip_name_string (ptr, end);
	  if (!add)
	    return -1;
	  break;

311
	case GRUB_ACPI_OPCODE_SCOPE:
312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
	  {
	    int scope_sleep_type;
	    int ll;
	    grub_uint8_t *name;
	    int name_len;

	    ptr++;
	    add = decode_length (ptr, &ll);
	    name = ptr + ll;
	    name_len = skip_name_string (name, ptr + add);
	    if (!name_len)
	      return -1;
	    scope_sleep_type = get_sleep_type (table, name + name_len,
					       ptr + add, name, name_len);
	    if (scope_sleep_type != -2)
	      return scope_sleep_type;
	    ptr += add;
	    break;
	  }
331 332 333 334 335 336 337
	case GRUB_ACPI_OPCODE_IF:
	case GRUB_ACPI_OPCODE_METHOD:
	  {
	    ptr++;
	    ptr += decode_length (ptr, 0);
	    break;
	  }
338 339 340
	default:
	  grub_printf ("Unknown opcode 0x%x\n", *ptr);
	  return -1;	  
341 342 343
	}
    }

344
  return -2;
345 346
}

347 348 349 350 351 352 353 354 355
#ifdef GRUB_DSDT_TEST
int
main (int argc, char **argv)
{
  FILE *f;
  size_t len;
  unsigned char *buf;
  if (argc < 2)
    printf ("Usage: %s FILE\n", argv[0]);
356
  f = grub_util_fopen (argv[1], "rb");
357 358 359 360 361 362 363 364 365 366 367
  if (!f)
    {
      printf ("Couldn't open file\n");
      return 1;
    }
  fseek (f, 0, SEEK_END);
  len = ftell (f);
  fseek (f, 0, SEEK_SET);
  buf = malloc (len);
  if (!buf)
    {
368
      printf (_("error: %s.\n"), _("out of memory"));
369 370 371 372 373
      fclose (f);
      return 2;
    }
  if (fread (buf, 1, len, f) != len)
    {
374
      printf (_("cannot read `%s': %s"), argv[1], strerror (errno));
375 376 377 378 379
      free (buf);
      fclose (f);
      return 2;
    }

380
  printf ("Sleep type = %d\n", get_sleep_type (buf, NULL, buf + len, NULL, 0));
381 382 383 384 385 386 387
  free (buf);
  fclose (f);
  return 0;
}

#else

388 389 390 391 392
void
grub_acpi_halt (void)
{
  struct grub_acpi_rsdp_v20 *rsdp2;
  struct grub_acpi_rsdp_v10 *rsdp1;
393 394 395 396
  struct grub_acpi_table_header *rsdt;
  grub_uint32_t *entry_ptr;
  grub_uint32_t port = 0;
  int sleep_type = -1;
397 398 399 400 401 402 403 404 405 406

  rsdp2 = grub_acpi_get_rsdpv2 ();
  if (rsdp2)
    rsdp1 = &(rsdp2->rsdpv1);
  else
    rsdp1 = grub_acpi_get_rsdpv1 ();
  grub_dprintf ("acpi", "rsdp1=%p\n", rsdp1);
  if (!rsdp1)
    return;

407
  rsdt = (struct grub_acpi_table_header *) (grub_addr_t) rsdp1->rsdt_addr;
408 409 410 411 412
  for (entry_ptr = (grub_uint32_t *) (rsdt + 1);
       entry_ptr < (grub_uint32_t *) (((grub_uint8_t *) rsdt)
				      + rsdt->length);
       entry_ptr++)
    {
413
      if (grub_memcmp ((void *) (grub_addr_t) *entry_ptr, "FACP", 4) == 0)
414 415
	{
	  struct grub_acpi_fadt *fadt
416
	    = ((struct grub_acpi_fadt *) (grub_addr_t) *entry_ptr);
417
	  struct grub_acpi_table_header *dsdt
418
	    = (struct grub_acpi_table_header *) (grub_addr_t) fadt->dsdt_addr;
419
	  grub_uint8_t *buf = (grub_uint8_t *) dsdt;
420 421 422 423 424 425

	  port = fadt->pm1a;

	  grub_dprintf ("acpi", "PM1a port=%x\n", port);

	  if (grub_memcmp (dsdt->signature, "DSDT",
426 427
			   sizeof (dsdt->signature)) == 0
	      && sleep_type < 0)
428 429 430
	    sleep_type = get_sleep_type (buf, NULL, buf + dsdt->length,
					 NULL, 0);
	}
431 432
      else if (grub_memcmp ((void *) (grub_addr_t) *entry_ptr, "SSDT", 4) == 0
	       && sleep_type < 0)
433 434 435 436
	{
	  struct grub_acpi_table_header *ssdt
	    = (struct grub_acpi_table_header *) (grub_addr_t) *entry_ptr;
	  grub_uint8_t *buf = (grub_uint8_t *) ssdt;
437

438
	  grub_dprintf ("acpi", "SSDT = %p\n", ssdt);
439

440 441 442
	  sleep_type = get_sleep_type (buf, NULL, buf + ssdt->length, NULL, 0);
	}
    }
443

444
  grub_dprintf ("acpi", "SLP_TYP = %d, port = 0x%x\n", sleep_type, port);
445
  if (port && sleep_type >= 0 && sleep_type < 8)
446 447
    grub_outw (GRUB_ACPI_SLP_EN | (sleep_type << GRUB_ACPI_SLP_TYP_OFFSET),
	       port & 0xffff);
448

449 450
  grub_millisleep (1500);

451
  /* TRANSLATORS: It's computer shutdown using ACPI, not disabling ACPI.  */
452
  grub_puts_ (N_("ACPI shutdown failed"));
453
}
454
#endif