Kernel: Implement ACPI shutdown

This commit is contained in:
Undefine authored and Sönke Holz committed 2026-11-07 13:27:30 +01:00
1 parent b2c515561a
commit 05ec8eddae
3 files changed
+118 -3

No files matched your search

+102 -2
View File
@@ -213,9 +213,109 @@ void Parser::try_acpi_reboot()
Processor::halt();
}
void Parser::try_acpi_shutdown()
// https://uefi.org/specs/ACPI/6.6/16_Waking_and_Sleeping.html#transitioning-from-the-working-to-the-soft-off-state
ErrorOr<void> Parser::try_acpi_shutdown()
{
dmesgln("ACPI: Shutdown is not supported with the current configuration, aborting!");
if (!m_aml_interpreter) {
dmesgln("ACPI: AML interpreter not available");
return Error::from_errno(ENOTSUP);
}
auto fadt = TRY(Memory::map_typed<::ACPI::Structures::FADT>(m_fadt));
// NOTE: We need to check the length, because older ACPI revision might not have those fields
auto pm1a_control = TRY(get_pm1a_control());
auto pm1b_control = TRY(get_pm1b_control());
bool sleep_control_available = fadt->h.length >= offsetof(::ACPI::Structures::FADT, sleep_control) + sizeof(fadt->sleep_control) && !fadt->sleep_control.is_null();
auto s5_values = TRY(extract_s5_values());
// FIXME: 1. OSPM executes the _PTS control method, passing the argument 5.
// 2. OSPM prepares its components to shut down (flushing disk caches).
// NOTE: This step is done ahead of this function call
// 3. If not a HW-reduced ACPI platform, OSPM writes SLP_TYPa (from the \_S5 object) with the SLP_ENa bit set to the PM1a_CNT register.
if (!m_hardware_flags.hardware_reduced_acpi) {
if (!pm1a_control.has_value()) {
dmesgln("ACPI: PM1a control register not available");
return Error::from_errno(ENOTSUP);
}
::ACPI::Structures::PM1ControlRegister shutdown_value = bit_cast<::ACPI::Structures::PM1ControlRegister>(static_cast<u16>(TRY(read_generic_address(*pm1a_control))));
shutdown_value.SLP_EN = true;
shutdown_value.SLP_TYPx = s5_values.SLP_TYPa;
TRY(write_generic_address(*pm1a_control, bit_cast<u16>(shutdown_value)));
}
// 4. OSPM writes SLP_TYPb (from the \_S5 object) with the SLP_ENb bit set to the PM1b_CNT register, or writes the HW-reduced ACPI Sleep
// Type value for S5 and the SLP_EN bit to the Sleep Control Register.
if (m_hardware_flags.hardware_reduced_acpi) {
if (!sleep_control_available) {
dmesgln("ACPI: Sleep control register not available");
return Error::from_errno(ENOTSUP);
}
auto shutdown_value = bit_cast<::ACPI::Structures::SleepControlRegister>(static_cast<u8>(TRY(read_generic_address(fadt->sleep_control))));
shutdown_value.SLP_EN = true;
shutdown_value.SLP_TYPx = s5_values.SLP_TYPa;
TRY(write_generic_address(fadt->sleep_control, bit_cast<u8>(shutdown_value)));
} else if (pm1b_control.has_value()) {
// PM1b control register is optional, and doesn't need to be present for the system to support S5 sleep state.
auto shutdown_value = bit_cast<::ACPI::Structures::PM1ControlRegister>(static_cast<u16>(TRY(read_generic_address(*pm1b_control))));
shutdown_value.SLP_EN = true;
shutdown_value.SLP_TYPx = s5_values.SLP_TYPb;
TRY(write_generic_address(*pm1b_control, bit_cast<u16>(shutdown_value)));
}
// 5. The system enters the Soft Off state.
Processor::halt();
}
ErrorOr<Parser::S5Values> Parser::extract_s5_values()
{
auto s5_node = TRY(m_aml_interpreter->evaluate("\\_S5_"sv));
auto s5_package = TRY(s5_node.release_as<::ACPI::AML::Package>());
if (s5_package.package().size() < 2)
return Error::from_errno(EINVAL);
u16 SLP_TYPa = TRY(s5_package.package()[0].as<u64>());
u16 SLP_TYPb = TRY(s5_package.package()[1].as<u64>());
return Parser::S5Values { SLP_TYPa, SLP_TYPb };
}
ErrorOr<Optional<::ACPI::Structures::GenericAddressStructure>> Parser::get_pm1a_control()
{
auto fadt = TRY(Memory::map_typed<::ACPI::Structures::FADT>(m_fadt));
if (fadt->h.length >= offsetof(::ACPI::Structures::FADT, x_pm1a_cnt_blk) + sizeof(fadt->x_pm1a_cnt_blk) && !fadt->x_pm1a_cnt_blk.is_null())
return fadt->x_pm1a_cnt_blk;
#if ARCH(X86_64)
if (fadt->PM1a_CNT_BLK != 0)
return make_generic_address_from_io_port(fadt->PM1a_CNT_BLK);
#endif
return OptionalNone {};
}
ErrorOr<Optional<::ACPI::Structures::GenericAddressStructure>> Parser::get_pm1b_control()
{
auto fadt = TRY(Memory::map_typed<::ACPI::Structures::FADT>(m_fadt));
if (fadt->h.length >= offsetof(::ACPI::Structures::FADT, x_pm1b_cnt_blk) + sizeof(fadt->x_pm1b_cnt_blk) && !fadt->x_pm1b_cnt_blk.is_null())
return fadt->x_pm1b_cnt_blk;
#if ARCH(X86_64)
if (fadt->PM1b_CNT_BLK != 0)
return make_generic_address_from_io_port(fadt->PM1b_CNT_BLK);
#endif
return OptionalNone {};
}
ErrorOr<size_t> Parser::get_table_size(PhysicalAddress table_header)
+10 -1
View File
@@ -31,7 +31,7 @@ public:
void try_acpi_reboot();
bool can_reboot();
void try_acpi_shutdown();
ErrorOr<void> try_acpi_shutdown();
PhysicalAddress rsdp() const { return m_rsdp; }
PhysicalAddress main_system_description_table() const { return m_main_system_description_table; }
@@ -87,6 +87,15 @@ private:
bool validate_reset_register(Memory::TypedMapping<::ACPI::Structures::FADT> const&);
struct S5Values {
u16 SLP_TYPa;
u16 SLP_TYPb;
};
ErrorOr<S5Values> extract_s5_values();
ErrorOr<Optional<::ACPI::Structures::GenericAddressStructure>> get_pm1a_control();
ErrorOr<Optional<::ACPI::Structures::GenericAddressStructure>> get_pm1b_control();
OwnPtr<InterruptHandler> m_interrupt_handler;
PhysicalAddress m_rsdp;
+6
View File
@@ -128,7 +128,13 @@ ErrorOr<void> PowerStateSwitchTask::perform_shutdown(PowerStateSwitchTask::DoReb
VERIFY(do_reboot == DoReboot::No);
dbgln("Attempting system shutdown...");
if (ACPI::is_enabled()) {
dbgln("Attempting shutdown via ACPI");
if (auto maybe_error = ACPI::Parser::the()->try_acpi_shutdown(); maybe_error.is_error())
dmesgln("ACPI: Failed to shutdown: {}", maybe_error.release_error());
}
arch_specific_poweroff(PowerOffOrRebootReason::NoReason);
dmesgln("Shutdown can't be completed. It's safe to turn off the computer!");
Processor::halt();
VERIFY_NOT_REACHED();