diff --git a/libc/irq/acpi-fadt-init.S b/libc/irq/acpi-fadt-init.S index 7894a02f99f4..1eca1dbd0fc9 100644 --- a/libc/irq/acpi-fadt-init.S +++ b/libc/irq/acpi-fadt-init.S @@ -41,3 +41,8 @@ _AcpiBootFlags: .short kAcpiFadtLegacyDevices | kAcpiFadt8042 .endobj _AcpiBootFlags,globl .previous + .bss +_AcpiDefDevices: + .skip 8 + .endobj _AcpiDefDevices,globl + .previous diff --git a/libc/irq/acpi-fadt.c b/libc/irq/acpi-fadt.c index 91ca3c07355f..840ad6178961 100644 --- a/libc/irq/acpi-fadt.c +++ b/libc/irq/acpi-fadt.c @@ -25,14 +25,35 @@ │ OTHER DEALINGS IN THE SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/dce.h" +#include "libc/inttypes.h" +#include "libc/intrin/bswap.h" #include "libc/irq/acpi.internal.h" #include "libc/str/str.h" #ifdef __x86_64__ +/* + * Try to semi-correctly parse the ACPI Machine Language (AML) present in + * the Differentiated System Description Table (DSDT), in order to get an + * idea of what the system's hardware configuration is. + * + * (Parsing AML with anywhere close to 100% correctness — à la Intel's + * ACPICA — requires us to keep track of a _lot_ of information at parse + * time, & does not look quite necessary at the moment. We will see.) + * + * The logic for partially parsing AML is largely based on that of the + * SeaBIOS project, with some additions to handle AML features encountered + * on actual firmwares. + * + * @see libc/irq/acpi.internal.h + * @see src/fw/dsdt_parser.c in SeaBIOS, https://www.seabios.org/Download + */ + typedef struct { /* lowest-level NameSeg of the most recent NameString/NamePath */ uint8_t name_seg[4]; + /* device structure for current Device Package, or NULL if none */ + AcpiDefDevice *dev; } AmlParseState; textstartup static void _AcpiCheckParseOverrun(const uint8_t *nxt, @@ -111,6 +132,7 @@ textstartup static const uint8_t *_AcpiParsePkgLength(const uint8_t *aml, uint8_t lead = aml[0]; uint32_t pl = 0; const uint8_t *nxt = aml + 1 + (lead >> 6); + _AcpiCheckParseOverrun(nxt, aml_end); switch (lead >> 6) { default: pl = (uint32_t)aml[3] << 20; /* FALLTHRU */ @@ -171,12 +193,145 @@ textstartup static const uint8_t *_AcpiParseTermArg(AmlParseState *parse, nxt = _AcpiParseNameString(parse, nxt, aml_end); return nxt; default: - ACPI_FATAL("AML: unknown TermArg type %#x @ %p", (unsigned)aml[0], aml); + ACPI_FATAL("AML: weird TermArg type %#x @ %p", (unsigned)aml[0], aml); } } +textstartup static const uint8_t *_AcpiParseTermArgInt(AmlParseState *parse, + const uint8_t *aml, + const uint8_t *aml_end, + uint64_t *value) { + const uint8_t *nxt = aml + 1; + uint64_t v = 0; + _AcpiCheckParseOverrun(nxt, aml_end); + switch (aml[0]) { + case kAmlZeroOp: + break; + case kAmlOneOp: + v = 1; + break; + case kAmlOnesOp: + --v; + break; + case kAmlByteOp: + v = *nxt++; + break; + case kAmlWordOp: + v = READ16LE(nxt); + nxt += 2; + break; + case kAmlDwordOp: + v = READ32LE(nxt); + nxt += 4; + break; + case kAmlQwordOp: + v = READ64LE(nxt); + nxt += 8; + break; + default: + ACPI_FATAL("AML: weird TermArg => Integer type %#x @ %p", + (unsigned)aml[0], aml); + } + *value = v; + return nxt; +} + static const uint8_t *_AcpiParseTermList(AmlParseState *, const uint8_t *, const uint8_t *); +static const uint8_t *_AcpiParseTermObj(AmlParseState *, + const uint8_t *, const uint8_t *); + +textstartup static const uint8_t * +_AcpiParseDataObjectHid(AmlParseState *state, const uint8_t *aml, + const uint8_t *aml_end, AcpiDeviceHid *hid) { + const uint8_t *nxt = aml + 1, *end; + uint64_t value; + uint32_t evalu; + AcpiDeviceHid id; + switch (aml[0]) { + case kAmlStringOp: + end = nxt; + while (*end) ++end; + *hid = id = _AcpiCompressHid(nxt, end - nxt); + ACPI_INFO("AML: dev _HID \"%!s\"%s", nxt, id ? "" : " (!)"); + return end + 1; + case kAmlZeroOp: + case kAmlOneOp: + case kAmlOnesOp: + case kAmlByteOp: + case kAmlWordOp: + case kAmlDwordOp: + case kAmlQwordOp: + nxt = _AcpiParseTermArgInt(state, aml, aml_end, &value); + if (!IsTiny()) { + evalu = bswap_32((uint32_t)value); + unsigned a0 = (evalu >> 16) & 0x1F; + unsigned a1 = (evalu >> 21) & 0x1F; + unsigned a2 = (evalu >> 26) & 0x1F; + if (value >= (1ul << 31) || + !a0 || a0 > 26 || !a1 || a1 > 26 || !a2 || a2 > 26) { + ACPI_INFO("AML: dev _HID %#" PRIx64 " (!)", value); + } else { + ACPI_INFO("AML: dev _HID EISA %c%c%c%04X", + a0 - 1 + 'A', a1 - 1 + 'A', a2 - 1 + 'A', + (unsigned)(evalu & 0xFFFF)); + } + } + *hid = value; + return nxt; + default: + ACPI_WARN("AML: weird _HID type %#x @ %p", (unsigned)aml[0], aml); + *hid = 0; + return _AcpiParseTermObj(state, aml, aml_end); + } +} + +textstartup static const uint8_t * +_AcpiParseDataObjectCrs(AmlParseState *parse, const uint8_t *aml, + const uint8_t *aml_end, const uint8_t **crs, + size_t *crs_size) { + const uint8_t *nxt = aml + 1, *initer_end; + uint8_t *buf; + uint32_t pl; + uint64_t buf_size; + size_t initer_size; + switch (aml[0]) { + case kAmlBufferOp: + nxt = _AcpiParsePkgLength(nxt, aml_end, &pl); + initer_end = aml + 1 + pl; + nxt = _AcpiParseTermArgInt(parse, nxt, initer_end, &buf_size); + _AcpiCheckParseOverrun(nxt, initer_end); + /* + * The ACPI 6.5 spec § 19.6.10 says about Buffer objects, + * "If the BufferSize is larger than the length of the Initializer, + * the BufferSize is used as the final buffer size. At runtime, the + * AML interpreter will automatically pad zeros to the Initializer to + * match the BufferSize: ... + * "If the BufferSize is smaller than the length of the Initializer, + * the length of the Initializer is used as the buffer size: ..." + */ + initer_size = initer_end - nxt; + ACPI_INFO("AML: dev _CRS @ %p,+%#zx", nxt, initer_size); + if (buf_size <= initer_size) { + buf = (uint8_t *)nxt; + buf_size = initer_size; + } else { + buf = _AcpiOsAllocate(buf_size); + if (!buf) ACPI_FATAL("AML: no memory for _CRS"); + memcpy(buf, nxt, initer_size); + memset(buf + initer_size, 0, buf_size - initer_size); + ACPI_INFO("AML: -> %p,+%#zx", buf, (size_t)buf_size); + } + *crs = buf; + *crs_size = buf_size; + return initer_end; + default: + ACPI_WARN("AML: weird _CRS type %#x @ %p", (unsigned)aml[0], aml); + *crs = NULL; + *crs_size = 0; + return _AcpiParseTermObj(parse, aml, aml_end); + } +} textstartup static const uint8_t *_AcpiParseTermObj(AmlParseState *parse, const uint8_t *aml, @@ -195,7 +350,15 @@ textstartup static const uint8_t *_AcpiParseTermObj(AmlParseState *parse, return nxt; case kAmlNameOp: nxt = _AcpiParseNameString(parse, nxt, aml_end); - nxt = _AcpiParseTermObj(parse, nxt, aml_end); + if (parse->dev && READ32LE(parse->name_seg) == READ32LE("_HID")) { + nxt = _AcpiParseDataObjectHid(parse, nxt, aml_end, &parse->dev->hid); + } else if (parse->dev && READ32LE(parse->name_seg) == READ32LE("_CRS")) { + nxt = _AcpiParseDataObjectCrs(parse, nxt, aml_end, + &parse->dev->crs, &parse->dev->crs_size); + } else { + nxt = _AcpiParseTermObj(parse, nxt, aml_end); + } + /* TODO: handle _STA? */ return nxt; case kAmlByteOp: return nxt + 1; @@ -243,12 +406,36 @@ textstartup static const uint8_t *_AcpiParseTermObj(AmlParseState *parse, /* ignore */ return nxt + pl; case kAmlXDeviceOp: - ACPI_INFO("AML: DefDevice @ %p", aml); - _AcpiParsePkgLength(nxt, aml_end, &pl); - /* ignore */ - return nxt + pl; + { + AmlParseState subparse; + AcpiDefDevice tmp_dev, *dev; + nxt = _AcpiParsePkgLength(nxt, aml_end, &pl); + scope_end = aml + 2 + pl; + nxt = _AcpiParseNameString(parse, nxt, scope_end); + ACPI_INFO("AML: DefDevice %c%c%c%c @ %p", + (int)parse->name_seg[0], (int)parse->name_seg[1], + (int)parse->name_seg[2], (int)parse->name_seg[3], aml); + memset(&tmp_dev, 0, sizeof(tmp_dev)); + subparse = *parse; + subparse.dev = &tmp_dev; + nxt = _AcpiParseTermList(&subparse, nxt, scope_end); + if (tmp_dev.hid && tmp_dev.crs && tmp_dev.crs_size) { + dev = _AcpiOsAllocate(sizeof(*dev)); + if (!dev) { + ACPI_WARN("AML: no memory for %c%c%c%c dev info", + (int)parse->name_seg[0], (int)parse->name_seg[1], + (int)parse->name_seg[2], (int)parse->name_seg[3]); + } else { + *dev = tmp_dev; + dev->next = _AcpiDefDevices; + _AcpiDefDevices = dev; + ACPI_INFO("AML: -> %p", dev); + } + } + } + return nxt; default: - ACPI_FATAL("AML: unknown TermObj type %#x %#x @ %p", + ACPI_FATAL("AML: weird TermObj type %#x %#x @ %p", (unsigned)kAmlExtendedPrefix, (unsigned)aml[1], aml); } case kAmlCreateByteFieldOp: @@ -264,7 +451,7 @@ textstartup static const uint8_t *_AcpiParseTermObj(AmlParseState *parse, _AcpiParsePkgLength(nxt, aml_end, &pl); return nxt + pl; default: - ACPI_FATAL("AML: unknown TermObj type %#x @ %p", (unsigned)aml[0], aml); + ACPI_FATAL("AML: weird TermObj type %#x @ %p", (unsigned)aml[0], aml); } } diff --git a/libc/irq/acpi.internal.h b/libc/irq/acpi.internal.h index 2d8532b40b82..03eaecd56af7 100644 --- a/libc/irq/acpi.internal.h +++ b/libc/irq/acpi.internal.h @@ -2,6 +2,7 @@ #define COSMOPOLITAN_LIBC_IRQ_ACPI_INTERNAL_H_ #include "libc/dce.h" #include "libc/intrin/bits.h" +#include "libc/intrin/bswap.h" #include "libc/intrin/kprintf.h" #include "libc/log/color.internal.h" @@ -270,6 +271,23 @@ typedef struct thatispacked { uint8_t Aml[]; } AcpiTableDsdt; +/** + * @internal + * Private type for a device's hardware id. + */ +typedef uint64_t AcpiDeviceHid; + +/** + * @internal + * Private information about a device defined by an ACPI Device Package. + */ +typedef struct AcpiDefDevice { + struct AcpiDefDevice *next; + AcpiDeviceHid hid; + const uint8_t *crs; + size_t crs_size; +} AcpiDefDevice; + typedef uint32_t AcpiStatus; extern size_t _AcpiXsdtNumEntries, _AcpiNumIoApics; @@ -277,6 +295,7 @@ extern void **_AcpiXsdtEntries; extern uint16_t _AcpiBootFlags; extern uint32_t _AcpiMadtFlags; extern const AcpiMadtIoApic **_AcpiIoApics; +extern AcpiDefDevice *_AcpiDefDevices; extern void *_AcpiOsMapUncachedMemory(uintptr_t, size_t); extern void *_AcpiOsAllocate(size_t); @@ -294,6 +313,46 @@ forceinline AcpiStatus _AcpiGetTable(const char __sig[4], uint32_t __inst, return _AcpiGetTableImpl(READ32LE(__sig_copy), __inst, __phdr); } +/** + * @internal + * Converts a hardware id for an EISA device from string form to numeric + * form. If the HID is not an EISA type id, return 0. + * + * TODO: when the need arises, find a reasonable way to "compress" ACPI & + * PCI device ids as well. + */ +forceinline AcpiDeviceHid +_AcpiCompressHid(const uint8_t *__hid, size_t __len) { + uint8_t c; + unsigned a0, a1, a2, d0, d1, d2, d3; + uint32_t evalu; + if (__len != 7) return 0; + c = __hid[0]; + if (c < 'A' || c > 'Z') return 0; + a0 = c - 'A' + 1; + c = __hid[1]; + if (c < 'A' || c > 'Z') return 0; + a1 = c - 'A' + 1; + c = __hid[2]; + if (c < 'A' || c > 'Z') return 0; + a2 = c - 'A' + 1; + c = __hid[3]; + if (c < '0' || c > 'F' || (c > '9' && c < 'A')) return 0; + d0 = c <= '9' ? c - '0' : c - 'A' + 0xA; + c = __hid[4]; + if (c < '0' || c > 'F' || (c > '9' && c < 'A')) return 0; + d1 = c <= '9' ? c - '0' : c - 'A' + 0xA; + c = __hid[5]; + if (c < '0' || c > 'F' || (c > '9' && c < 'A')) return 0; + d2 = c <= '9' ? c - '0' : c - 'A' + 0xA; + c = __hid[6]; + if (c < '0' || c > 'F' || (c > '9' && c < 'A')) return 0; + d3 = c <= '9' ? c - '0' : c - 'A' + 0xA; + evalu = (uint32_t)a0 << 26 | (uint32_t)a1 << 21 | (uint32_t)a2 << 16 | + d0 << 12 | d1 << 8 | d2 << 4 | d3; + return bswap_32(evalu); +} + #define ACPI_INFO(FMT, ...) \ do { \ if (!IsTiny()) { \