fix(acpi): read S5 sleep types from the DSDT _S5 object

The FADT signature constant was byte-swapped ("FCAP"), so FADT lookup
always failed. Correct it to "FACP".

SLP_TYP is platform-defined and must come from the DSDT _S5 package.
QEMU/OVMF defines _S5 as 0, so the previous hardcoded value 5 never
triggered S5. Parse _S5 and use its SLP_TYPa/SLP_TYPb for PM1a/PM1b.
This commit is contained in:
TermiNexus committed 2026-10-27 17:36:35 +08:00
1 parent b33fbb75dc
commit cec0647708
1 file changed
+67 -6
+67 -6
View File
@@ -4,11 +4,13 @@ import asm
import drivers.serial.uart.serial as serial
define ACPI_RSDP_SIG: uint64 = 0x2052545020445352 # "RSD PTR "
define ACPI_FADT_SIG: uint32 = 0x50414346 # "FACP"
define ACPI_SLP_TYP_S5: uint16 = 5
define ACPI_FADT_SIG: uint32 = 0x50434146 # "FACP"
define ACPI_SLP_EN: uint16 = 0x2000
define ACPI_PM1_CNT_PM1A: uint64 = 64 # FADT PM1a_CNT_BLK
define ACPI_PM1_CNT_PM1B: uint64 = 68 # FADT PM1b_CNT_BLK
define ACPI_DSDT: uint64 = 40 # FADT DSDT
define ACPI_X_DSDT: uint64 = 140 # FADT X_DSDT
define ACPI_S5_NOT_FOUND: uint64 = 0xFFFFFFFFFFFFFFFF
rsdp_phys: uint64 = 0
@@ -20,6 +22,10 @@ def _rd8(addr: uint64) -> uint8:
p: uint8* = uint8*(addr)
return p[0]
def _rd16(addr: uint64) -> uint16:
p: uint16* = uint16*(addr)
return p[0]
def _rd32(addr: uint64) -> uint32:
p: uint32* = uint32*(addr)
return p[0]
@@ -67,20 +73,75 @@ def _find_fadt() -> uint64:
rsdt: uint64 = uint64(_rd32(rsdp + uint64(16)))
return _find_table(rsdt, 4, ACPI_FADT_SIG)
def _find_dsdt(fadt: uint64) -> uint64:
dsdt: uint64 = uint64(_rd32(fadt + ACPI_DSDT))
if dsdt == 0 and _rd32(fadt + uint64(4)) >= 148:
dsdt = _rd64(fadt + ACPI_X_DSDT)
return dsdt
# Size in bytes of an AML integer element.
def _aml_int_size(addr: uint64) -> uint64:
op: uint8 = _rd8(addr)
if op == 0x0A: return 2
if op == 0x0B: return 3
if op == 0x0C: return 5
if op == 0x0E: return 9
if op == 0x0D:
n: uint64 = 1
while _rd8(addr + n) != 0: n = n + 1
return n + 1
return 1
def _aml_int(addr: uint64) -> uint64:
op: uint8 = _rd8(addr)
if op == 0x00: return 0
if op == 0x01: return 1
if op == 0x0A: return uint64(_rd8(addr + 1))
if op == 0x0B: return uint64(_rd16(addr + 1))
if op == 0x0C: return uint64(_rd32(addr + 1))
if op == 0x0E: return _rd64(addr + 1)
if op == 0xFF: return 0xFFFFFFFFFFFFFFFF
return 0
# Locate _S5 and return SLP_TYPa in bits 0..15, SLP_TYPb in bits 16..31.
def _s5_from_table(table: uint64) -> uint64:
if table == 0: return ACPI_S5_NOT_FOUND
length: uint32 = _rd32(table + uint64(4))
if length < 45: return ACPI_S5_NOT_FOUND
off: uint64 = 36
while off + 9 < uint64(length):
if _rd8(table + off) == 0x5F and _rd8(table + off + 1) == 0x53:
if _rd8(table + off + 2) == 0x35 and _rd8(table + off + 3) == 0x5F:
pkg: uint64 = table + off + 4
if _rd8(pkg) == 0x12:
lead: uint8 = _rd8(pkg + 1)
body: uint64 = pkg + 2 + uint64(lead >> 6)
if _rd8(body) >= 2:
a_addr: uint64 = body + 1
a: uint64 = _aml_int(a_addr)
b_addr: uint64 = a_addr + _aml_int_size(a_addr)
b: uint64 = _aml_int(b_addr)
return (a & 0xFFFF) | ((b & 0xFFFF) << 16)
off = off + 1
return ACPI_S5_NOT_FOUND
def poweroff():
"""Enter ACPI S5 using the PM1 control block advertised by the FADT."""
"""Enter ACPI S5 using the PM1 control blocks and _S5 types."""
serial.puts("[acpi] S5 shutdown\n")
fadt: uint64 = _find_fadt()
if fadt == 0:
serial.puts("[acpi] FADT not found\n")
return
s5: uint64 = _s5_from_table(_find_dsdt(fadt))
if s5 == ACPI_S5_NOT_FOUND:
serial.puts("[acpi] _S5 not found\n")
return
pm1a: uint32 = _rd32(fadt + ACPI_PM1_CNT_PM1A)
pm1b: uint32 = _rd32(fadt + ACPI_PM1_CNT_PM1B)
value: uint16 = (ACPI_SLP_TYP_S5 << 10) | ACPI_SLP_EN
if pm1a == 0 and pm1b == 0:
serial.puts("[acpi] no PM1 control block\n")
return
if pm1a != 0:
asm.outw(uint16(pm1a), value)
asm.outw(uint16(pm1a), uint16(((s5 & 0xFFFF) << 10) | uint64(ACPI_SLP_EN)))
if pm1b != 0:
asm.outw(uint16(pm1b), value)
asm.outw(uint16(pm1b), uint16((((s5 >> 16) & 0xFFFF) << 10) | uint64(ACPI_SLP_EN)))