mirror of
https://github.com/BoredDevNL/BoredOS.git
synced 2026-05-15 10:48:38 +00:00
400 lines
14 KiB
C
400 lines
14 KiB
C
// Copyright (c) 2026 Myles Wilson (myles@bleedkernel.com)
|
|
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
|
|
// This header needs to maintain in any file it is present in, as per the GPL license terms.
|
|
|
|
#include "acpi_aml.h"
|
|
#include "../core/kutils.h"
|
|
#include "../core/kconsole.h"
|
|
|
|
// Internal helpers for decoding AML
|
|
|
|
static uint64_t decode_pkglen(const uint8_t **pp, const uint8_t *end) {
|
|
if (*pp >= end) return 0;
|
|
uint8_t b0 = *(*pp)++;
|
|
uint8_t count = (b0 >> 6);
|
|
if (count == 0) return (b0 & 0x3F);
|
|
|
|
uint64_t len = (b0 & 0x0F);
|
|
for (uint8_t i = 0; i < count; i++) {
|
|
if (*pp >= end) break;
|
|
len |= ((uint64_t)*(*pp)++) << (4 + 8 * i);
|
|
}
|
|
return len;
|
|
}
|
|
|
|
static uint64_t decode_integer(const uint8_t **pp, const uint8_t *end) {
|
|
if (*pp >= end) return 0;
|
|
uint8_t op = *(*pp)++;
|
|
switch (op) {
|
|
case AML_ZERO_OP: return 0;
|
|
case AML_ONE_OP: return 1;
|
|
case AML_BYTE_PREFIX: {
|
|
if (*pp >= end) return 0;
|
|
return *(*pp)++;
|
|
}
|
|
case AML_WORD_PREFIX: {
|
|
if (*pp + 1 >= end) return 0;
|
|
uint16_t v = (uint16_t)(*pp)[0] | ((uint16_t)(*pp)[1] << 8);
|
|
*pp += 2;
|
|
return v;
|
|
}
|
|
case AML_DWORD_PREFIX: {
|
|
if (*pp + 3 >= end) return 0;
|
|
uint32_t v = (uint32_t)(*pp)[0] | ((uint32_t)(*pp)[1] << 8) |
|
|
((uint32_t)(*pp)[2] << 16) | ((uint32_t)(*pp)[3] << 24);
|
|
*pp += 4;
|
|
return v;
|
|
}
|
|
case AML_QWORD_PREFIX: {
|
|
if (*pp + 7 >= end) return 0;
|
|
uint64_t v = 0;
|
|
for (int i = 0; i < 8; i++) v |= ((uint64_t)(*pp)[i] << (8 * i));
|
|
*pp += 8;
|
|
return v;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void decode_nameseg(const uint8_t **pp, const uint8_t *end, char out[AML_NAME_LEN]) {
|
|
if (*pp + 3 >= end) {
|
|
memcpy(out, " ", 4);
|
|
out[4] = '\0';
|
|
return;
|
|
}
|
|
memcpy(out, *pp, 4);
|
|
out[4] = '\0';
|
|
*pp += 4;
|
|
}
|
|
|
|
static void eisaid_to_str(uint32_t v, char out[AML_HID_LEN]) {
|
|
// Decode EISA ID: compressed 32-bit ID as per ACPI specification.
|
|
|
|
// Some firmware swaps the bytes, so handle both
|
|
uint32_t id = v;
|
|
if (((id >> 26) & 0x1F) == 0) {
|
|
// Swap bytes
|
|
id = ((v >> 24) & 0xFF) | ((v >> 8) & 0xFF00) | ((v << 8) & 0xFF0000) | ((v << 24) & 0xFF000000);
|
|
}
|
|
|
|
out[0] = (char)('@' + ((id >> 26) & 0x1F));
|
|
out[1] = (char)('@' + ((id >> 21) & 0x1F));
|
|
out[2] = (char)('@' + ((id >> 16) & 0x1F));
|
|
|
|
static const char hex[] = "0123456789ABCDEF";
|
|
out[3] = hex[(id >> 12) & 0xF];
|
|
out[4] = hex[(id >> 8) & 0xF];
|
|
out[5] = hex[(id >> 4) & 0xF];
|
|
out[6] = hex[(id >> 0) & 0xF];
|
|
out[7] = '\0';
|
|
}
|
|
|
|
static void parse_hid(const uint8_t **pp, const uint8_t *end, aml_i2c_dev_t *dev) {
|
|
if (*pp >= end) return;
|
|
uint8_t op = **pp;
|
|
|
|
if (op == AML_STRING_PREFIX) {
|
|
(*pp)++;
|
|
const char *s = (const char *)*pp;
|
|
size_t len = 0;
|
|
while (*pp < end && **pp) { len++; (*pp)++; }
|
|
if (*pp < end) (*pp)++;
|
|
|
|
if (len >= AML_HID_LEN) len = AML_HID_LEN - 1;
|
|
memcpy(dev->hid, s, len);
|
|
dev->hid[len] = '\0';
|
|
} else if (op == AML_DWORD_PREFIX) {
|
|
(*pp)++;
|
|
if (*pp + 3 < end) {
|
|
uint32_t v = (uint32_t)(*pp)[0] | ((uint32_t)(*pp)[1] << 8) |
|
|
((uint32_t)(*pp)[2] << 16) | ((uint32_t)(*pp)[3] << 24);
|
|
eisaid_to_str(v, dev->hid);
|
|
*pp += 4;
|
|
}
|
|
} else {
|
|
(*pp)++;
|
|
}
|
|
}
|
|
|
|
static void parse_crs(const uint8_t **pp, const uint8_t *end, aml_i2c_dev_t *dev) {
|
|
if (*pp >= end || **pp != AML_BUFFER_OP) return;
|
|
(*pp)++;
|
|
|
|
const uint8_t *pkg_start = *pp;
|
|
size_t pkglen = decode_pkglen(pp, end);
|
|
if (!pkglen) return;
|
|
|
|
const uint8_t *buf_end = pkg_start + pkglen;
|
|
if (buf_end > end) buf_end = end;
|
|
|
|
decode_integer(pp, buf_end); // skip BufferSize
|
|
|
|
const uint8_t *res = *pp;
|
|
while (res < buf_end) {
|
|
uint8_t tag = *res;
|
|
|
|
if (tag == ACPI_RESOURCE_END_TAG) break;
|
|
|
|
if (tag == ACPI_LARGE_I2C_SERIAL_BUS) {
|
|
uint16_t item_len = (uint16_t)(res[1]) | ((uint16_t)(res[2]) << 8);
|
|
if (res + 3 + item_len > buf_end) break;
|
|
|
|
// Offset 5 is Serial Bus Type (1 = I2C)
|
|
if (res[5] == ACPI_I2C_SERIAL_BUS_TYPE && item_len >= 15) {
|
|
uint32_t speed = (uint32_t)res[12] | ((uint32_t)res[13] << 8) |
|
|
((uint32_t)res[14] << 16) | ((uint32_t)res[15] << 24);
|
|
uint16_t addr = (uint16_t)res[16] | ((uint16_t)res[17] << 8);
|
|
|
|
if (addr != 0) {
|
|
dev->speed_hz = speed;
|
|
dev->slave_address = addr;
|
|
}
|
|
}
|
|
res += 3 + item_len;
|
|
continue;
|
|
}
|
|
|
|
if (tag & 0x80) {
|
|
if (res + 3 > buf_end) break;
|
|
uint16_t len = (uint16_t)(res[1]) | ((uint16_t)(res[2]) << 8);
|
|
res += 3 + len;
|
|
} else {
|
|
res += 1 + (tag & 0x07);
|
|
}
|
|
}
|
|
*pp = buf_end;
|
|
}
|
|
|
|
static void record_power_state(const char name[AML_NAME_LEN], aml_i2c_dev_t *dev) {
|
|
if (memcmp(name, "_PS0", 4) == 0) dev->power_flags |= AML_PWR_HAS_PS0;
|
|
else if (memcmp(name, "_PS3", 4) == 0) dev->power_flags |= AML_PWR_HAS_PS3;
|
|
else if (memcmp(name, "_PR0", 4) == 0) dev->power_flags |= AML_PWR_HAS_PR0;
|
|
else if (memcmp(name, "_PR3", 4) == 0) dev->power_flags |= AML_PWR_HAS_PR3;
|
|
}
|
|
|
|
bool is_touchpad_hid(const char *hid) {
|
|
if (!hid || !hid[0]) return false;
|
|
if (memcmp(hid, "PNP0C50", 7) == 0) return true; // Generic HID-over-I2C
|
|
if (memcmp(hid, "SYNA", 4) == 0) return true; // Synaptics
|
|
if (memcmp(hid, "ELAN", 4) == 0) return true; // Elantech
|
|
if (memcmp(hid, "ALPS", 4) == 0) return true; // Alps
|
|
return false;
|
|
}
|
|
|
|
static void skip_object(const uint8_t **pp, const uint8_t *end) {
|
|
if (*pp >= end) return;
|
|
uint8_t op = **pp;
|
|
switch (op) {
|
|
case AML_ZERO_OP:
|
|
case AML_ONE_OP:
|
|
(*pp)++; return;
|
|
case AML_BYTE_PREFIX: (*pp) += 2; return;
|
|
case AML_WORD_PREFIX: (*pp) += 3; return;
|
|
case AML_DWORD_PREFIX: (*pp) += 5; return;
|
|
case AML_QWORD_PREFIX: (*pp) += 9; return;
|
|
case AML_STRING_PREFIX:
|
|
(*pp)++;
|
|
while (*pp < end && **pp) (*pp)++;
|
|
if (*pp < end) (*pp)++;
|
|
return;
|
|
case AML_BUFFER_OP:
|
|
case AML_PACKAGE_OP: {
|
|
(*pp)++;
|
|
const uint8_t *start = *pp;
|
|
size_t len = decode_pkglen(pp, end);
|
|
if (len) *pp = start + len;
|
|
return;
|
|
}
|
|
default:
|
|
(*pp)++;
|
|
}
|
|
}
|
|
|
|
static void scan_device_scope(const uint8_t *p, const uint8_t *end, aml_i2c_dev_t *dev) {
|
|
while (p < end) {
|
|
uint8_t op = *p;
|
|
|
|
if (op == AML_NAME_OP) {
|
|
p++;
|
|
char seg[AML_NAME_LEN];
|
|
decode_nameseg(&p, end, seg);
|
|
|
|
if (memcmp(seg, "_HID", 4) == 0) {
|
|
parse_hid(&p, end, dev);
|
|
} else if (memcmp(seg, "_CID", 4) == 0) {
|
|
aml_i2c_dev_t tmp = {0};
|
|
parse_hid(&p, end, &tmp);
|
|
if (!dev->hid[0] || memcmp(dev->hid, "MCHP", 4) == 0) {
|
|
memcpy(dev->hid, tmp.hid, AML_HID_LEN);
|
|
}
|
|
} else if (memcmp(seg, "_CRS", 4) == 0) {
|
|
parse_crs(&p, end, dev);
|
|
} else {
|
|
record_power_state(seg, dev);
|
|
skip_object(&p, end);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (op == AML_METHOD_OP) {
|
|
p++;
|
|
const uint8_t *method_start = p;
|
|
size_t pkglen = decode_pkglen(&p, end);
|
|
if (!pkglen) break;
|
|
const uint8_t *method_end = method_start + pkglen;
|
|
if (method_end > end) method_end = end;
|
|
|
|
// Search the method body for a literal Buffer containing an I2C descriptor
|
|
const uint8_t *scan = p;
|
|
while (scan + 8 < method_end && dev->speed_hz == 0) {
|
|
if (*scan == AML_BUFFER_OP) {
|
|
const uint8_t *buf_ptr = scan;
|
|
parse_crs(&buf_ptr, method_end, dev);
|
|
}
|
|
scan++;
|
|
}
|
|
p = method_end;
|
|
continue;
|
|
}
|
|
|
|
// fallback: scan for I2C tag in buffers
|
|
if (op == AML_BUFFER_OP && dev->speed_hz == 0) {
|
|
const uint8_t *scan = p;
|
|
parse_crs(&scan, end, dev);
|
|
}
|
|
|
|
p++;
|
|
}
|
|
}
|
|
|
|
void aml_find_i2c_controllers(const uint8_t *aml, size_t len) {
|
|
if (!aml || !len) return;
|
|
const uint8_t *p = aml;
|
|
const uint8_t *end = aml + len;
|
|
|
|
while (p + 10 < end) {
|
|
// Look for NameOp + "_HID" or similar pattern
|
|
if (*p == AML_NAME_OP) {
|
|
char name[AML_NAME_LEN];
|
|
const uint8_t *scan = p + 1;
|
|
decode_nameseg(&scan, end, name);
|
|
if (memcmp(name, "_HID", 4) == 0) {
|
|
// Check if it matches INTC1040 or INTC1043
|
|
if (scan + 8 < end && scan[0] == AML_STRING_PREFIX) {
|
|
const char *hid = (const char*)(scan + 1);
|
|
if (memcmp(hid, "INTC1040", 8) == 0 || memcmp(hid, "INTC1043", 8) == 0) {
|
|
|
|
// Scan for Memory/DWord/QWord resource descriptors
|
|
const uint8_t *crs = scan;
|
|
while (crs + 14 < end && crs < scan + 2048) {
|
|
if (crs[0] == 0x86) {
|
|
// Memory32Fixed
|
|
} else if (crs[0] == 0x88) {
|
|
// DWord Memory
|
|
} else if (crs[0] == 0x89) {
|
|
// QWord Memory
|
|
} else if (crs[0] == 0x8A) {
|
|
// Extended Resource
|
|
}
|
|
crs++;
|
|
}
|
|
}
|
|
// Also look for the Touchpad itself
|
|
else if (is_touchpad_hid(hid)) {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
p++;
|
|
}
|
|
}
|
|
|
|
void aml_walk_table(const uint8_t *aml, size_t len, aml_walk_ctx_t *ctx) {
|
|
if (!aml || !len || !ctx || !ctx->devices) return;
|
|
const uint8_t *p = aml;
|
|
const uint8_t *end = aml + len;
|
|
|
|
while (p + 2 < end) {
|
|
uint8_t op = *p;
|
|
if (op == AML_EXTOP_PREFIX && p[1] == AML_DEVICE_OP) {
|
|
p += 2;
|
|
const uint8_t *scope_start = p;
|
|
size_t pkglen = decode_pkglen(&p, end);
|
|
if (!pkglen) { p++; continue; }
|
|
const uint8_t *scope_end = scope_start + pkglen;
|
|
if (scope_end > end) scope_end = end;
|
|
|
|
char dev_name[AML_NAME_LEN];
|
|
decode_nameseg(&p, scope_end, dev_name);
|
|
|
|
if (ctx->count < ctx->capacity) {
|
|
aml_i2c_dev_t *dev = &ctx->devices[ctx->count];
|
|
memset(dev, 0, sizeof(aml_i2c_dev_t));
|
|
memcpy(dev->name, dev_name, AML_NAME_LEN);
|
|
scan_device_scope(p, scope_end, dev);
|
|
|
|
if (dev->hid[0]) {
|
|
if (dev->speed_hz || is_touchpad_hid(dev->hid) || memcmp(dev->name, "TPD", 3) == 0) {
|
|
dev->valid = 1;
|
|
ctx->count++;
|
|
}
|
|
}
|
|
|
|
if (dev->slave_address == 0) {
|
|
const uint8_t *scan = p;
|
|
while (scan + 20 < scope_end && dev->slave_address == 0) {
|
|
if (*scan == ACPI_LARGE_I2C_SERIAL_BUS) {
|
|
uint16_t len = (uint16_t)(scan[1]) | ((uint16_t)(scan[2]) << 8);
|
|
if (scan + 3 + len <= scope_end && scan[5] == ACPI_I2C_SERIAL_BUS_TYPE && len >= 15) {
|
|
dev->speed_hz = (uint32_t)scan[12] | ((uint32_t)scan[13] << 8) |
|
|
((uint32_t)scan[14] << 16) | ((uint32_t)scan[15] << 24);
|
|
dev->slave_address = (uint16_t)scan[16] | ((uint16_t)scan[17] << 8);
|
|
}
|
|
}
|
|
scan++;
|
|
}
|
|
}
|
|
}
|
|
p++;
|
|
} else if (op == AML_SCOPE_OP) {
|
|
p++;
|
|
size_t pkglen = decode_pkglen(&p, end);
|
|
// Just move into the scope and keep scanning
|
|
if (!pkglen) p++;
|
|
} else {
|
|
p++;
|
|
}
|
|
}
|
|
}
|
|
|
|
int aml_parse_s5(const uint8_t *aml, size_t len, uint16_t *slp_typa, uint16_t *slp_typb) {
|
|
if (!aml || !len || !slp_typa || !slp_typb) return 0;
|
|
const uint8_t *end = aml + len;
|
|
const uint8_t *p = aml;
|
|
|
|
while (p + 4 < end) {
|
|
if (p[0] == '_' && p[1] == 'S' && p[2] == '5' && p[3] == '_') {
|
|
p += 4;
|
|
int found_pkg = 0;
|
|
for (int skip = 0; skip < 4 && p + skip < end; skip++) {
|
|
if (p[skip] == AML_PACKAGE_OP) { p += skip + 1; found_pkg = 1; break; }
|
|
}
|
|
if (!found_pkg) continue;
|
|
const uint8_t *pkg_start = p;
|
|
size_t pkglen = decode_pkglen(&p, end);
|
|
if (!pkglen) continue;
|
|
const uint8_t *pkg_end = pkg_start + pkglen;
|
|
if (pkg_end > end) pkg_end = end;
|
|
if (p >= pkg_end) continue;
|
|
uint8_t num = *p++;
|
|
if (num < 2) continue;
|
|
uint64_t typa = decode_integer(&p, pkg_end);
|
|
uint64_t typb = decode_integer(&p, pkg_end);
|
|
*slp_typa = (uint16_t)((typa & 0x7) << 10);
|
|
*slp_typb = (uint16_t)((typb & 0x7) << 10);
|
|
return 1;
|
|
}
|
|
p++;
|
|
}
|
|
return 0;
|
|
}
|