diff options
Diffstat (limited to 'roms/seabios/src/fw/dsdt_parser.c')
-rw-r--r-- | roms/seabios/src/fw/dsdt_parser.c | 673 |
1 files changed, 673 insertions, 0 deletions
diff --git a/roms/seabios/src/fw/dsdt_parser.c b/roms/seabios/src/fw/dsdt_parser.c new file mode 100644 index 000000000..eb5496f35 --- /dev/null +++ b/roms/seabios/src/fw/dsdt_parser.c @@ -0,0 +1,673 @@ +// Support for parsing dsdt acpi tables +// +// Copyright (C) 2008,2009 Kevin O'Connor <kevin@koconnor.net> +// +// 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); + } +} |