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 kraxel@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-leave@seabios.org