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
|
// 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:
|
||||||
|
|
|
@ -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;
|
||||||
|
@ -105,7 +92,7 @@ namespace Kernel::ACPI::AML
|
||||||
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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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)
|
|
||||||
{
|
|
||||||
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;
|
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)
|
||||||
|
|
|
@ -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");
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue