Kernel: Implement ACPI reset

This commit is contained in:
Bananymous 2024-04-22 13:46:24 +03:00
parent 195c5e92a4
commit e7e1dd91c7
5 changed files with 167 additions and 46 deletions

View File

@ -32,6 +32,10 @@ namespace Kernel::ACPI
// This function will return only if there was an error // This function will return only if there was an error
void poweroff(); void poweroff();
// This function will reset the system
// This function will return only if there was an error
void reset();
void handle_irq() override; void handle_irq() override;
private: private:
@ -40,6 +44,7 @@ namespace Kernel::ACPI
FADT& fadt() { return *m_fadt; } FADT& fadt() { return *m_fadt; }
bool prepare_sleep(uint8_t sleep_state);
void acpi_event_task(); void acpi_event_task();
private: private:

View File

@ -9,20 +9,7 @@ namespace Kernel::ACPI::AML
struct OpRegion : public NamedObject struct OpRegion : public NamedObject
{ {
enum class RegionSpace using RegionSpace = GAS::AddressSpaceID;
{
SystemMemory = 0,
SystemIO = 1,
PCIConfig = 2,
EmbeddedController = 3,
SMBus = 4,
SystemCMOS = 5,
PCIBarTarget = 6,
IPMI = 7,
GeneralPurposeIO = 8,
GenericSerialBus = 9,
PCC = 10,
};
RegionSpace region_space; RegionSpace region_space;
uint64_t region_offset; uint64_t region_offset;
uint64_t region_length; uint64_t region_length;
@ -95,17 +82,17 @@ namespace Kernel::ACPI::AML
BAN::StringView region_space_name; BAN::StringView region_space_name;
switch (region_space) switch (region_space)
{ {
case RegionSpace::SystemMemory: region_space_name = "SystemMemory"sv; break; case RegionSpace::SystemMemory: region_space_name = "SystemMemory"sv; break;
case RegionSpace::SystemIO: region_space_name = "SystemIO"sv; break; case RegionSpace::SystemIO: region_space_name = "SystemIO"sv; break;
case RegionSpace::PCIConfig: region_space_name = "PCIConfig"sv; break; case RegionSpace::PCIConfig: region_space_name = "PCIConfig"sv; break;
case RegionSpace::EmbeddedController: region_space_name = "EmbeddedController"sv; break; case RegionSpace::EmbeddedController: region_space_name = "EmbeddedController"sv; break;
case RegionSpace::SMBus: region_space_name = "SMBus"sv; break; case RegionSpace::SMBus: region_space_name = "SMBus"sv; break;
case RegionSpace::SystemCMOS: region_space_name = "SystemCMOS"sv; break; case RegionSpace::SystemCMOS: region_space_name = "SystemCMOS"sv; break;
case RegionSpace::PCIBarTarget: region_space_name = "PCIBarTarget"sv; break; case RegionSpace::PCIBarTarget: region_space_name = "PCIBarTarget"sv; break;
case RegionSpace::IPMI: region_space_name = "IPMI"sv; break; case RegionSpace::IPMI: region_space_name = "IPMI"sv; break;
case RegionSpace::GeneralPurposeIO: region_space_name = "GeneralPurposeIO"sv; break; case RegionSpace::GeneralPurposeIO: region_space_name = "GeneralPurposeIO"sv; break;
case RegionSpace::GenericSerialBus: region_space_name = "GenericSerialBus"sv; break; case RegionSpace::GenericSerialBus: region_space_name = "GenericSerialBus"sv; break;
case RegionSpace::PCC: region_space_name = "PCC"sv; break; case RegionSpace::PlatformCommunicationChannel: region_space_name = "PlatformCommunicationChannel"sv; break;
default: region_space_name = "Unknown"sv; break; default: region_space_name = "Unknown"sv; break;
} }
AML_DEBUG_PRINT_INDENT(indent); AML_DEBUG_PRINT_INDENT(indent);

View File

@ -7,12 +7,31 @@ namespace Kernel::ACPI
struct GAS 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<uint64_t> read();
bool write(uint64_t value);
AddressSpaceID address_space_id;
uint8_t register_bit_width; uint8_t register_bit_width;
uint8_t register_bit_offset; uint8_t register_bit_offset;
uint8_t access_size; uint8_t access_size;
uint64_t address; uint64_t address;
} __attribute__((packed)); } __attribute__((packed));
static_assert(sizeof(GAS) == 12);
struct SDTHeader struct SDTHeader
{ {
@ -67,7 +86,7 @@ namespace Kernel::ACPI
uint16_t iapc_boot_arch; uint16_t iapc_boot_arch;
uint8_t __reserved2; uint8_t __reserved2;
uint32_t flags; uint32_t flags;
uint8_t reset_reg[12]; GAS reset_reg;
uint8_t reset_value; uint8_t reset_value;
uint16_t arm_boot_arch; uint16_t arm_boot_arch;
uint8_t fadt_minor_version; uint8_t fadt_minor_version;

View File

@ -3,6 +3,7 @@
#include <kernel/ACPI/ACPI.h> #include <kernel/ACPI/ACPI.h>
#include <kernel/ACPI/AML.h> #include <kernel/ACPI/AML.h>
#include <kernel/ACPI/AML/Device.h> #include <kernel/ACPI/AML/Device.h>
#include <kernel/ACPI/AML/Field.h>
#include <kernel/ACPI/AML/Integer.h> #include <kernel/ACPI/AML/Integer.h>
#include <kernel/ACPI/AML/Method.h> #include <kernel/ACPI/AML/Method.h>
#include <kernel/ACPI/AML/Package.h> #include <kernel/ACPI/AML/Package.h>
@ -110,6 +111,66 @@ acpi_release_global_lock:
ASSERT(!acpi_release_global_lock(s_global_lock)); ASSERT(!acpi_release_global_lock(s_global_lock));
} }
static BAN::Optional<AML::FieldRules::AccessType> 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<uint64_t> GAS::read()
{
auto access_type = get_access_type(access_size);
if (!access_type.has_value())
return {};
auto op_region = MUST(BAN::RefPtr<AML::OpRegion>::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<AML::FieldElement>::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<AML::OpRegion>::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<AML::FieldElement>::create(""sv, register_bit_offset, register_bit_width, field_rules));
field_element->op_region = op_region;
return field_element->store(MUST(BAN::RefPtr<AML::Integer>::create(value)));
}
enum PM1Event : uint16_t enum PM1Event : uint16_t
{ {
PM1_EVN_TMR = 1 << 0, PM1_EVN_TMR = 1 << 0,
@ -395,6 +456,30 @@ acpi_release_global_lock:
return nullptr; 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<AML::Method*>(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<AML::Integer>::create(sleep_state))).has_value())
{
dwarnln("Failed to evaluate \\_PTS");
return false;
}
dprintln("Executed \\_PTS");
}
return true;
}
void ACPI::poweroff() void ACPI::poweroff()
{ {
if (!m_namespace) if (!m_namespace)
@ -435,24 +520,8 @@ acpi_release_global_lock:
return; return;
} }
auto pts_object = m_namespace->find_object({}, AML::NameString("_PTS"), AML::Namespace::FindMode::ForceAbsolute); if (!prepare_sleep(5))
if (pts_object && pts_object->type == AML::Node::Type::Method) return;
{
auto* method = static_cast<AML::Method*>(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<AML::Integer>::create(5))).has_value())
{
dwarnln("Failed to evaluate \\_PTS");
return;
}
dprintln("Executed \\_PTS");
}
dprintln("Entering sleep state S5"); dprintln("Entering sleep state S5");
@ -470,6 +539,47 @@ acpi_release_global_lock:
pm1b_data |= PM1_CNT_SLP_EN; pm1b_data |= PM1_CNT_SLP_EN;
IO::outw(fadt().pm1b_cnt_blk, pm1b_data); 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<uint8_t>(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<void> ACPI::enter_acpi_mode(uint8_t mode) BAN::ErrorOr<void> ACPI::enter_acpi_mode(uint8_t mode)

View File

@ -1190,7 +1190,7 @@ namespace Kernel
[[noreturn]] static void reset_system() [[noreturn]] static void reset_system()
{ {
// TODO: ACPI reset ACPI::ACPI::get().reset();
dwarnln("Could not reset with ACPI, crashing the cpu"); dwarnln("Could not reset with ACPI, crashing the cpu");