/* * AcpiShutdown.cpp * ACPI S5 (soft-off) shutdown via PM1 control registers * Copyright (c) 2026 Daniel Hammer */ #include "AcpiShutdown.hpp" #include #include "AcpiSleep.hpp" #include "AcpiEvents.hpp" #include #include #include #include #include #include #include #include using namespace Kt; namespace Hal { namespace AcpiShutdown { static constexpr uint16_t SLP_EN = 1 << 13; static constexpr uint16_t SLP_TYP_MASK = 0x1C00; // bits 10-12 static bool g_available = false; static uint32_t g_pm1aControlBlock = 0; static uint32_t g_pm1bControlBlock = 0; static uint16_t g_slpTypA = 0; static uint16_t g_slpTypB = 0; void Initialize(ACPI::CommonSDTHeader* xsdt) { FADT::ParsedFADT fadt{}; if (!FADT::Parse(xsdt, fadt) || !fadt.Valid) { KernelLogStream(ERROR, "ACPI") << "Failed to parse FADT - ACPI shutdown unavailable"; return; } auto* dsdt = (void*)Memory::HHDM(fadt.DsdtAddress); AML::InitializeInterpreter(dsdt, xsdt); if (AML::GetInterpreter().IsInitialized()) { AcpiDevices::DeviceList devices{}; AcpiDevices::EnumerateAll(devices); } AcpiSleep::Initialize(xsdt); // Hardware-reduced ACPI (ARM) has no PM1 registers: the // architecture powers off instead (Arch::PowerOff). if ((fadt.Flags & FADT::FlagHardwareReduced) || fadt.PM1aControlBlock == 0) { KernelLogStream(INFO, "ACPI") << "No PM1 control block - ACPI S5 shutdown unavailable"; return; } AML::S5Object s5 = AML::FindS5(dsdt); if (!s5.Valid) { KernelLogStream(ERROR, "ACPI") << "Could not find \\_S5_ in DSDT - ACPI shutdown unavailable"; return; } g_pm1aControlBlock = fadt.PM1aControlBlock; g_pm1bControlBlock = fadt.PM1bControlBlock; g_slpTypA = s5.SLP_TYPa; g_slpTypB = s5.SLP_TYPb; g_available = true; KernelLogStream(OK, "ACPI") << "ACPI shutdown initialized (PM1a=" << base::hex << (uint64_t)g_pm1aControlBlock << " SLP_TYPa=" << (uint64_t)g_slpTypA << ")"; } bool IsAvailable() { return g_available; } void Shutdown() { if (!g_available) return; KernelLogStream(INFO, "ACPI") << "Performing ACPI S5 shutdown..."; // Evaluate _PTS(5) — Prepare To Sleep (S5 = soft-off) auto& interp = AML::GetInterpreter(); if (interp.IsInitialized()) { int32_t node = interp.GetNamespace().FindNode("\\_PTS"); if (node >= 0) { AML::Object arg{}; arg.Type = AML::ObjectType::Integer; arg.Integer = 5; AML::Object result{}; interp.EvaluateMethod("\\_PTS", &arg, 1, result); } } Arch::DisableInterrupts(); // Phase 1: Write SLP_TYP to PM1x_CNT without SLP_EN, // preserving existing register bits uint16_t pm1a = Io::In16((uint16_t)g_pm1aControlBlock); pm1a = (pm1a & ~SLP_TYP_MASK) | (g_slpTypA << 10); Io::Out16(pm1a, (uint16_t)g_pm1aControlBlock); if (g_pm1bControlBlock != 0) { uint16_t pm1b = Io::In16((uint16_t)g_pm1bControlBlock); pm1b = (pm1b & ~SLP_TYP_MASK) | (g_slpTypB << 10); Io::Out16(pm1b, (uint16_t)g_pm1bControlBlock); } // Flush all caches before entering sleep state Arch::WritebackInvalidateCaches(); // Phase 2: Assert SLP_EN to trigger the transition Io::Out16(pm1a | SLP_EN, (uint16_t)g_pm1aControlBlock); if (g_pm1bControlBlock != 0) { uint16_t pm1b = Io::In16((uint16_t)g_pm1bControlBlock); Io::Out16(pm1b | SLP_EN, (uint16_t)g_pm1bControlBlock); } // Halt and wait for the hardware to power off for (;;) { Arch::Halt(); } } }; };