Kernel: Implement ACPI reset
This commit is contained in:
parent
195c5e92a4
commit
e7e1dd91c7
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
@ -105,7 +92,7 @@ namespace Kernel::ACPI::AML
|
|||
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::PlatformCommunicationChannel: region_space_name = "PlatformCommunicationChannel"sv; break;
|
||||
default: region_space_name = "Unknown"sv; break;
|
||||
}
|
||||
AML_DEBUG_PRINT_INDENT(indent);
|
||||
|
|
|
@ -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<uint64_t> 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;
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include <kernel/ACPI/ACPI.h>
|
||||
#include <kernel/ACPI/AML.h>
|
||||
#include <kernel/ACPI/AML/Device.h>
|
||||
#include <kernel/ACPI/AML/Field.h>
|
||||
#include <kernel/ACPI/AML/Integer.h>
|
||||
#include <kernel/ACPI/AML/Method.h>
|
||||
#include <kernel/ACPI/AML/Package.h>
|
||||
|
@ -110,6 +111,66 @@ acpi_release_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
|
||||
{
|
||||
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<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()
|
||||
{
|
||||
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<AML::Method*>(pts_object.ptr());
|
||||
if (method->arg_count != 1)
|
||||
{
|
||||
dwarnln("Method \\_PTS has {} arguments, expected 1", method->arg_count);
|
||||
if (!prepare_sleep(5))
|
||||
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");
|
||||
|
||||
|
@ -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<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)
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
Loading…
Reference in New Issue