ACPI: I2C Device Enumeration & Logging Function Accessability (#46)

* ACPI I2C Device Enumeration and Updated docs with better access to logging techniques

* updated copyright notices

bit of a hastle on my end, still getting used to being this cool
This commit is contained in:
Myles "Mellurboo" Wilson 2026-05-14 14:22:32 +01:00 committed by GitHub
parent 86d05c6040
commit 637670f8b0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 804 additions and 53 deletions

View file

@ -1,35 +1,87 @@
<div align="center"> <div align="center">
<h1>ACPI</h1> <h1>ACPI</h1>
<p><em>ACPI Power Interface</em></p> <p><em>Power management and hardware enumeration via the Advanced Configuration and Power Interface.</em></p>
</div> </div>
ACPI Subsystem ---
BoredOS implements an ACPI subsystem which manages power state transitions. BoredOS implements an ACPI subsystem that handles power state transitions, hardware discovery, and I2C device enumeration from the firmware tables. The implementation lives in `src/drivers/ACPI/`.
The implementation lives
in `src/acpi/` ## Table Discovery
At boot, the kernel requests the RSDP (Root System Description Pointer) from Limine. If the pointer is absent or fails its checksum, the kernel panics - this is a hard requirement, not a graceful fallback.
From the RSDP, the kernel determines which top-level descriptor table to use:
- **XSDT** (64-bit pointers) - used when RSDP revision ≥ 2 and a valid `xsdt_address` is present.
- **RSDT** (32-bit pointers) - fallback for older firmware.
This selection is handled transparently by `acpi_get_sdt()`, which walks whichever table is available and returns the first SDT matching a given 4-char signature.
> [!NOTE]
> The DSDT is a special case - it is not listed in the XSDT/RSDT. It is instead pointed to directly by the `dsdt` field in the FADT. Use `acpi_get_dsdt()` to retrieve it.
--- ---
### Startup and Table Discovery ## FADT and ACPI Enable
The first thing that ACPI does at boot time is to locate the RSDP.This can simply be requested fromlimine. The FADT (Fixed ACPI Description Table, signature `"FACP"`) is located first. It contains the port addresses needed for power management and the SMI command port used to hand off hardware control from firmware to the OS.
If the pointer is not present or the pointer is not valid we panic!It is a hardware requirement
If this is not present then we also check a checksum if that fails then the RSDP is also not valid and we panic If `smi_cmd` and `acpi_enable` are both non-zero, the kernel writes the enable byte to the SMI command port and spins on `PM1a_CNT` bit 0 until the hardware acknowledges. A timeout here is a fatal error.
XSDT vs RSDT Fallback
When the RSDP specifies aRevision of 2 or greater, and we have a valid XSDT Address then we should try and use the XSDT instead,otherwise we use the RSDT.This is automatically abstracted by `acpi_get_sdt()`
--- ---
### Shutdown and Power Off ## MADT and Interrupt Routing
This works by writing an appropriate value to PM1 Control Block register, and will prompt hardware to move into S5 power state. The MADT (Multiple APIC Description Table, signature `"APIC"`) is parsed for Interrupt Source Override entries (type 2). These describe cases where a legacy ISA IRQ is wired to a different GSI on the I/O APIC - for example, IRQ 0 (PIT) may be redirected to GSI 2 on some platforms.
### Performing the power off Two functions expose this mapping to the rest of the kernel:
Once the sleep types are identified and so on, this will continue as follows
On hardware that actually obeys ACPI standards this process should only perform step 1, and your machine should be turned off. To try and handle virtual machines or emulators that are known to fail at step 1,
we send the virtual machine interrupts second in case writing bytes makes our hardware think it is acting up and we do not wish to cause an unnecessary interrupt - `acpi_irq_to_gsi(irq)` - returns the GSI for a given IRQ, or the IRQ itself if no override exists.
- `acpi_irq_flags(irq)` - returns the polarity and trigger mode flags for the override.
---
## AML Parsing
The DSDT and any SSDTs present in the firmware contain AML (ACPI Machine Language) bytecode describing the hardware namespace. BoredOS does not implement a full AML interpreter - instead `src/drivers/ACPI/acpi_aml.c` implements a targeted byte-stream walker sufficient for I2C device enumeration.
The walker handles:
- **`_HID`** - device identification, either as an inline string or a packed EISAID integer.
- **`_CRS`** - current resource settings; specifically scans for `I2cSerialBusV2` descriptors (large item tag `0x8E`) to extract slave address, bus speed, and addressing mode.
- **`_DSM`** - device-specific method; scans the raw method body for the I2C-HID GUID (`3cdff6f7-4267-4555-ad05-b30a3d8938de`) and extracts the HID descriptor register address from the static return package.
- **Power states** - records presence of `_PS0`, `_PS3`, `_PR0`, `_PR3` as flags.
> [!IMPORTANT]
> `_CRS` and `_DSM` must be statically defined `Name()` objects for the walker to read them. Dynamically computed resources via AML method evaluation are not supported.
---
## I2C Enumeration
`acpi_i2c_enumerate()` is called at the end of `acpi_init()`. It walks the DSDT via `acpi_get_dsdt()` followed by all SSDTs found in the XSDT/RSDT, feeding each into `aml_walk_table()`.
Devices are emitted into a flat table when both a `_HID` and a valid `I2cSerialBusV2` resource (non-zero `connection_speed`) are found. The result is accessible via:
- `acpi_i2c_count()` - number of enumerated devices.
- `acpi_i2c_get(index)` - pointer to an `aml_i2c_dev_t` record containing the name, HID, slave address, speed, DSM result, and power flags.
---
## Shutdown
Performing a clean S5 (soft power-off) requires writing the correct `SLP_TYP` value to the PM1 Control Block. This value is firmware-specific and must be read from the `_S5_` object in the DSDT.
`aml_parse_s5()` scans the DSDT AML for the `_S5_` name and extracts `SLP_TYPa` and `SLP_TYPb`, pre-shifted to bit position 10 ready for `outw`. If the parse fails, the kernel falls back to `SLP_TYP = 5`, which works on the majority of hardware.
### Shutdown sequence
On compliant hardware, step 1 is all that runs and the machine powers off immediately. The remaining steps target known virtual machine implementations that don't respond to standard ACPI.
1. Write `SLP_TYPa | SLP_EN` to `PM1a_CNT_BLK` (and `PM1b_CNT_BLK` if present).
2. Write known virtualizer-specific magic values - QEMU (`0x604`), VirtualBox (`0x4004`), Bochs (`0xB004`), Cloud Hypervisor (`0x600`) - to trigger their respective shutdown paths.
3. Disable interrupts and halt indefinitely as a last resort.
> [!NOTE]
> The virtualizer ports are sent after the ACPI write deliberately. On real hardware, sending those port values is harmless, but the ordering avoids any ambiguity about what triggered the power-off.

View file

@ -10,4 +10,13 @@ void kconsole_putc(char c);
void kconsole_write(const char *s); void kconsole_write(const char *s);
void kconsole_set_active(bool active); void kconsole_set_active(bool active);
void serial_write(const char *str);
void serial_write_num_locked(uint32_t n);
void serial_write_num(uint32_t n);
void serial_write_hex_locked(uint64_t n);
void serial_write_hex(uint64_t n);
void log_ok(const char *msg);
void log_fail(const char *msg);
#endif // KCONSOLE_H #endif // KCONSOLE_H

View file

@ -5,7 +5,7 @@
#include "wm.h" #include "wm.h"
#include "io.h" #include "io.h"
#include "../drivers/acpi.h" #include "../drivers/ACPI/acpi.h"
void memset(void *dest, int val, size_t len) { void memset(void *dest, int val, size_t len) {
unsigned char *ptr = (unsigned char *)dest; unsigned char *ptr = (unsigned char *)dest;
@ -75,6 +75,20 @@ void strcpy(char *dest, const char *src) {
*dest = 0; *dest = 0;
} }
char *strncpy(char *dest, const char *src, size_t n) {
size_t i;
for (i = 0; i < n && src[i] != '\0'; i++) {
dest[i] = src[i];
}
for (; i < n; i++) {
dest[i] = '\0';
}
return dest;
}
int atoi(const char *str) { int atoi(const char *str) {
int res = 0; int res = 0;
int sign = 1; int sign = 1;

View file

@ -17,6 +17,7 @@ size_t strlen(const char *str);
int strcmp(const char *s1, const char *s2); int strcmp(const char *s1, const char *s2);
int strncmp(const char *s1, const char *s2, size_t n); int strncmp(const char *s1, const char *s2, size_t n);
void strcpy(char *dest, const char *src); void strcpy(char *dest, const char *src);
char *strncpy(char *dest, const char *src, size_t n);
int atoi(const char *str); int atoi(const char *str);
void itoa(int n, char *buf); void itoa(int n, char *buf);
void itoa_hex(uint64_t n, char *buf); void itoa_hex(uint64_t n, char *buf);

View file

@ -35,7 +35,7 @@
#include "sys/bootfs_state.h" #include "sys/bootfs_state.h"
#include "input/keymap.h" #include "input/keymap.h"
#include "input/keyboard.h" #include "input/keyboard.h"
#include "../drivers/acpi.h" #include "../drivers/ACPI/acpi.h"
extern void sysfs_init_subsystems(void); extern void sysfs_init_subsystems(void);
@ -134,7 +134,7 @@ void serial_write(const char *str) {
spinlock_release_irqrestore(&serial_lock, flags); spinlock_release_irqrestore(&serial_lock, flags);
} }
static void serial_write_num_locked(uint32_t n) { void serial_write_num_locked(uint32_t n) {
if (n >= 10) serial_write_num_locked(n / 10); if (n >= 10) serial_write_num_locked(n / 10);
char c = '0' + (n % 10); char c = '0' + (n % 10);
while ((inb(0x3F8 + 5) & 0x20) == 0); while ((inb(0x3F8 + 5) & 0x20) == 0);
@ -148,7 +148,7 @@ void serial_write_num(uint32_t n) {
spinlock_release_irqrestore(&serial_lock, flags); spinlock_release_irqrestore(&serial_lock, flags);
} }
static void serial_write_hex_locked(uint64_t n) { void serial_write_hex_locked(uint64_t n) {
char *hex = "0123456789ABCDEF"; char *hex = "0123456789ABCDEF";
if (n >= 16) serial_write_hex_locked(n / 16); if (n >= 16) serial_write_hex_locked(n / 16);
char c = hex[n % 16]; char c = hex[n % 16];
@ -378,7 +378,7 @@ void kmain(void) {
acpi_init(); acpi_init();
process_init(); process_init();
fat32_init(); fat32_init();
log_ok("FAT32 ready"); log_ok("FAT32 ready");

View file

@ -1,7 +1,12 @@
// 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 <stdint.h> #include <stdint.h>
#include <stddef.h> #include <stddef.h>
#include "acpi_structures.h" #include "acpi_structures.h"
#include "../I2C/acpi_i2c.h"
#include "acpi.h" #include "acpi.h"
#include "../sys/idt.h" #include "../sys/idt.h"
#include "../core/limine.h" #include "../core/limine.h"
@ -36,7 +41,7 @@ static int acpi_checksum(void *ptr, size_t len) {
return sum == 0; return sum == 0;
} }
static void *acpi_get_rsdp(void){ struct acpi_rsdp *acpi_get_rsdp(void){
extern volatile struct limine_rsdp_request acpi_rsdp_request; extern volatile struct limine_rsdp_request acpi_rsdp_request;
if (!acpi_rsdp_request.response) if (!acpi_rsdp_request.response)
@ -47,7 +52,7 @@ static void *acpi_get_rsdp(void){
return acpi_rsdp_request.response->address; return acpi_rsdp_request.response->address;
} }
static struct acpi_sdt *acpi_get_sdt(const char signature[4]) { struct acpi_sdt *acpi_get_sdt(const char signature[4]) {
if (acpi_rsdp->revision >= 2 && acpi_rsdp->xsdt_address) { if (acpi_rsdp->revision >= 2 && acpi_rsdp->xsdt_address) {
struct acpi_xsdt *acpi_xsdt = (struct acpi_xsdt *)p2v(acpi_rsdp->xsdt_address); struct acpi_xsdt *acpi_xsdt = (struct acpi_xsdt *)p2v(acpi_rsdp->xsdt_address);
if (acpi_checksum(acpi_xsdt, acpi_xsdt->header.length)) { if (acpi_checksum(acpi_xsdt, acpi_xsdt->header.length)) {
@ -81,8 +86,8 @@ static struct acpi_sdt *acpi_get_sdt(const char signature[4]) {
return NULL; return NULL;
} }
static uint16_t SLP_TYPa = 0; uint16_t SLP_TYPa = 0;
static uint16_t SLP_TYPb = 0; uint16_t SLP_TYPb = 0;
void acpi_parse_s5(void) { void acpi_parse_s5(void) {
if (!acpi_fadt || !acpi_fadt->dsdt) return; if (!acpi_fadt || !acpi_fadt->dsdt) return;
@ -199,9 +204,12 @@ int acpi_init(void){
ptr += h->length; ptr += h->length;
} }
acpi_i2c_enumerate();
return 0; return 0;
} }
uint32_t acpi_irq_to_gsi(uint32_t irq) { uint32_t acpi_irq_to_gsi(uint32_t irq) {
for (size_t i = 0; i < iso_count; i++) { for (size_t i = 0; i < iso_count; i++) {
if (iso_table[i].source == irq) if (iso_table[i].source == irq)
@ -218,3 +226,8 @@ uint16_t acpi_irq_flags(uint32_t irq) {
return 0; return 0;
} }
struct acpi_sdt *acpi_get_dsdt(void) {
if (!acpi_fadt || !acpi_fadt->dsdt) return NULL;
return (struct acpi_sdt *)p2v((uintptr_t)acpi_fadt->dsdt);
}

35
src/drivers/ACPI/acpi.h Normal file
View file

@ -0,0 +1,35 @@
// 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.
#ifndef ACPI_H
#define ACPI_H
//power stuff
#define PM1A_CNT fadt->pm1a_cnt_blk
#define PM1B_CNT fadt->pm1b_cnt_blk
#define ACPI_S5 0x5
#define SLP_EN (1 << 13)
#define ACPI_PM1_SLEEP_CMD(slp_typ) (((slp_typ) << 10) | SLP_EN)
extern uint16_t SLP_TYPa;
extern uint16_t SLP_TYPb;
int acpi_init(void);
uint32_t acpi_irq_to_gsi(uint32_t irq);
uint16_t acpi_irq_flags(uint32_t irq);
struct acpi_rsdp *acpi_get_rsdp(void);
struct acpi_sdt *acpi_get_sdt(const char signature[4]);
struct acpi_sdt *acpi_get_dsdt(void);
void acpi_parse_s5(void);
// ACPI Functionality Implementations
// - do reboot properly
__attribute__((noreturn)) void acpi_shutdown(void);
#endif

417
src/drivers/ACPI/acpi_aml.c Normal file
View file

@ -0,0 +1,417 @@
// 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 "kutils.h"
#include "kconsole.h"
#include <stdint.h>
#include <stddef.h>
/// @brief Decode AML PkgLength field, advance past it
/// @param pp in/out byte pointer, advanced past the field on return
/// @param end hard bound; returns 0 if field exceeds it
/// @return total package length including the PkgLength bytes, 0 on error
static size_t decode_pkglen(const uint8_t **pp, const uint8_t *end) {
if (*pp >= end) return 0;
uint8_t b0 = *(*pp)++;
uint8_t extra = b0 >> 6;
size_t len;
if (extra == 0) {
len = b0 & 0x3F; // 1-byte form: bits[5:0]
} else {
len = b0 & 0x0F; // multi-byte form: bits[3:0] are low nibble
if (*pp + extra > end) return 0;
for (uint8_t i = 0; i < extra; i++)
len |= (size_t)(*(*pp)++) << (4 + 8 * i);
}
return len;
}
/// @brief Decode the last NameSeg of an AML NameString into out
/// @param pp in/out byte pointer, advanced past the full name on return
/// @param end hard bound
/// @param out receives null-terminated 4-char NameSeg
static void decode_nameseg(const uint8_t **pp, const uint8_t *end,
char out[AML_NAME_LEN]) {
out[0] = '\0';
if (*pp >= end) return;
while (*pp < end && (**pp == '\\' || **pp == '^')) (*pp)++;
if (*pp >= end) return;
uint8_t lead = **pp;
if (lead == 0x00) {
(*pp)++;
return;
} else if (lead == 0x2E) { // DualNamePath two 4-char segs
(*pp)++;
if (*pp + 8 > end) return;
(*pp) += 4;
for (int i = 0; i < 4; i++) out[i] = (char)(*pp)[i];
out[4] = '\0';
(*pp) += 4;
} else if (lead == 0x2F) { // MultiNamePath count + N segs
(*pp)++;
if (*pp >= end) return;
uint8_t count = *(*pp)++;
if (*pp + (size_t)count * 4 > end) return;
const uint8_t *last = *pp + (count - 1) * 4;
for (int i = 0; i < 4; i++) out[i] = (char)last[i];
out[4] = '\0';
*pp += (size_t)count * 4;
} else { // plain 4-char NameSeg
if (*pp + 4 > end) return;
for (int i = 0; i < 4; i++) out[i] = (char)(*pp)[i];
out[4] = '\0';
(*pp) += 4;
}
}
/// @brief Decode any AML integer data object (Zero, One, Byte, Word, DWord, QWord)
/// @param pp in/out byte pointer, advanced past the object on return
/// @param end hard bound
/// @return decoded integer value, 0 on unknown opcode (pointer not advanced)
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 + 1 > end) return 0;
return *(*pp)++;
case AML_WORD_PREFIX: {
if (*pp + 2 > 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 + 4 > 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 + 8 > 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;
}
default:
(*pp)--;
return 0;
}
}
/// @brief Convert 32-bit packed EISAID value to ASCII HID string (e.g. "PNP0C0E")
/// @param id EISAID as decoded by decode_integer (little-endian from AML)
/// @param out receives null-terminated 8-char string
static void eisaid_to_str(uint32_t id, char out[AML_HID_LEN]) {
uint32_t v = ((id & 0x000000FF) << 24)
| ((id & 0x0000FF00) << 8)
| ((id & 0x00FF0000) >> 8)
| ((id & 0xFF000000) >> 24);
out[0] = (char)('@' + ((v >> 26) & 0x1F));
out[1] = (char)('@' + ((v >> 21) & 0x1F));
out[2] = (char)('@' + ((v >> 16) & 0x1F));
static const char hex[] = "0123456789ABCDEF";
out[3] = hex[(v >> 12) & 0xF];
out[4] = hex[(v >> 8) & 0xF];
out[5] = hex[(v >> 4) & 0xF];
out[6] = hex[(v >> 0) & 0xF];
out[7] = '\0';
}
/// @brief Parse _HID data object (string or EISAID DWord) into dev->hid
/// @param pp in/out byte pointer positioned at the data object
/// @param end hard bound
/// @param dev target device record
static void parse_hid(const uint8_t **pp, const uint8_t *end,
aml_i2c_dev_t *dev) {
if (*pp >= end) return;
if (**pp == AML_STRING_PREFIX) {
(*pp)++;
const char *s = (const char *)*pp;
strncpy(dev->hid, s, AML_HID_LEN - 1);
dev->hid[AML_HID_LEN - 1] = '\0';
while (*pp < end && **pp) (*pp)++;
if (*pp < end) (*pp)++;
} else if (**pp == AML_DWORD_PREFIX) {
uint32_t id = (uint32_t)decode_integer(pp, end);
eisaid_to_str(id, dev->hid);
}
}
/// @brief Parse _CRS Buffer for an I2cSerialBusV2 descriptor, fill dev CRS fields
/// @param pp in/out byte pointer positioned at BufferOp
/// @param end hard bound
/// @param dev receives slave_address, speed_hz, ten_bit_addr on success
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) {
if (res + sizeof(aml_i2c_resource_t) > buf_end) break;
const aml_i2c_resource_t *r = (const aml_i2c_resource_t *)res;
if (r->serial_bus_type == ACPI_I2C_SERIAL_BUS_TYPE) {
dev->slave_address = r->slave_address;
dev->speed_hz = r->connection_speed;
dev->ten_bit_addr = (r->type_specific_flags & 0x01) ? 1 : 0;
}
res += sizeof(uint8_t) + sizeof(uint16_t) + r->length;
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;
}
/// @brief Best-effort skip of one AML data object to keep the scan on track
/// @param pp in/out byte pointer, advanced past the object on return
/// @param end hard bound
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)++;
}
}
/// @brief Scan one Device() scope body for _HID, _CRS, _DSM, and power objects
/// @param p first byte of the scope body (after NameSeg)
/// @param end one past last byte of the scope
/// @param dev device record to populate
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, "_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;
char seg[AML_NAME_LEN];
const uint8_t *name_ptr = p;
decode_nameseg(&name_ptr, method_end, seg);
if (memcmp(seg, "_DSM", 4) == 0) {
const uint8_t *body = name_ptr + 1; // skip MethodFlags we dont care abt it rn
const uint8_t *scan = body;
const uint8_t *guid = (const uint8_t *)ACPI_I2C_HID_DSM_GUID;
while (scan + 16 < method_end) {
if (memcmp(scan, guid, 16) != 0) { scan++; continue; }
scan += 16;
while (scan + 4 < method_end) {
if (*scan != AML_RETURN_OP) { scan++; continue; }
scan++;
if (*scan != AML_PACKAGE_OP) { continue; }
scan++;
const uint8_t *ps = scan;
size_t pl = decode_pkglen(&scan, method_end);
if (!pl) break;
const uint8_t *pe = ps + pl;
if (pe > method_end || scan >= pe) break;
uint8_t nelem = *scan++;
if (nelem < 1) break;
uint64_t val = decode_integer(&scan, pe);
if (val <= 0xFFFF) {
dev->hid_desc_addr = (uint16_t)val;
dev->has_dsm = 1;
}
goto method_done;
}
break;
}
} else {
record_power_state(seg, dev);
}
method_done:
p = method_end;
continue;
}
// skip nested Device or Scope without recursing
if (op == AML_EXTOP_PREFIX && p + 1 < end && *(p + 1) == AML_DEVICE_OP) {
p += 2;
const uint8_t *s = p;
size_t l = decode_pkglen(&p, end);
if (l) p = s + l; else break;
continue;
}
if (op == AML_SCOPE_OP) {
p++;
const uint8_t *s = p;
size_t l = decode_pkglen(&p, end);
if (l) p = s + l; else break;
continue;
}
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) {
if (p[0] != AML_EXTOP_PREFIX || p[1] != AML_DEVICE_OP) { p++; continue; }
p += 2;
const uint8_t *scope_start = p;
size_t pkglen = decode_pkglen(&p, end);
if (!pkglen) 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) break;
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);
scan_device_scope(p, scope_end, dev);
if (dev->hid[0] && dev->speed_hz) {
dev->valid = 1;
ctx->count++;
}
p = scope_end;
}
}
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++; continue;
}
p += 4;
// scan ahead up to 4 bytes for PackageOp; on diff firmware layouts YMMV
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;
}
return 0;
}

