boredos_mirror/src/drivers/ACPI/acpi_aml.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;
}