From e7e1dd91c7951b1747f51c59a423960c9bc54ae4 Mon Sep 17 00:00:00 2001 From: Bananymous Date: Mon, 22 Apr 2024 13:46:24 +0300 Subject: [PATCH] Kernel: Implement ACPI reset --- kernel/include/kernel/ACPI/ACPI.h | 5 + kernel/include/kernel/ACPI/AML/Region.h | 37 ++---- kernel/include/kernel/ACPI/Headers.h | 23 +++- kernel/kernel/ACPI/ACPI.cpp | 146 +++++++++++++++++++++--- kernel/kernel/Process.cpp | 2 +- 5 files changed, 167 insertions(+), 46 deletions(-) diff --git a/kernel/include/kernel/ACPI/ACPI.h b/kernel/include/kernel/ACPI/ACPI.h index 82eabaef..f5bccd48 100644 --- a/kernel/include/kernel/ACPI/ACPI.h +++ b/kernel/include/kernel/ACPI/ACPI.h @@ -32,6 +32,10 @@ namespace Kernel::ACPI // This function will return only if there was an error void poweroff(); + // This function will reset the system + // This function will return only if there was an error + void reset(); + void handle_irq() override; private: @@ -40,6 +44,7 @@ namespace Kernel::ACPI FADT& fadt() { return *m_fadt; } + bool prepare_sleep(uint8_t sleep_state); void acpi_event_task(); private: diff --git a/kernel/include/kernel/ACPI/AML/Region.h b/kernel/include/kernel/ACPI/AML/Region.h index dd2f7dcc..f38c0e0f 100644 --- a/kernel/include/kernel/ACPI/AML/Region.h +++ b/kernel/include/kernel/ACPI/AML/Region.h @@ -9,20 +9,7 @@ namespace Kernel::ACPI::AML struct OpRegion : public NamedObject { - enum class RegionSpace - { - SystemMemory = 0, - SystemIO = 1, - PCIConfig = 2, - EmbeddedController = 3, - SMBus = 4, - SystemCMOS = 5, - PCIBarTarget = 6, - IPMI = 7, - GeneralPurposeIO = 8, - GenericSerialBus = 9, - PCC = 10, - }; + using RegionSpace = GAS::AddressSpaceID; RegionSpace region_space; uint64_t region_offset; uint64_t region_length; @@ -95,17 +82,17 @@ namespace Kernel::ACPI::AML BAN::StringView region_space_name; switch (region_space) { - case RegionSpace::SystemMemory: region_space_name = "SystemMemory"sv; break; - case RegionSpace::SystemIO: region_space_name = "SystemIO"sv; break; - case RegionSpace::PCIConfig: region_space_name = "PCIConfig"sv; break; - case RegionSpace::EmbeddedController: region_space_name = "EmbeddedController"sv; break; - case RegionSpace::SMBus: region_space_name = "SMBus"sv; break; - case RegionSpace::SystemCMOS: region_space_name = "SystemCMOS"sv; break; - case RegionSpace::PCIBarTarget: region_space_name = "PCIBarTarget"sv; break; - case RegionSpace::IPMI: region_space_name = "IPMI"sv; break; - case RegionSpace::GeneralPurposeIO: region_space_name = "GeneralPurposeIO"sv; break; - case RegionSpace::GenericSerialBus: region_space_name = "GenericSerialBus"sv; break; - case RegionSpace::PCC: region_space_name = "PCC"sv; break; + case RegionSpace::SystemMemory: region_space_name = "SystemMemory"sv; break; + case RegionSpace::SystemIO: region_space_name = "SystemIO"sv; break; + case RegionSpace::PCIConfig: region_space_name = "PCIConfig"sv; break; + case RegionSpace::EmbeddedController: region_space_name = "EmbeddedController"sv; break; + case RegionSpace::SMBus: region_space_name = "SMBus"sv; break; + case RegionSpace::SystemCMOS: region_space_name = "SystemCMOS"sv; break; + case RegionSpace::PCIBarTarget: region_space_name = "PCIBarTarget"sv; break; + case RegionSpace::IPMI: region_space_name = "IPMI"sv; break; + case RegionSpace::GeneralPurposeIO: region_space_name = "GeneralPurposeIO"sv; break; + case RegionSpace::GenericSerialBus: region_space_name = "GenericSerialBus"sv; break; + case RegionSpace::PlatformCommunicationChannel: region_space_name = "PlatformCommunicationChannel"sv; break; default: region_space_name = "Unknown"sv; break; } AML_DEBUG_PRINT_INDENT(indent); diff --git a/kernel/include/kernel/ACPI/Headers.h b/kernel/include/kernel/ACPI/Headers.h index 880de38c..cf05cbb9 100644 --- a/kernel/include/kernel/ACPI/Headers.h +++ b/kernel/include/kernel/ACPI/Headers.h @@ -7,12 +7,31 @@ namespace Kernel::ACPI struct GAS { - uint8_t address_space_id; + enum class AddressSpaceID : uint8_t + { + SystemMemory = 0x00, + SystemIO = 0x01, + PCIConfig = 0x02, + EmbeddedController = 0x03, + SMBus = 0x04, + SystemCMOS = 0x05, + PCIBarTarget = 0x06, + IPMI = 0x07, + GeneralPurposeIO = 0x08, + GenericSerialBus = 0x09, + PlatformCommunicationChannel = 0x0A, + }; + + BAN::Optional read(); + bool write(uint64_t value); + + AddressSpaceID address_space_id; uint8_t register_bit_width; uint8_t register_bit_offset; uint8_t access_size; uint64_t address; } __attribute__((packed)); + static_assert(sizeof(GAS) == 12); struct SDTHeader { @@ -67,7 +86,7 @@ namespace Kernel::ACPI uint16_t iapc_boot_arch; uint8_t __reserved2; uint32_t flags; - uint8_t reset_reg[12]; + GAS reset_reg; uint8_t reset_value; uint16_t arm_boot_arch; uint8_t fadt_minor_version; diff --git a/kernel/kernel/ACPI/ACPI.cpp b/kernel/kernel/ACPI/ACPI.cpp index a1a2d41f..31c17740 100644 --- a/kernel/kernel/ACPI/ACPI.cpp +++ b/kernel/kernel/ACPI/ACPI.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -110,6 +111,66 @@ acpi_release_global_lock: ASSERT(!acpi_release_global_lock(s_global_lock)); } + static BAN::Optional get_access_type(uint8_t access_size) + { + switch (access_size) + { + case 0: return AML::FieldRules::AccessType::Any; + case 1: return AML::FieldRules::AccessType::Byte; + case 2: return AML::FieldRules::AccessType::Word; + case 3: return AML::FieldRules::AccessType::DWord; + case 4: return AML::FieldRules::AccessType::QWord; + default: + dwarnln("Unknown access size {}", access_size); + return {}; + } + } + + BAN::Optional GAS::read() + { + auto access_type = get_access_type(access_size); + if (!access_type.has_value()) + return {}; + + auto op_region = MUST(BAN::RefPtr::create(""sv, address_space_id, (uint64_t)address, 0xFFFFFFFF)); + + auto field_rules = AML::FieldRules { + .access_type = access_type.value(), + .lock_rule = AML::FieldRules::LockRule::NoLock, + .update_rule = AML::FieldRules::UpdateRule::Preserve, + .access_attrib = AML::FieldRules::AccessAttrib::Normal, + .access_length = 0 + }; + auto field_element = MUST(BAN::RefPtr::create(""sv, register_bit_offset, register_bit_width, field_rules)); + field_element->op_region = op_region; + + auto result = field_element->as_integer(); + if (!result.has_value()) + return {}; + return result.value(); + } + + bool GAS::write(uint64_t value) + { + auto access_type = get_access_type(access_size); + if (!access_type.has_value()) + return {}; + + auto op_region = MUST(BAN::RefPtr::create(""sv, address_space_id, (uint64_t)address, 0xFFFFFFFF)); + + auto field_rules = AML::FieldRules { + .access_type = access_type.value(), + .lock_rule = AML::FieldRules::LockRule::NoLock, + .update_rule = AML::FieldRules::UpdateRule::Preserve, + .access_attrib = AML::FieldRules::AccessAttrib::Normal, + .access_length = 0 + }; + auto field_element = MUST(BAN::RefPtr::create(""sv, register_bit_offset, register_bit_width, field_rules)); + field_element->op_region = op_region; + + return field_element->store(MUST(BAN::RefPtr::create(value))); + } + enum PM1Event : uint16_t { PM1_EVN_TMR = 1 << 0, @@ -395,6 +456,30 @@ acpi_release_global_lock: return nullptr; } + bool ACPI::prepare_sleep(uint8_t sleep_state) + { + auto pts_object = m_namespace->find_object({}, AML::NameString("_PTS"), AML::Namespace::FindMode::ForceAbsolute); + if (pts_object && pts_object->type == AML::Node::Type::Method) + { + auto* method = static_cast(pts_object.ptr()); + if (method->arg_count != 1) + { + dwarnln("Method \\_PTS has {} arguments, expected 1", method->arg_count); + return false; + } + + if (!method->invoke(MUST(BAN::RefPtr::create(sleep_state))).has_value()) + { + dwarnln("Failed to evaluate \\_PTS"); + return false; + } + + dprintln("Executed \\_PTS"); + } + + return true; + } + void ACPI::poweroff() { if (!m_namespace) @@ -435,24 +520,8 @@ acpi_release_global_lock: return; } - auto pts_object = m_namespace->find_object({}, AML::NameString("_PTS"), AML::Namespace::FindMode::ForceAbsolute); - if (pts_object && pts_object->type == AML::Node::Type::Method) - { - auto* method = static_cast(pts_object.ptr()); - if (method->arg_count != 1) - { - dwarnln("Method \\_PTS has {} arguments, expected 1", method->arg_count); - return; - } - - if (!method->invoke(MUST(BAN::RefPtr::create(5))).has_value()) - { - dwarnln("Failed to evaluate \\_PTS"); - return; - } - - dprintln("Executed \\_PTS"); - } + if (!prepare_sleep(5)) + return; dprintln("Entering sleep state S5"); @@ -470,6 +539,47 @@ acpi_release_global_lock: pm1b_data |= PM1_CNT_SLP_EN; IO::outw(fadt().pm1b_cnt_blk, pm1b_data); } + + // system must not execute after sleep registers are written + g_paniced = true; + asm volatile("ud2"); + } + + void ACPI::reset() + { + // https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/04_ACPI_Hardware_Specification/ACPI_Hardware_Specification.html#reset-register + + auto& reset_reg = fadt().reset_reg; + switch (reset_reg.address_space_id) + { + case GAS::AddressSpaceID::SystemMemory: + case GAS::AddressSpaceID::SystemIO: + case GAS::AddressSpaceID::PCIConfig: + break; + default: + dwarnln("Reset register has invalid address space ID ({})", static_cast(reset_reg.address_space_id)); + return; + } + if (reset_reg.register_bit_offset != 0 || reset_reg.register_bit_width != 8) + { + dwarnln("Reset register has invalid location ({} bits at bit offset {})", reset_reg.register_bit_width, reset_reg.register_bit_offset); + return; + } + + if (!prepare_sleep(5)) + return; + + dprintln("Resetting system"); + + if (!reset_reg.write(fadt().reset_value)) + { + dwarnln("Could not write reset value"); + return; + } + + // system must not execute after reset register is written + g_paniced = true; + asm volatile("ud2"); } BAN::ErrorOr ACPI::enter_acpi_mode(uint8_t mode) diff --git a/kernel/kernel/Process.cpp b/kernel/kernel/Process.cpp index c388c844..926e19af 100644 --- a/kernel/kernel/Process.cpp +++ b/kernel/kernel/Process.cpp @@ -1190,7 +1190,7 @@ namespace Kernel [[noreturn]] static void reset_system() { - // TODO: ACPI reset + ACPI::ACPI::get().reset(); dwarnln("Could not reset with ACPI, crashing the cpu");