View file

@ -0,0 +1,96 @@
// 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.
#ifndef ACPI_AML_H
#define ACPI_AML_H
#include <stdint.h>
#include <stddef.h>
#define AML_ZERO_OP 0x00
#define AML_ONE_OP 0x01
#define AML_NAME_OP 0x08
#define AML_BYTE_PREFIX 0x0A
#define AML_WORD_PREFIX 0x0B
#define AML_DWORD_PREFIX 0x0C
#define AML_STRING_PREFIX 0x0D
#define AML_QWORD_PREFIX 0x0E
#define AML_SCOPE_OP 0x10
#define AML_BUFFER_OP 0x11
#define AML_PACKAGE_OP 0x12
#define AML_METHOD_OP 0x14
#define AML_EXTOP_PREFIX 0x5B
#define AML_DEVICE_OP 0x82 // always preceded by 0x5B
#define AML_RETURN_OP 0xA4
// ACPI resource descriptor tags
#define ACPI_RESOURCE_END_TAG 0x79
#define ACPI_LARGE_ITEM 0x80
#define ACPI_LARGE_I2C_SERIAL_BUS 0x8E
#define ACPI_I2C_SERIAL_BUS_TYPE 0x01
// I2cSerialBusV2 resource descriptor
typedef struct __attribute__((packed)) {
uint8_t tag;
uint16_t length;
uint8_t revision_id;
uint8_t resource_source_index;
uint8_t serial_bus_type;
uint8_t general_flags;
uint16_t type_specific_flags;
uint8_t type_specific_revision_id;
uint16_t type_data_length;
uint32_t connection_speed;
uint16_t slave_address;
} aml_i2c_resource_t;
/*
GUID: 3cdff6f7-4267-4555-ad05-b30a3d8938de, mixed-endian AML layout
sorry i know its a magic number but tis the way of things
*/
#define ACPI_I2C_HID_DSM_GUID \
"\xf7\xf6\xdf\x3c\x67\x42\x55\x45\xad\x05\xb3\x0a\x3d\x89\x38\xde"
#define AML_PWR_HAS_PS0 (1 << 0)
#define AML_PWR_HAS_PS3 (1 << 1)
#define AML_PWR_HAS_PR0 (1 << 2)
#define AML_PWR_HAS_PR3 (1 << 3)
#define AML_HID_LEN 9
#define AML_NAME_LEN 5
typedef struct {
char name[AML_NAME_LEN];
char hid[AML_HID_LEN];
uint16_t slave_address;
uint32_t speed_hz;
uint8_t ten_bit_addr;
uint16_t hid_desc_addr;
uint8_t has_dsm;
uint8_t power_flags;
uint8_t valid;
} aml_i2c_dev_t;
typedef struct {
aml_i2c_dev_t *devices;
size_t capacity;
size_t count;
} aml_walk_ctx_t;
/// @brief Walk one DSDT or SSDT AML region, emitting I2C device records
/// @param aml first AML byte (SDT base + 36)
/// @param len byte length of the AML region
/// @param ctx output context; devices array must be pre-allocated
void aml_walk_table(const uint8_t *aml, size_t len, aml_walk_ctx_t *ctx);
/// @brief Scan DSDT AML for _S5_ and extract SLP_TYPa/b for S5 power-off
/// @param aml first AML byte (DSDT base + 36)
/// @param len byte length of the AML region
/// @param slp_typa out - SLP_TYPa pre-shifted to PM1_CNT bit 10
/// @param slp_typb out - SLP_TYPb pre-shifted to PM1_CNT bit 10
/// @return 1 if _S5_ found, 0 if not found (caller should use fallback)
int aml_parse_s5(const uint8_t *aml, size_t len,
uint16_t *slp_typa, uint16_t *slp_typb);
#endif

