Skip to content

Commit

Permalink
[metal] Partly parse ACPI Device Packages; extract _HID & _CRS
Browse files Browse the repository at this point in the history
  • Loading branch information
tkchia committed Sep 20, 2023
1 parent fce7303 commit 4cb9b60
Show file tree
Hide file tree
Showing 3 changed files with 281 additions and 8 deletions.
5 changes: 5 additions & 0 deletions libc/irq/acpi-fadt-init.S
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,8 @@ _AcpiBootFlags:
.short kAcpiFadtLegacyDevices | kAcpiFadt8042
.endobj _AcpiBootFlags,globl
.previous
.bss
_AcpiDefDevices:
.skip 8
.endobj _AcpiDefDevices,globl
.previous
196 changes: 188 additions & 8 deletions libc/irq/acpi-fadt.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -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:
Expand All @@ -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);
}
}

Expand Down
88 changes: 88 additions & 0 deletions libc/irq/acpi.internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -270,13 +271,31 @@ 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;
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);
Expand All @@ -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()) { \
Expand Down

0 comments on commit 4cb9b60

Please sign in to comment.