// Support for parsing dsdt acpi tables // // Copyright (C) 2008,2009 Kevin O'Connor // // This file may be distributed under the terms of the GNU LGPLv3 license. #include "config.h" // CONFIG_* #include "list.h" // hlist_* #include "malloc.h" // malloc_* #include "output.h" // dprintf #include "string.h" // memcpy #include "util.h" #include "std/acpi.h" // struct rsdp_descriptor /**************************************************************** * DSDT parser ****************************************************************/ struct acpi_device { struct hlist_node node; char name[16]; u8 *hid_aml; u8 *sta_aml; u8 *crs_data; int crs_size; }; static struct hlist_head acpi_devices VARVERIFY32INIT; static const int parse_dumpdevs = 0; struct parse_state { char name[32]; struct acpi_device *dev; int error; int depth; }; static void parse_termlist(struct parse_state *s, u8 *ptr, int offset, int pkglength); static void hex(const u8 *ptr, int count, int lvl, const char *item) { int l = 0, i; do { dprintf(lvl, "%s: %04x: ", item, l); for (i = l; i < l+16; i += 4) dprintf(lvl, "%02x %02x %02x %02x ", ptr[i+0], ptr[i+1], ptr[i+2], ptr[i+3]); for (i = l; i < l+16; i++) dprintf(lvl, "%c", (ptr[i] > 0x20 && ptr[i] < 0x80) ? ptr[i] : '.'); dprintf(lvl, "\n"); l += 16; } while (l < count); } static u64 parse_resource_int(u8 *ptr, int count) { u64 value = 0; int index = 0; for (index = 0; index < count; index++) value |= (u64)ptr[index] << (index * 8); return value; } static int parse_resource_bit(u8 *ptr, int count) { int bit; for (bit = 0; bit < count*8; bit++) if (ptr[bit/8] & (1 << (bit%8))) return bit; return 0; } static int parse_resource(u8 *ptr, int length, int *type, u64 *min, u64 *max) { int rname, rsize; u64 len; *type = -1; *min = 0; *max = 0; len = 0; if (!(ptr[0] & 0x80)) { /* small resource */ rname = (ptr[0] >> 3) & 0x0f; rsize = ptr[0] & 0x07; rsize++; switch (rname) { case 0x04: /* irq */ *min = parse_resource_bit(ptr + 1, rsize); *max = *min; *type = 3; break; case 0x0f: /* end marker */ return 0; case 0x08: /* io */ *min = parse_resource_int(ptr + 2, 2); *max = parse_resource_int(ptr + 4, 2); if (*min == *max) { *max = *min + ptr[7] - 1; *type = 1; } break; case 0x09: /* fixed io */ *min = parse_resource_int(ptr + 2, 2); *max = *min + ptr[4] - 1; *type = 1; break; default: dprintf(3, "%s: small: 0x%x (len %d)\n", __func__, rname, rsize); break; } } else { /* large resource */ rname = ptr[0] & 0x7f; rsize = ptr[2] << 8 | ptr[1]; rsize += 3; switch (rname) { case 0x06: /* 32-bit Fixed Location Memory Range Descriptor */ *min = parse_resource_int(ptr + 4, 4); len = parse_resource_int(ptr + 8, 4); *max = *min + len - 1; *type = 0; break; case 0x07: /* DWORD Address Space Descriptor */ *min = parse_resource_int(ptr + 10, 4); *max = parse_resource_int(ptr + 14, 4); *type = ptr[3]; break; case 0x08: /* WORD Address Space Descriptor */ *min = parse_resource_int(ptr + 8, 2); *max = parse_resource_int(ptr + 10, 2); *type = ptr[3]; break; case 0x09: /* irq */ *min = parse_resource_int(ptr + 5, 4); *max = *min; *type = 3; break; case 0x0a: /* QWORD Address Space Descriptor */ *min = parse_resource_int(ptr + 14, 8); *max = parse_resource_int(ptr + 22, 8); *type = ptr[3]; break; default: dprintf(3, "%s: large: 0x%x (len %d)\n", __func__, rname, rsize); break; } } return rsize; } static int find_resource(u8 *ptr, int len, int kind, u64 *min, u64 *max) { int type, size, offset = 0; do { size = parse_resource(ptr + offset, len - offset, &type, min, max); if (kind == type) return 0; offset += size; } while (size > 0 && offset < len); return -1; } static int print_resources(const char *prefix, u8 *ptr, int len) { static const char *typename[] = { "mem", "i/o", "bus" }; int type, size, offset = 0; u64 min, max; do { size = parse_resource(ptr + offset, len - offset, &type, &min, &max); switch (type) { case 0: case 1: case 2: dprintf(1, "%s%s 0x%llx -> 0x%llx\n", prefix, typename[type], min, max); break; case 3: dprintf(1, "%sirq %lld\n", prefix, min); break; } offset += size; } while (size > 0 && offset < len); return -1; } static int parse_nameseg(u8 *ptr, char **dst) { if (dst && *dst) { *(dst[0]++) = ptr[0]; if (ptr[1] != '_') *(dst[0]++) = ptr[1]; if (ptr[2] != '_') *(dst[0]++) = ptr[2]; if (ptr[3] != '_') *(dst[0]++) = ptr[3]; *(dst[0]) = 0; } return 4; } static int parse_namestring(struct parse_state *s, u8 *ptr, const char *item) { char *dst = s->name; int offset = 0; int i, count; for (;;) { switch (ptr[offset]) { case 0: /* null name */ offset++; *(dst++) = 0; break; case 0x2e: offset++; offset += parse_nameseg(ptr + offset, &dst); *(dst++) = '.'; offset += parse_nameseg(ptr + offset, &dst); break; case 0x2f: offset++; count = ptr[offset]; offset++; for (i = 0; i < count; i++) { if (i) *(dst++) = '.'; offset += parse_nameseg(ptr + offset, &dst); } break; case '\\': *(dst++) = '\\'; offset++; continue; case '^': *(dst++) = '^'; offset++; continue; case 'A' ... 'Z': case '_': offset += parse_nameseg(ptr, &dst); break; default: hex(ptr, 16, 3, __func__); s->error = 1; break; } break; } dprintf(5, "%s: %d %s '%s'\n", __func__, s->depth, item, s->name); return offset; } static int parse_termarg_int(u8 *ptr, int *error, u64 *dst) { u64 value; int offset = 1; switch (ptr[0]) { case 0x00: /* zero */ value = 0; break; case 0x01: /* one */ value = 1; break; case 0x0a: /* byte prefix */ value = ptr[1]; offset++; break; case 0x0b: /* word prefix */ value = ptr[1] | ((unsigned long)ptr[2] << 8); offset += 2; break; case 0x0c: /* dword prefix */ value = ptr[1] | ((unsigned long)ptr[2] << 8) | ((unsigned long)ptr[3] << 16) | ((unsigned long)ptr[4] << 24); offset += 4; break; default: value = 0; hex(ptr, 16, 3, __func__); if (error) *error = 1; break; } if (dst) *dst = value; dprintf(5, "%s: 0x%llx\n", __func__, value); return offset; } static int parse_pkglength(u8 *ptr, int *pkglength) { int offset = 2; *pkglength = 0; switch (ptr[0] >> 6) { case 3: *pkglength |= ptr[3] << 20; offset++; case 2: *pkglength |= ptr[2] << 12; offset++; case 1: *pkglength |= ptr[1] << 4; *pkglength |= ptr[0] & 0x0f; return offset; case 0: default: *pkglength |= ptr[0] & 0x3f; return 1; } } static int parse_pkg_common(struct parse_state *s, u8 *ptr, const char *item, int *pkglength) { int offset; offset = parse_pkglength(ptr, pkglength); offset += parse_namestring(s, ptr + offset, item); return offset; } static int parse_pkg_scope(struct parse_state *s, u8 *ptr) { int offset, pkglength; offset = parse_pkg_common(s, ptr, "scope", &pkglength); parse_termlist(s, ptr, offset, pkglength); return pkglength; } static int parse_pkg_device(struct parse_state *s, u8 *ptr) { int offset, pkglength; offset = parse_pkg_common(s, ptr, "device", &pkglength); s->dev = malloc_tmp(sizeof(struct acpi_device)); if (!s->dev) { warn_noalloc(); s->error = 1; return pkglength; } memset(s->dev, 0, sizeof(struct acpi_device)); hlist_add_head(&s->dev->node, &acpi_devices); strtcpy(s->dev->name, s->name, sizeof(s->name)); parse_termlist(s, ptr, offset, pkglength); s->dev = NULL; return pkglength; } static int parse_pkg_buffer(struct parse_state *s, u8 *ptr) { u64 blen; int pkglength, offset; offset = parse_pkglength(ptr, &pkglength); offset += parse_termarg_int(ptr + offset, &s->error, &blen); if (s->dev && strcmp(s->name, "_CRS") == 0) { s->dev->crs_data = ptr + offset; s->dev->crs_size = blen; } return pkglength; } static int parse_pkg_skip(struct parse_state *s, u8 *ptr, int op, int name) { int pkglength, offset; char item[8]; snprintf(item, sizeof(item), "op %x", op); offset = parse_pkglength(ptr, &pkglength); if (name) { parse_namestring(s, ptr + offset, item); } else { dprintf(5, "%s: %s (%d)\n", __func__, item, pkglength); } return pkglength; } static int parse_termobj(struct parse_state *s, u8 *ptr) { int offset = 1; if (s->depth == 16) { dprintf(1, "%s: deep recursion\n", __func__); s->error = 1; return offset; } s->depth++; switch (ptr[0]) { case 0x00: /* zero */ break; case 0x01: /* one */ break; case 0x08: /* name op */ offset += parse_namestring(s, ptr + offset, "name"); offset += parse_termobj(s, ptr + offset); if (s->dev && strcmp(s->name, "_HID") == 0) s->dev->hid_aml = ptr; if (s->dev && strcmp(s->name, "_STA") == 0) s->dev->sta_aml = ptr; break; case 0x0a: /* byte prefix */ offset++; break; case 0x0b: /* word prefix */ offset += 2; break; case 0x0c: /* dword prefix */ offset += 4; break; case 0x0d: /* string prefix */ while (ptr[offset]) offset++; offset++; break; case 0x10: /* scope op */ offset += parse_pkg_scope(s, ptr + offset); break; case 0x11: /* buffer op */ offset += parse_pkg_buffer(s, ptr + offset); break; case 0x12: /* package op */ case 0x13: /* var package op */ offset += parse_pkg_skip(s, ptr + offset, ptr[0], 0); break; case 0x14: /* method op */ offset += parse_pkg_skip(s, ptr + offset, ptr[0], 1); if (s->dev && strcmp(s->name, "_STA") == 0) s->dev->sta_aml = ptr; break; case 0x5b: /* ext op prefix */ offset++; switch (ptr[1]) { case 0x01: /* mutex op */ offset += parse_namestring(s, ptr + offset, "mutex"); offset++; /* sync flags */ break; case 0x80: /* op region op */ offset += parse_namestring(s, ptr + offset, "op region"); offset++; /* region space */ offset += parse_termarg_int(ptr + offset, &s->error, NULL); offset += parse_termarg_int(ptr + offset, &s->error, NULL); break; case 0x81: /* field op */ case 0x83: /* processor op */ case 0x84: /* power resource op */ case 0x85: /* thermal zone op */ offset += parse_pkg_skip(s, ptr + offset, 0x5b00 | ptr[1], 1); break; case 0x82: /* device op */ offset += parse_pkg_device(s, ptr + offset); break; default: hex(ptr, 16, 3, __func__); s->error = 1; break; } break; default: hex(ptr, 16, 3, __func__); s->error = 1; break; } s->depth--; return offset; } static void parse_termlist(struct parse_state *s, u8 *ptr, int offset, int pkglength) { for (;;) { offset += parse_termobj(s, ptr + offset); if (offset == pkglength) return; if (offset > pkglength) { dprintf(1, "%s: overrun: %d/%d\n", __func__, offset, pkglength); s->error = 1; return; } if (s->error) { dprintf(1, "%s: parse error, skip from %d/%d\n", __func__, offset, pkglength); s->error = 0; return; } } } static struct acpi_device *acpi_dsdt_find(struct acpi_device *prev, const u8 *aml1, int size1, const u8 *aml2, int size2) { struct acpi_device *dev; struct hlist_node *node; if (!prev) node = acpi_devices.first; else node = prev->node.next; for (; node != NULL; node = dev->node.next) { dev = container_of(node, struct acpi_device, node); if (!aml1 && !aml2) return dev; if (!dev->hid_aml) continue; if (aml1 && memcmp(dev->hid_aml + 5, aml1, size1) == 0) return dev; if (aml2 && memcmp(dev->hid_aml + 5, aml2, size2) == 0) return dev; } return NULL; } static int acpi_dsdt_present(struct acpi_device *dev) { if (!dev) return 0; /* no */ if (!dev->sta_aml) return 1; /* yes */ if (dev->sta_aml[0] == 0x14) return -1; /* unknown (can't evaluate method) */ if (dev->sta_aml[0] == 0x08) { u64 value = 0; parse_termarg_int(dev->sta_aml + 5, NULL, &value); if (value == 0) return 0; /* no */ else return 1; /* yes */ } return -1; /* unknown (should not happen) */ } /**************************************************************** * DSDT parser, public interface ****************************************************************/ struct acpi_device *acpi_dsdt_find_string(struct acpi_device *prev, const char *hid) { if (!CONFIG_ACPI_PARSE) return NULL; u8 aml[10]; int len = snprintf((char*)aml, sizeof(aml), "\x0d%s", hid); return acpi_dsdt_find(prev, aml, len, NULL, 0); } struct acpi_device *acpi_dsdt_find_eisaid(struct acpi_device *prev, u16 eisaid) { if (!CONFIG_ACPI_PARSE) return NULL; u8 aml1[] = { 0x0c, 0x41, 0xd0, eisaid >> 8, eisaid & 0xff }; u8 aml2[10]; int len2 = snprintf((char*)aml2, sizeof(aml2), "\x0dPNP%04X", eisaid); return acpi_dsdt_find(prev, aml1, 5, aml2, len2); } char *acpi_dsdt_name(struct acpi_device *dev) { if (!CONFIG_ACPI_PARSE || !dev) return NULL; return dev->name; } int acpi_dsdt_find_io(struct acpi_device *dev, u64 *min, u64 *max) { if (!CONFIG_ACPI_PARSE || !dev || !dev->crs_data) return -1; return find_resource(dev->crs_data, dev->crs_size, 1 /* I/O */, min, max); } int acpi_dsdt_find_mem(struct acpi_device *dev, u64 *min, u64 *max) { if (!CONFIG_ACPI_PARSE || !dev || !dev->crs_data) return -1; return find_resource(dev->crs_data, dev->crs_size, 0 /* mem */, min, max); } int acpi_dsdt_find_irq(struct acpi_device *dev, u64 *irq) { u64 max; if (!CONFIG_ACPI_PARSE || !dev || !dev->crs_data) return -1; return find_resource(dev->crs_data, dev->crs_size, 3 /* irq */, irq, &max); } int acpi_dsdt_present_eisaid(u16 eisaid) { if (!CONFIG_ACPI_PARSE) return -1; /* unknown */ if (hlist_empty(&acpi_devices)) return -1; /* unknown (no dsdt table) */ struct acpi_device *dev = acpi_dsdt_find_eisaid(NULL, eisaid); return acpi_dsdt_present(dev); } void acpi_dsdt_parse(void) { if (!CONFIG_ACPI_PARSE) return; struct fadt_descriptor_rev1 *fadt = find_acpi_table(FACP_SIGNATURE); if (!fadt) return; u8 *dsdt = (void*)(fadt->dsdt); if (!dsdt) return; u32 length = *(u32*)(dsdt + 4); u32 offset = 0x24; dprintf(1, "ACPI: parse DSDT at %p (len %d)\n", dsdt, length); struct parse_state s; memset(&s, 0, sizeof(s)); parse_termlist(&s, dsdt, offset, length); if (!parse_dumpdevs) return; struct acpi_device *dev; dprintf(1, "ACPI: dumping dsdt devices\n"); for (dev = acpi_dsdt_find(NULL, NULL, 0, NULL, 0); dev != NULL; dev = acpi_dsdt_find(dev, NULL, 0, NULL, 0)) { dprintf(1, " %s", acpi_dsdt_name(dev)); if (dev->hid_aml) dprintf(1, ", hid"); if (dev->sta_aml) dprintf(1, ", sta (0x%x)", dev->sta_aml[0]); if (dev->crs_data) dprintf(1, ", crs"); dprintf(1, "\n"); if (dev->crs_data) print_resources(" ", dev->crs_data, dev->crs_size); } }