On Fri, Apr 03, 2020 at 10:31:19AM +0200, Gerd Hoffmann wrote: > Create a list of devices found in the DSDT table. Add helper functions > to find devices, walk the list and figure device informations like mmio > ranges and irqs. > > Signed-off-by: Gerd Hoffmann <kra...@redhat.com> > --- > src/util.h | 10 + > src/fw/biostables.c | 622 ++++++++++++++++++++++++++++++++++++++++++++ > src/post.c | 2 + > src/Kconfig | 7 + > 4 files changed, 641 insertions(+) > > diff --git a/src/util.h b/src/util.h > index 4f27fc307439..8ee0370492b8 100644 > --- a/src/util.h > +++ b/src/util.h > @@ -94,6 +94,16 @@ void display_uuid(void); > void copy_table(void *pos); > void smbios_setup(void); > > +struct acpi_device; > +void acpi_dsdt_parse(void); > +struct acpi_device *acpi_dsdt_find_string(struct acpi_device *prev, const > char *hid); > +struct acpi_device *acpi_dsdt_find_eisaid(struct acpi_device *prev, u16 > eisaid); > +char *acpi_dsdt_name(struct acpi_device *dev); > +int acpi_dsdt_present_eisaid(u16 eisaid); > +int acpi_dsdt_find_io(struct acpi_device *dev, u64 *min, u64 *max); > +int acpi_dsdt_find_mem(struct acpi_device *dev, u64 *min, u64 *max); > +int acpi_dsdt_find_irq(struct acpi_device *dev, u64 *irq); > + > // fw/coreboot.c > extern const char *CBvendor, *CBpart; > struct cbfs_file; > diff --git a/src/fw/biostables.c b/src/fw/biostables.c > index 0d4fdb9c22e8..ebe1c90fca5e 100644 > --- a/src/fw/biostables.c > +++ b/src/fw/biostables.c > @@ -17,6 +17,7 @@ > #include "std/smbios.h" // struct smbios_entry_point > #include "string.h" // memcpy > #include "util.h" // copy_table > +#include "list.h" // hlist_* > #include "x86.h" // outb > > struct pir_header *PirAddr VARFSEG; > @@ -509,3 +510,624 @@ copy_table(void *pos) > copy_acpi_rsdp(pos); > copy_smbios(pos); > } > + > +/**************************************************************** > + * DSDT parser > + ****************************************************************/
I think this code is sufficiently large to demand it's own C file - for example src/fw/dsdt_parser.c . > + > +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; It would be good to add VARVERIFY32INIT to this global. > + > +static int parse_error = 0; > +static int parse_dumptree = 0; > +static char parse_name[32]; > +static struct acpi_device *parse_dev; I think it would be preferable to not use global variables for temporary state. I think the above could be moved into a new "struct dsdt_parsing_state" and passed between functions. (I suspect the "u8 *ptr" could be moved into that struct as well.) > + > +static void parse_termlist(u8 *ptr, int offset, int pkglength); I'm a little concerned about the unbounded recursion in this parsing code. The main SeaBIOS execution stack is pretty large, but nothing stops the dsdt table from doing something goofy. I think a sanity check on recursion depth may be worthwhile. > + > +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(u8 *ptr, const char *item) > +{ > + char *dst = parse_name; > + int offset = 0; > + int i, count; > + > +again: > + 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++; > + goto again; > + case '^': > + *(dst++) = '^'; > + offset++; > + goto again; I think this code would be more clear if it used "for (;;) {" and "continue" instead of a backwards goto. > + case 'A' ... 'Z': > + case '_': > + offset += parse_nameseg(ptr, &dst); > + break; > + default: > + hex(ptr, 16, 3, __func__); > + parse_error = 1; > + break; > + } > + dprintf(5, "%s: %s '%s'\n", __func__, item, parse_name); > + return offset; > +} > + > +static int parse_termarg_int(u8 *ptr, 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__); > + parse_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(u8 *ptr, const char *item, int *pkglength) > +{ > + int offset; > + > + offset = parse_pkglength(ptr, pkglength); > + offset += parse_namestring(ptr + offset, item); > + return offset; > +} > + > +static int parse_pkg_scope(u8 *ptr) > +{ > + int offset, pkglength; > + > + offset = parse_pkg_common(ptr, "skope", &pkglength); skope? > + parse_termlist(ptr, offset, pkglength); > + return pkglength; > +} > + > +static int parse_pkg_device(u8 *ptr) > +{ > + int offset, pkglength; > + > + offset = parse_pkg_common(ptr, "device", &pkglength); > + > + parse_dev = malloc_high(sizeof(*parse_dev)); Shouldn't this be malloc_tmp() ? > + if (!parse_dev) { > + warn_noalloc(); > + parse_error = 1; > + return pkglength; > + } > + > + memset(parse_dev, 0, sizeof(*parse_dev)); > + hlist_add_head(&parse_dev->node, &acpi_devices); > + strtcpy(parse_dev->name, parse_name, sizeof(parse_dev->name)); > + > + parse_termlist(ptr, offset, pkglength); > + return pkglength; > +} > + > +static int parse_pkg_buffer(u8 *ptr) > +{ > + u64 blen; > + int pkglength, offset; > + > + offset = parse_pkglength(ptr, &pkglength); > + offset += parse_termarg_int(ptr + offset, &blen); > + if (strcmp(parse_name, "_CRS") == 0) { > + parse_dev->crs_data = ptr + offset; > + parse_dev->crs_size = blen; > + } > + return pkglength; > +} > + > +static int parse_pkg_skip(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(ptr + offset, item); > + } else { > + dprintf(5, "%s: %s (%d)\n", __func__, item, pkglength); > + } > + return pkglength; > +} > + > +static int parse_termobj(u8 *ptr) > +{ > + int offset = 1; > + > + switch (ptr[0]) { > + case 0x00: /* zero */ > + break; > + case 0x01: /* one */ > + break; > + case 0x08: /* name op */ > + offset += parse_namestring(ptr + offset, "name"); > + offset += parse_termobj(ptr + offset); > + if (strcmp(parse_name, "_HID") == 0) > + parse_dev->hid_aml = ptr; > + if (strcmp(parse_name, "_STA") == 0) > + parse_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(ptr + offset); > + break; > + case 0x11: /* buffer op */ > + offset += parse_pkg_buffer(ptr + offset); > + break; > + case 0x12: /* package op */ > + case 0x13: /* var package op */ > + offset += parse_pkg_skip(ptr + offset, ptr[0], 0); > + break; > + case 0x14: /* method op */ > + offset += parse_pkg_skip(ptr + offset, ptr[0], 1); > + if (strcmp(parse_name, "_STA") == 0) > + parse_dev->sta_aml = ptr; > + break; > + case 0x5b: /* ext op prefix */ > + offset++; > + switch (ptr[1]) { > + case 0x01: /* mutex op */ > + offset += parse_namestring(ptr + offset, "mutex"); > + offset++; /* sync flags */ > + break; > + case 0x80: /* op region op */ > + offset += parse_namestring(ptr + offset, "op region"); > + offset++; /* region space */ > + offset += parse_termarg_int(ptr + offset, NULL); > + offset += parse_termarg_int(ptr + offset, NULL); > + break; > + case 0x81: /* field op */ > + case 0x83: /* processor op */ > + case 0x84: /* power resource op */ > + case 0x85: /* thermal zone op */ > + offset += parse_pkg_skip(ptr + offset, 0x5b00 | ptr[1], 1); > + break; > + case 0x82: /* device op */ > + offset += parse_pkg_device(ptr + offset); > + break; > + default: > + hex(ptr, 16, 3, __func__); > + parse_error = 1; > + break; > + } > + break; > + default: > + hex(ptr, 16, 3, __func__); > + parse_error = 1; > + break; > + } > + > + return offset; > +} > + > +static void parse_termlist(u8 *ptr, int offset, int pkglength) > +{ > + for (;;) { > + offset += parse_termobj(ptr + offset); > + if (offset == pkglength) > + return; > + if (offset > pkglength) { > + dprintf(1, "%s: overrun: %d/%d\n", __func__, > + offset, pkglength); > + parse_error = 1; > + return; > + } > + if (parse_error) { > + dprintf(1, "%s: parse error, skip from %d/%d\n", __func__, > + offset, pkglength); > + parse_error = 0; > + return; > + } > + } > +} > + > +static struct acpi_device *acpi_dsdt_find(struct acpi_device *prev, const u8 > *aml, int size) This code should be wrapped to 80 characters. (I know there's a bunch of places where I goofed at this in the past, but I think going forward we should try to keep to 80 characters.) > +{ > + 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 (!aml) > + return dev; > + if (!dev->hid_aml) > + continue; > + if (memcmp(dev->hid_aml + 5, aml, size) == 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, &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); > +} > + > +struct acpi_device *acpi_dsdt_find_eisaid(struct acpi_device *prev, u16 > eisaid) > +{ > + if (!CONFIG_ACPI_PARSE) > + return NULL; > + u8 aml[] = { > + 0x0c, 0x41, 0xd0, > + eisaid >> 8, > + eisaid & 0xff > + }; > + return acpi_dsdt_find(prev, aml, 5); > +} > + > +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 */ > + > + 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); > + parse_termlist(dsdt, offset, length); > + > + if (parse_dumptree) { > + struct acpi_device *dev; > + dprintf(1, "ACPI: dumping dsdt devices\n"); > + for (dev = acpi_dsdt_find(NULL, NULL, 0); > + dev != NULL; > + dev = acpi_dsdt_find(dev, 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); > + } > + } > +} > diff --git a/src/post.c b/src/post.c > index f93106a1c9c9..febdc0859764 100644 > --- a/src/post.c > +++ b/src/post.c > @@ -149,6 +149,8 @@ platform_hardware_setup(void) > qemu_platform_setup(); > coreboot_platform_setup(); > > + acpi_dsdt_parse(); Instead of adding this to post.c, could we add the call to find_acpi_features()? (And arrange for qemu_platform_setup() to call find_acpi_features() or directly call acpi_dsdt_parse().) > + > // Setup timers and periodic clock interrupt > timer_setup(); > clock_setup(); > diff --git a/src/Kconfig b/src/Kconfig > index 6606ce4d46c9..ab36e676bf12 100644 > --- a/src/Kconfig > +++ b/src/Kconfig > @@ -524,6 +524,13 @@ menu "BIOS Tables" > This option can be disabled for QEMU 1.6 and older > to save some space in the ROM file. > If unsure, say Y. > + config ACPI_PARSE > + bool "Include ACPI DSDT parser." > + default n > + help > + Support parsing ACPI DSDT for device probing. > + Needed to find virtio-mmio devices. > + If unsure, say N. If we're going to add a dsdt parser then I think it should default to enabled. -Kevin > endmenu > > source vgasrc/Kconfig > -- > 2.18.2 > _______________________________________________ > SeaBIOS mailing list -- seabios@seabios.org > To unsubscribe send an email to seabios-le...@seabios.org _______________________________________________ SeaBIOS mailing list -- seabios@seabios.org To unsubscribe send an email to seabios-le...@seabios.org