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..9248994a7ebd 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,138 @@ 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; + 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()) { + char str[kAcpiDecompressHidMax]; + if (!_AcpiDecompressHid(value, str)) { + ACPI_INFO("AML: dev _HID %#" PRIx64 " (!)", value); + } else { + ACPI_INFO("AML: dev _HID EISA %s", str); + } + } + *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 +343,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 +399,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 +444,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..22dd16b6701b 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,75 @@ 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 kAcpiDecompressHidMax 8 + +forceinline bool _AcpiDecompressHid(uintmax_t __hid, + char __str[kAcpiDecompressHidMax]) { + uint32_t __evalu; + unsigned char __a0, __a1, __a2, __d0, __d1, __d2, __d3; + if (__hid >= (1ul << 31)) return false; + __evalu = bswap_32((uint32_t)__hid); + __a0 = (__evalu >> 26) & 0x1F; + if (!__a0 || __a0 > 26) return false; + __a1 = (__evalu >> 21) & 0x1F; + if (!__a1 || __a1 > 26) return false; + __a2 = (__evalu >> 16) & 0x1F; + if (!__a2 || __a2 > 26) return false; + __d0 = (__evalu >> 12) & 0xF; + __d1 = (__evalu >> 8) & 0xF; + __d2 = (__evalu >> 4) & 0xF; + __d3 = (__evalu >> 0) & 0xF; + __str[0] = __a0 - 1 + 'A'; + __str[1] = __a1 - 1 + 'A'; + __str[2] = __a2 - 1 + 'A'; + __str[3] = __d0 <= 9 ? __d0 + '0' : __d0 - 0xA + 'A'; + __str[4] = __d1 <= 9 ? __d1 + '0' : __d1 - 0xA + 'A'; + __str[5] = __d2 <= 9 ? __d2 + '0' : __d2 - 0xA + 'A'; + __str[6] = __d3 <= 9 ? __d3 + '0' : __d3 - 0xA + 'A'; + __str[7] = 0; + return true; +} + #define ACPI_INFO(FMT, ...) \ do { \ if (!IsTiny()) { \