View file

@ -1,3 +1,7 @@
// 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.
#ifndef ACPI_STRUCTURES_H #ifndef ACPI_STRUCTURES_H
#define ACPI_STRUCTURES_H #define ACPI_STRUCTURES_H

115
src/drivers/I2C/acpi_i2c.c Normal file
View file

@ -0,0 +1,115 @@
// 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_i2c.h"
#include "../ACPI/acpi.h"
#include "../ACPI/acpi_aml.h"
#include "../ACPI/acpi_structures.h"
#include "../core/kconsole.h"
#include "../core/platform.h"
static aml_i2c_dev_t i2c_devices[ACPI_I2C_MAX_DEVICES];
static size_t i2c_device_count = 0;
// ACPI SDT header is 36 bytes AML starts concurrently, nice and convenient
#define ACPI_SDT_HEADER_LEN 36
static void walk_sdt(struct acpi_sdt *sdt, aml_walk_ctx_t *ctx) {
if (!sdt) return;
if (sdt->length <= ACPI_SDT_HEADER_LEN) return;
const uint8_t *aml = (const uint8_t *)sdt + ACPI_SDT_HEADER_LEN;
size_t len = sdt->length - ACPI_SDT_HEADER_LEN;
aml_walk_table(aml, len, ctx);
}
static void walk_all_ssdts(aml_walk_ctx_t *ctx) {
struct acpi_rsdp *rsdp = acpi_get_rsdp();
if (!rsdp) return;
if (rsdp->revision >= 2 && rsdp->xsdt_address) {
struct acpi_xsdt *xsdt = (struct acpi_xsdt *)p2v(rsdp->xsdt_address);
size_t entries = (xsdt->header.length - sizeof(struct acpi_sdt)) / 8;
for (size_t i = 0; i < entries; i++) {
struct acpi_sdt *sdt = (struct acpi_sdt *)p2v(xsdt->tables[i]);
if (!sdt) continue;
if (__builtin_memcmp(sdt->signature, "SSDT", 4) == 0)
walk_sdt(sdt, ctx);
}
return;
}
// RSDT fallback
if (!rsdp->rsdt_address) return;
struct acpi_sdt *rsdt = (struct acpi_sdt *)p2v(rsdp->rsdt_address);
uint32_t *tables = (uint32_t *)((uint8_t *)rsdt + sizeof(struct acpi_sdt));
size_t entries = (rsdt->length - sizeof(struct acpi_sdt)) / 4;
for (size_t i = 0; i < entries; i++) {
struct acpi_sdt *sdt = (struct acpi_sdt *)p2v(tables[i]);
if (!sdt) continue;
if (__builtin_memcmp(sdt->signature, "SSDT", 4) == 0)
walk_sdt(sdt, ctx);
}
}
int acpi_i2c_enumerate(void) {
i2c_device_count = 0;
aml_walk_ctx_t ctx = {
.devices = i2c_devices,
.capacity = ACPI_I2C_MAX_DEVICES,
.count = 0,
};
// Walk DSDT - pointed to by FADT, not listed in XSDT/RSDT
struct acpi_sdt *dsdt = acpi_get_dsdt();
if (dsdt) {
walk_sdt(dsdt, &ctx);
} else {
serial_write("[acpi_i2c] warning: DSDT not found\n");
}
// Walk all SSDTs
walk_all_ssdts(&ctx);
i2c_device_count = ctx.count;
for (size_t i = 0; i < i2c_device_count; i++) {
const aml_i2c_dev_t *d = &i2c_devices[i];
serial_write("[acpi_i2c] ");
serial_write(d->name);
serial_write(" hid=");
serial_write(d->hid);
serial_write(" addr=0x");
serial_write_hex(d->slave_address);
serial_write(" speed=");
serial_write_num(d->speed_hz);
serial_write(" Hz");
if (d->ten_bit_addr) serial_write(" 10-bit");
if (d->power_flags & (AML_PWR_HAS_PS0 | AML_PWR_HAS_PS3)) serial_write(" PS0/3");
if (d->power_flags & (AML_PWR_HAS_PR0 | AML_PWR_HAS_PR3)) serial_write(" PR0/3");
if (d->has_dsm) {
serial_write(" hid_desc_reg=0x");
serial_write_hex(d->hid_desc_addr);
}
serial_write("\n");
}
if (i2c_device_count != 0) {
log_ok("I2C enumeration complete");
} else {
log_fail("No I2C devices found in ACPI tables");
}
return (int)i2c_device_count;
}
size_t acpi_i2c_count(void) {
return i2c_device_count;
}
const aml_i2c_dev_t *acpi_i2c_get(size_t index) {
if (index >= i2c_device_count) return NULL;
return &i2c_devices[index];
}

View file

@ -0,0 +1,20 @@
// 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.
#ifndef ACPI_I2C_H
#define ACPI_I2C_H
#include <stdint.h>
#include <stddef.h>
#include "../ACPI/acpi_aml.h"
#define ACPI_I2C_MAX_DEVICES 32
int acpi_i2c_enumerate(void);
size_t acpi_i2c_count(void);
const aml_i2c_dev_t *acpi_i2c_get(size_t index);
#endif

View file

@ -1,25 +0,0 @@
#ifndef ACPI_H
#define ACPI_H
int acpi_init(void);
__attribute__((noreturn)) void acpi_shutdown(void);
__attribute__((noreturn)) void acpi_reboot(void);
uint32_t acpi_irq_to_gsi(uint32_t irq);
uint16_t acpi_irq_flags(uint32_t irq);
//power stuff
#define PM1A_CNT fadt->pm1a_cnt_blk
#define PM1B_CNT fadt->pm1b_cnt_blk
#define ACPI_S5 0x5
#define SLP_EN (1 << 13)
#define ACPI_PM1_SLEEP_CMD(slp_typ) (((slp_typ) << 10) | SLP_EN)
void acpi_parse_s5(void);
__attribute__((noreturn)) void acpi_shutdown(void);
#endif