854 lines
22 KiB
C++
854 lines
22 KiB
C++
#include <BAN/ScopeGuard.h>
|
|
#include <BAN/StringView.h>
|
|
#include <kernel/ACPI/ACPI.h>
|
|
#include <kernel/ACPI/BatterySystem.h>
|
|
#include <kernel/ACPI/AML/OpRegion.h>
|
|
#include <kernel/BootInfo.h>
|
|
#include <kernel/InterruptController.h>
|
|
#include <kernel/IO.h>
|
|
#include <kernel/Memory/PageTable.h>
|
|
#include <kernel/Process.h>
|
|
#include <kernel/Timer/Timer.h>
|
|
|
|
#define RSPD_SIZE 20
|
|
#define RSPDv2_SIZE 36
|
|
|
|
namespace Kernel::ACPI
|
|
{
|
|
|
|
static uint32_t* s_global_lock { nullptr };
|
|
|
|
// https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/05_ACPI_Software_Programming_Model/ACPI_Software_Programming_Model.html#global-lock
|
|
#if ARCH(x86_64)
|
|
asm(R"(
|
|
.global acpi_acquire_global_lock
|
|
acpi_acquire_global_lock:
|
|
movl (%rdi), %edx
|
|
andl $(~1), %edx
|
|
btsl $1, %edx
|
|
adcl $0, %edx
|
|
|
|
lock cmpxchgl %edx, (%rdi)
|
|
jnz acpi_acquire_global_lock
|
|
|
|
cmpb $3, %dl
|
|
sbbq %rax, %rax
|
|
negq %rax
|
|
|
|
ret
|
|
|
|
.global acpi_release_global_lock
|
|
acpi_release_global_lock:
|
|
movl (%rdi), %eax
|
|
movl %eax, %edx
|
|
|
|
andl $(~3), %edx
|
|
|
|
lock cmpxchgl %edx, (%rdi)
|
|
jnz acpi_release_global_lock
|
|
|
|
andq $1, %rax
|
|
|
|
ret
|
|
)");
|
|
#elif ARCH(i686)
|
|
asm(R"(
|
|
.global acpi_acquire_global_lock
|
|
acpi_acquire_global_lock:
|
|
movl 4(%esp), %ecx
|
|
movl (%ecx), %edx
|
|
andl $(~1), %edx
|
|
btsl $1, %edx
|
|
adcl $0, %edx
|
|
|
|
lock cmpxchgl %edx, (%ecx)
|
|
jnz acpi_acquire_global_lock
|
|
|
|
cmpb $3, %dl
|
|
sbbl %eax, %eax
|
|
negl %eax
|
|
|
|
ret
|
|
|
|
.global acpi_release_global_lock
|
|
acpi_release_global_lock:
|
|
movl 4(%esp), %ecx
|
|
movl (%ecx), %eax
|
|
movl %eax, %edx
|
|
|
|
andl $(~3), %edx
|
|
|
|
lock cmpxchgl %edx, (%ecx)
|
|
jnz acpi_release_global_lock
|
|
|
|
andl $1, %eax
|
|
|
|
ret
|
|
)");
|
|
#endif
|
|
|
|
// returns true if lock was acquired successfully
|
|
extern "C" bool acpi_acquire_global_lock(uint32_t* lock);
|
|
|
|
// returns true if lock was pending
|
|
extern "C" bool acpi_release_global_lock(uint32_t* lock);
|
|
|
|
void ACPI::acquire_global_lock()
|
|
{
|
|
if (!s_global_lock)
|
|
return;
|
|
ASSERT(acpi_acquire_global_lock(s_global_lock));
|
|
}
|
|
|
|
void ACPI::release_global_lock()
|
|
{
|
|
if (!s_global_lock)
|
|
return;
|
|
ASSERT(!acpi_release_global_lock(s_global_lock));
|
|
}
|
|
|
|
static BAN::ErrorOr<uint8_t> get_access_type(uint8_t access_size)
|
|
{
|
|
switch (access_size)
|
|
{
|
|
case 0: return 0;
|
|
case 1: return 1;
|
|
case 2: return 2;
|
|
case 3: return 3;
|
|
case 4: return 4;
|
|
default:
|
|
dwarnln("Unknown access size {}", access_size);
|
|
return BAN::Error::from_errno(EFAULT);
|
|
}
|
|
}
|
|
|
|
BAN::ErrorOr<uint64_t> GAS::read()
|
|
{
|
|
AML::OpRegion opregion;
|
|
opregion.address_space = address_space_id;
|
|
opregion.offset = address;
|
|
opregion.length = 0xFFFFFFFF;
|
|
|
|
AML::Node field_unit;
|
|
field_unit.type = AML::Node::Type::FieldUnit;
|
|
field_unit.as.field_unit.type = AML::FieldUnit::Type::Field;
|
|
field_unit.as.field_unit.as.field.opregion = opregion;
|
|
field_unit.as.field_unit.length = register_bit_width;
|
|
field_unit.as.field_unit.offset = register_bit_offset;
|
|
field_unit.as.field_unit.flags = TRY(get_access_type(access_size));
|
|
|
|
auto result = TRY(AML::convert_from_field_unit(field_unit, AML::ConvInteger, sizeof(uint64_t)));
|
|
return result.as.integer.value;
|
|
}
|
|
|
|
BAN::ErrorOr<void> GAS::write(uint64_t value)
|
|
{
|
|
AML::OpRegion opregion;
|
|
opregion.address_space = address_space_id;
|
|
opregion.offset = address;
|
|
opregion.length = 0xFFFFFFFF;
|
|
|
|
AML::Node field_unit;
|
|
field_unit.type = AML::Node::Type::FieldUnit;
|
|
field_unit.as.field_unit.type = AML::FieldUnit::Type::Field;
|
|
field_unit.as.field_unit.as.field.opregion = opregion;
|
|
field_unit.as.field_unit.length = register_bit_width;
|
|
field_unit.as.field_unit.offset = register_bit_offset;
|
|
field_unit.as.field_unit.flags = TRY(get_access_type(access_size));
|
|
|
|
AML::Node source;
|
|
source.type = AML::Node::Type::Integer;
|
|
source.as.integer.value = value;
|
|
|
|
TRY(AML::store_to_field_unit(source, field_unit));
|
|
|
|
return {};
|
|
}
|
|
|
|
enum PM1Event : uint16_t
|
|
{
|
|
PM1_EVN_TMR = 1 << 0,
|
|
PM1_EVN_GBL = 1 << 5,
|
|
PM1_EVN_PWRBTN = 1 << 8,
|
|
PM1_EVN_SLPBTN = 1 << 8,
|
|
PM1_EVN_RTC = 1 << 10,
|
|
PM1_EVN_PCIEXP_WAKE_DIS = 1 << 14,
|
|
};
|
|
|
|
enum PM1Control : uint16_t
|
|
{
|
|
PM1_CNT_SCI_EN = 1 << 0,
|
|
PM1_CNT_BM_RLD = 1 << 1,
|
|
PM1_CNT_GBL_RLS = 1 << 2,
|
|
PM1_CNT_SLP_EN = 1 << 13,
|
|
|
|
PM1_CNT_SLP_TYP_MASK = 0b111,
|
|
PM1_CNT_SLP_TYP_SHIFT = 10,
|
|
};
|
|
|
|
struct RSDT : public SDTHeader
|
|
{
|
|
uint32_t entries[];
|
|
} __attribute__((packed));
|
|
|
|
struct XSDT : public SDTHeader
|
|
{
|
|
uint64_t entries[];
|
|
} __attribute__((packed));
|
|
|
|
static ACPI* s_instance = nullptr;
|
|
|
|
BAN::ErrorOr<void> ACPI::initialize()
|
|
{
|
|
ASSERT(s_instance == nullptr);
|
|
s_instance = new ACPI;
|
|
if (s_instance == nullptr)
|
|
return BAN::Error::from_errno(ENOMEM);
|
|
TRY(s_instance->initialize_impl());
|
|
|
|
{
|
|
ASSERT(!s_global_lock);
|
|
const auto* fadt = static_cast<const FADT*>(ACPI::get().get_header("FACP"_sv, 0));
|
|
ASSERT(fadt);
|
|
|
|
uintptr_t facs_addr = fadt->firmware_ctrl;
|
|
if (fadt->length >= sizeof(FADT) && fadt->x_firmware_ctrl)
|
|
facs_addr = fadt->x_firmware_ctrl;
|
|
|
|
if (facs_addr)
|
|
{
|
|
size_t facs_size;
|
|
PageTable::with_fast_page(facs_addr & PAGE_ADDR_MASK, [&] {
|
|
facs_size = PageTable::fast_page_as<SDTHeader>(facs_addr % PAGE_SIZE).length;
|
|
});
|
|
|
|
size_t needed_pages = range_page_count(facs_addr, facs_size);
|
|
vaddr_t facs_vaddr = PageTable::kernel().reserve_free_contiguous_pages(needed_pages, KERNEL_OFFSET);
|
|
ASSERT(facs_vaddr);
|
|
|
|
PageTable::kernel().map_range_at(
|
|
facs_addr & PAGE_ADDR_MASK,
|
|
facs_vaddr,
|
|
needed_pages * PAGE_SIZE,
|
|
PageTable::Flags::ReadWrite | PageTable::Flags::Present
|
|
);
|
|
|
|
auto* facs = reinterpret_cast<FACS*>(facs_vaddr + (facs_addr % PAGE_SIZE));
|
|
s_global_lock = &facs->global_lock;
|
|
}
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
ACPI& ACPI::get()
|
|
{
|
|
ASSERT(s_instance != nullptr);
|
|
return *s_instance;
|
|
}
|
|
|
|
static bool is_rsdp(vaddr_t rsdp_addr)
|
|
{
|
|
const RSDP* rsdp = (const RSDP*)rsdp_addr;
|
|
|
|
if (memcmp(rsdp->signature, "RSD PTR ", 8) != 0)
|
|
return false;
|
|
|
|
{
|
|
uint8_t checksum = 0;
|
|
for (uint32_t i = 0; i < RSPD_SIZE; i++)
|
|
checksum += ((const uint8_t*)rsdp)[i];
|
|
if (checksum != 0)
|
|
return false;
|
|
}
|
|
|
|
if (rsdp->revision == 2)
|
|
{
|
|
uint8_t checksum = 0;
|
|
for (uint32_t i = 0; i < RSPDv2_SIZE; i++)
|
|
checksum += ((const uint8_t*)rsdp)[i];
|
|
if (checksum != 0)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static BAN::Optional<RSDP> locate_rsdp()
|
|
{
|
|
if (g_boot_info.rsdp.length)
|
|
return g_boot_info.rsdp;
|
|
|
|
// Look in main BIOS area below 1 MB
|
|
for (paddr_t paddr = 0x000E0000; paddr < 0x00100000; paddr += PAGE_SIZE)
|
|
{
|
|
BAN::Optional<RSDP> rsdp;
|
|
|
|
PageTable::with_fast_page(paddr, [&rsdp] {
|
|
for (size_t offset = 0; offset + sizeof(RSDP) <= PAGE_SIZE; offset += 16)
|
|
{
|
|
if (is_rsdp(PageTable::fast_page() + offset))
|
|
{
|
|
rsdp = PageTable::fast_page_as<RSDP>(offset);
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
if (rsdp.has_value())
|
|
return rsdp.release_value();
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
static bool is_valid_std_header(const SDTHeader* header)
|
|
{
|
|
uint8_t sum = 0;
|
|
for (uint32_t i = 0; i < header->length; i++)
|
|
sum += ((uint8_t*)header)[i];
|
|
return sum == 0;
|
|
}
|
|
|
|
BAN::ErrorOr<void> ACPI::initialize_impl()
|
|
{
|
|
auto opt_rsdp = locate_rsdp();
|
|
if (!opt_rsdp.has_value())
|
|
return BAN::Error::from_error_code(ErrorCode::ACPI_NoRootSDT);
|
|
const RSDP rsdp = opt_rsdp.release_value();
|
|
|
|
uint32_t root_entry_count = 0;
|
|
|
|
if (rsdp.revision >= 2)
|
|
{
|
|
TRY(PageTable::with_fast_page(rsdp.xsdt_address & PAGE_ADDR_MASK,
|
|
[&]() -> BAN::ErrorOr<void>
|
|
{
|
|
auto& xsdt = PageTable::fast_page_as<const XSDT>(rsdp.xsdt_address % PAGE_SIZE);
|
|
if (memcmp(xsdt.signature, "XSDT", 4) != 0)
|
|
return BAN::Error::from_error_code(ErrorCode::ACPI_RootInvalid);
|
|
if (!is_valid_std_header(&xsdt))
|
|
return BAN::Error::from_error_code(ErrorCode::ACPI_RootInvalid);
|
|
|
|
m_header_table_paddr = rsdp.xsdt_address + offsetof(XSDT, entries);
|
|
m_entry_size = 8;
|
|
root_entry_count = (xsdt.length - sizeof(SDTHeader)) / 8;
|
|
return {};
|
|
}
|
|
));
|
|
}
|
|
else
|
|
{
|
|
TRY(PageTable::with_fast_page(rsdp.rsdt_address & PAGE_ADDR_MASK,
|
|
[&]() -> BAN::ErrorOr<void>
|
|
{
|
|
auto& rsdt = PageTable::fast_page_as<const RSDT>(rsdp.rsdt_address % PAGE_SIZE);
|
|
if (memcmp(rsdt.signature, "RSDT", 4) != 0)
|
|
return BAN::Error::from_error_code(ErrorCode::ACPI_RootInvalid);
|
|
if (!is_valid_std_header(&rsdt))
|
|
return BAN::Error::from_error_code(ErrorCode::ACPI_RootInvalid);
|
|
|
|
m_header_table_paddr = rsdp.rsdt_address + offsetof(RSDT, entries);
|
|
m_entry_size = 4;
|
|
root_entry_count = (rsdt.length - sizeof(SDTHeader)) / 4;
|
|
return {};
|
|
}
|
|
));
|
|
}
|
|
|
|
size_t needed_pages = range_page_count(m_header_table_paddr, root_entry_count * m_entry_size);
|
|
m_header_table_vaddr = PageTable::kernel().reserve_free_contiguous_pages(needed_pages, KERNEL_OFFSET);
|
|
ASSERT(m_header_table_vaddr);
|
|
|
|
m_header_table_vaddr += m_header_table_paddr % PAGE_SIZE;
|
|
|
|
PageTable::kernel().map_range_at(
|
|
m_header_table_paddr & PAGE_ADDR_MASK,
|
|
m_header_table_vaddr & PAGE_ADDR_MASK,
|
|
needed_pages * PAGE_SIZE,
|
|
PageTable::Flags::Present
|
|
);
|
|
|
|
auto map_header =
|
|
[](paddr_t header_paddr) -> vaddr_t
|
|
{
|
|
size_t header_length;
|
|
PageTable::with_fast_page(header_paddr & PAGE_ADDR_MASK, [&] {
|
|
header_length = PageTable::fast_page_as<SDTHeader>(header_paddr % PAGE_SIZE).length;
|
|
});
|
|
|
|
size_t needed_pages = range_page_count(header_paddr, header_length);
|
|
vaddr_t page_vaddr = PageTable::kernel().reserve_free_contiguous_pages(needed_pages, KERNEL_OFFSET);
|
|
ASSERT(page_vaddr);
|
|
|
|
PageTable::kernel().map_range_at(
|
|
header_paddr & PAGE_ADDR_MASK,
|
|
page_vaddr,
|
|
needed_pages * PAGE_SIZE,
|
|
PageTable::Flags::Present
|
|
);
|
|
|
|
auto* header = (SDTHeader*)(page_vaddr + (header_paddr % PAGE_SIZE));
|
|
if (!is_valid_std_header(header))
|
|
{
|
|
PageTable::kernel().unmap_range(page_vaddr, needed_pages * PAGE_SIZE);
|
|
return 0;
|
|
}
|
|
|
|
return page_vaddr + (header_paddr % PAGE_SIZE);
|
|
};
|
|
|
|
for (uint32_t i = 0; i < root_entry_count; i++)
|
|
{
|
|
paddr_t header_paddr = (m_entry_size == 4) ?
|
|
((uint32_t*)m_header_table_vaddr)[i] :
|
|
((uint64_t*)m_header_table_vaddr)[i];
|
|
|
|
vaddr_t header_vaddr = map_header(header_paddr);
|
|
if (header_vaddr == 0)
|
|
continue;
|
|
|
|
MUST(m_mapped_headers.push_back({
|
|
.paddr = header_paddr,
|
|
.vaddr = header_vaddr
|
|
}));
|
|
}
|
|
|
|
for (size_t i = 0; i < m_mapped_headers.size(); i++)
|
|
{
|
|
auto* header = m_mapped_headers[i].as_header();
|
|
dprintln("found header {}", *header);
|
|
|
|
if (memcmp(header->signature, "FACP", 4) == 0)
|
|
{
|
|
auto* fadt = (FADT*)header;
|
|
|
|
paddr_t dsdt_paddr = 0;
|
|
if (fadt->length > offsetof(FADT, x_dsdt))
|
|
dsdt_paddr = fadt->x_dsdt;
|
|
if (dsdt_paddr == 0 || !PageTable::is_valid_pointer(dsdt_paddr))
|
|
dsdt_paddr = fadt->dsdt;
|
|
|
|
vaddr_t dsdt_vaddr = map_header(dsdt_paddr);
|
|
if (dsdt_vaddr == 0)
|
|
continue;
|
|
|
|
MUST(m_mapped_headers.push_back({
|
|
.paddr = dsdt_paddr,
|
|
.vaddr = dsdt_vaddr
|
|
}));
|
|
|
|
m_fadt = fadt;
|
|
m_hardware_reduced = fadt->flags & (1 << 20);
|
|
}
|
|
}
|
|
|
|
if (m_fadt == nullptr)
|
|
Kernel::panic("No FADT found");
|
|
|
|
return {};
|
|
}
|
|
|
|
const SDTHeader* ACPI::get_header(BAN::StringView signature, uint32_t index)
|
|
{
|
|
if (signature.size() != 4)
|
|
{
|
|
dprintln("Trying to get ACPI header with {} byte signature ??", signature.size());
|
|
return nullptr;
|
|
}
|
|
uint32_t cnt = 0;
|
|
for (auto& mapped_header : m_mapped_headers)
|
|
{
|
|
auto* header = mapped_header.as_header();
|
|
if (memcmp(header->signature, signature.data(), 4) == 0)
|
|
if (cnt++ == index)
|
|
return header;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
BAN::ErrorOr<void> ACPI::prepare_sleep(uint8_t sleep_state)
|
|
{
|
|
auto [pts_path, pts_object] = TRY(m_namespace->find_named_object({}, MUST(AML::NameString::from_string("\\_PTS"))));
|
|
if (pts_object == nullptr)
|
|
return {};
|
|
|
|
auto& pts_node = pts_object->node;
|
|
if (pts_node.type != AML::Node::Type::Method)
|
|
{
|
|
dwarnln("Object \\_PTS is not a method");
|
|
return BAN::Error::from_errno(EFAULT);
|
|
}
|
|
|
|
if (pts_node.as.method.arg_count != 1)
|
|
{
|
|
dwarnln("Method \\_PTS has {} arguments, expected 1", pts_node.as.method.arg_count);
|
|
return BAN::Error::from_errno(EFAULT);
|
|
}
|
|
|
|
AML::Reference arg_ref;
|
|
arg_ref.node.type = AML::Node::Type::Integer;
|
|
arg_ref.node.as.integer.value = sleep_state;
|
|
arg_ref.ref_count = 2;
|
|
|
|
BAN::Array<AML::Reference*, 7> arguments(nullptr);
|
|
arguments[0] = &arg_ref; // method call should not delete argument
|
|
TRY(AML::method_call(pts_path, pts_node, BAN::move(arguments)));
|
|
|
|
dprintln("Executed \\_PTS({})", sleep_state);
|
|
|
|
return {};
|
|
}
|
|
|
|
BAN::ErrorOr<void> ACPI::poweroff()
|
|
{
|
|
if (!m_namespace)
|
|
{
|
|
dwarnln("ACPI namespace not initialized");
|
|
return BAN::Error::from_errno(EFAULT);
|
|
}
|
|
|
|
auto [_, s5_object] = TRY(m_namespace->find_named_object({}, TRY(AML::NameString::from_string("\\_S5_"_sv))));
|
|
if (!s5_object)
|
|
{
|
|
dwarnln("\\_S5 not found");
|
|
return BAN::Error::from_errno(EFAULT);
|
|
}
|
|
|
|
auto& s5_node = s5_object->node;
|
|
if (s5_node.type != AML::Node::Type::Package)
|
|
{
|
|
dwarnln("\\_S5 is not a package");
|
|
return BAN::Error::from_errno(EFAULT);
|
|
}
|
|
if (s5_node.as.package->num_elements < 2)
|
|
{
|
|
dwarnln("\\_S5 package has {} elements, expected atleast 2", s5_node.as.package->num_elements);
|
|
return BAN::Error::from_errno(EFAULT);
|
|
}
|
|
|
|
if (!s5_node.as.package->elements[0].resolved || !s5_node.as.package->elements[1].resolved)
|
|
{
|
|
dwarnln("TODO: lazy evaluate package \\_S5 elements");
|
|
return BAN::Error::from_errno(ENOTSUP);
|
|
}
|
|
|
|
auto slp_typa_node = TRY(AML::convert_node(TRY(s5_node.as.package->elements[0].value.node->copy()), AML::ConvInteger, sizeof(uint64_t)));
|
|
auto slp_typb_node = TRY(AML::convert_node(TRY(s5_node.as.package->elements[1].value.node->copy()), AML::ConvInteger, sizeof(uint64_t)));
|
|
|
|
TRY(prepare_sleep(5));
|
|
|
|
dprintln("Entering sleep state S5");
|
|
|
|
const auto slp_typa_value = slp_typa_node.as.integer.value;
|
|
const auto slp_typb_value = slp_typb_node.as.integer.value;
|
|
|
|
uint16_t pm1a_data = IO::inw(fadt().pm1a_cnt_blk);
|
|
pm1a_data &= ~(PM1_CNT_SLP_TYP_MASK << PM1_CNT_SLP_TYP_SHIFT);
|
|
pm1a_data |= (slp_typa_value & PM1_CNT_SLP_TYP_MASK) << PM1_CNT_SLP_TYP_SHIFT;
|
|
pm1a_data |= PM1_CNT_SLP_EN;
|
|
IO::outw(fadt().pm1a_cnt_blk, pm1a_data);
|
|
|
|
if (fadt().pm1b_cnt_blk != 0)
|
|
{
|
|
uint16_t pm1b_data = IO::inw(fadt().pm1b_cnt_blk);
|
|
pm1b_data &= ~(PM1_CNT_SLP_TYP_MASK << PM1_CNT_SLP_TYP_SHIFT);
|
|
pm1b_data |= (slp_typb_value & PM1_CNT_SLP_TYP_MASK) << PM1_CNT_SLP_TYP_SHIFT;
|
|
pm1b_data |= PM1_CNT_SLP_EN;
|
|
IO::outw(fadt().pm1b_cnt_blk, pm1b_data);
|
|
}
|
|
|
|
// system must not execute after sleep registers are written
|
|
ASSERT_NOT_REACHED();
|
|
}
|
|
|
|
BAN::ErrorOr<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 BAN::Error::from_errno(EFAULT);
|
|
}
|
|
|
|
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 BAN::Error::from_errno(EFAULT);
|
|
}
|
|
|
|
TRY(prepare_sleep(5));
|
|
|
|
dprintln("Resetting system");
|
|
|
|
TRY(reset_reg.write(fadt().reset_value));
|
|
|
|
// system must not execute after reset register is written
|
|
ASSERT_NOT_REACHED();
|
|
}
|
|
|
|
BAN::ErrorOr<void> ACPI::load_aml_tables(BAN::StringView name, bool all)
|
|
{
|
|
BAN::ErrorOr<void> result {};
|
|
|
|
for (uint32_t i = 0;; i++)
|
|
{
|
|
auto* header = get_header(name, i);
|
|
if (header == nullptr)
|
|
break;
|
|
|
|
if (all)
|
|
dprintln("Parsing {}{}, {} bytes", name, i + 1, header->length);
|
|
else
|
|
dprintln("Parsing {}, {} bytes", name, header->length);
|
|
|
|
auto header_span = BAN::ConstByteSpan(reinterpret_cast<const uint8_t*>(header), header->length);
|
|
if (auto parse_ret = m_namespace->parse(header_span); parse_ret.is_error())
|
|
result = parse_ret.release_error();
|
|
|
|
if (!all)
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
BAN::ErrorOr<void> ACPI::enter_acpi_mode(uint8_t mode)
|
|
{
|
|
ASSERT(!m_namespace);
|
|
|
|
// https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/16_Waking_and_Sleeping/initialization.html#placing-the-system-in-acpi-mode
|
|
|
|
// If not hardware-reduced ACPI and SCI_EN is not set
|
|
if (!hardware_reduced() && !(IO::inw(fadt().pm1a_cnt_blk) & PM1_CNT_SCI_EN))
|
|
{
|
|
// https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/04_ACPI_Hardware_Specification/ACPI_Hardware_Specification.html#legacy-acpi-select-and-the-sci-interrupt
|
|
IO::outb(fadt().smi_cmd, fadt().acpi_enable);
|
|
|
|
// Spec says to poll until SCI_EN is set, but doesn't specify timeout
|
|
for (size_t i = 0; i < 100; i++)
|
|
{
|
|
if (IO::inw(fadt().pm1a_cnt_blk) & PM1_CNT_SCI_EN)
|
|
break;
|
|
SystemTimer::get().sleep_ms(10);
|
|
}
|
|
|
|
if (!(IO::inw(fadt().pm1a_cnt_blk) & PM1_CNT_SCI_EN))
|
|
{
|
|
dwarnln("Failed to enable ACPI mode");
|
|
return BAN::Error::from_errno(EINVAL);
|
|
}
|
|
|
|
// Enable power and sleep buttons
|
|
IO::outw(fadt().pm1a_evt_blk + fadt().pm1_evt_len / 2, PM1_EVN_PWRBTN | PM1_EVN_SLPBTN);
|
|
IO::outw(fadt().pm1b_evt_blk + fadt().pm1_evt_len / 2, PM1_EVN_PWRBTN | PM1_EVN_SLPBTN);
|
|
}
|
|
|
|
dprintln("Entered ACPI mode");
|
|
|
|
TRY(AML::Namespace::prepare_root_namespace());
|
|
m_namespace = &AML::Namespace::root_namespace();
|
|
|
|
if (auto ret = load_aml_tables("DSDT"_sv, false); ret.is_error())
|
|
dwarnln("Could not load DSDT: {}", ret.error());
|
|
if (auto ret = load_aml_tables("SSDT"_sv, true); ret.is_error())
|
|
dwarnln("Could not load all SSDTs: {}", ret.error());
|
|
if (auto ret = load_aml_tables("PSDT"_sv, true); ret.is_error())
|
|
dwarnln("Could not load all PSDTs: {}", ret.error());
|
|
|
|
dprintln("Loaded ACPI tables");
|
|
|
|
if (auto ret = m_namespace->post_load_initialize(); ret.is_error())
|
|
dwarnln("Failed to initialize ACPI namespace: {}", ret.error());
|
|
|
|
auto [pic_path, pic_obj] = TRY(m_namespace->find_named_object({}, TRY(AML::NameString::from_string("\\_PIC"_sv))));
|
|
if (pic_obj && pic_obj->node.type == AML::Node::Type::Method)
|
|
{
|
|
auto& pic_node = pic_obj->node;
|
|
if (pic_node.as.method.arg_count != 1)
|
|
{
|
|
dwarnln("Method \\_PIC has {} arguments, expected 1", pic_node.as.method.arg_count);
|
|
return BAN::Error::from_errno(EINVAL);
|
|
}
|
|
|
|
AML::Reference arg_ref;
|
|
arg_ref.node.type = AML::Node::Type::Integer;
|
|
arg_ref.node.as.integer.value = mode;
|
|
arg_ref.ref_count = 2;
|
|
|
|
BAN::Array<AML::Reference*, 7> arguments(nullptr);
|
|
arguments[0] = &arg_ref; // method call should not delete argument
|
|
TRY(AML::method_call(pic_path, pic_node, BAN::move(arguments)));
|
|
}
|
|
|
|
dprintln("Evaluated \\_PIC({})", mode);
|
|
|
|
uint8_t irq = fadt().sci_int;
|
|
if (auto ret = InterruptController::get().reserve_irq(irq); ret.is_error())
|
|
dwarnln("Could not enable ACPI interrupt: {}", ret.error());
|
|
else
|
|
{
|
|
auto hex_sv_to_int =
|
|
[](BAN::StringView sv) -> BAN::Optional<uint32_t>
|
|
{
|
|
uint32_t ret = 0;
|
|
for (char c : sv)
|
|
{
|
|
ret <<= 4;
|
|
if (c >= '0' && c <= '9')
|
|
ret += c - '0';
|
|
else if (c >= 'A' && c <= 'F')
|
|
ret += c - 'A' + 10;
|
|
else if (c >= 'a' && c <= 'f')
|
|
ret += c - 'a' + 10;
|
|
else
|
|
return {};
|
|
}
|
|
return ret;
|
|
};
|
|
|
|
if (fadt().gpe0_blk)
|
|
{
|
|
auto [gpe_scope, gpe_obj] = TRY(m_namespace->find_named_object({}, TRY(AML::NameString::from_string("\\_GPE"))));
|
|
if (gpe_obj && gpe_obj->node.is_scope())
|
|
{
|
|
m_gpe_scope = BAN::move(gpe_scope);
|
|
|
|
// Enable all events in _GPE (_Lxx or _Exx)
|
|
TRY(m_namespace->for_each_child(m_gpe_scope,
|
|
[&](BAN::StringView name, AML::Reference* node_ref) -> BAN::Iteration
|
|
{
|
|
if (node_ref->node.type != AML::Node::Type::Method)
|
|
return BAN::Iteration::Continue;
|
|
|
|
ASSERT(name.size() == 4);
|
|
if (!name.starts_with("_L"_sv) && !name.starts_with("_E"_sv))
|
|
return BAN::Iteration::Continue;
|
|
|
|
auto index = hex_sv_to_int(name.substring(2));
|
|
if (!index.has_value())
|
|
{
|
|
dwarnln("invalid GPE number '{}'", name);
|
|
return BAN::Iteration::Continue;
|
|
}
|
|
|
|
auto byte = index.value() / 8;
|
|
auto bit = index.value() % 8;
|
|
auto gpe0_en_port = fadt().gpe0_blk + (fadt().gpe0_blk_len / 2) + byte;
|
|
IO::outb(gpe0_en_port, IO::inb(gpe0_en_port) | (1 << bit));
|
|
|
|
m_gpe_methods[index.value()] = node_ref;
|
|
node_ref->ref_count++;
|
|
|
|
dprintln("Enabled GPE {}", index.value(), byte, bit);
|
|
|
|
return BAN::Iteration::Continue;
|
|
}
|
|
));
|
|
}
|
|
}
|
|
|
|
set_irq(irq);
|
|
InterruptController::get().enable_irq(irq);
|
|
|
|
Process::create_kernel([](void*) { get().acpi_event_task(); }, nullptr);
|
|
}
|
|
|
|
dprintln("Initialized ACPI interrupts");
|
|
|
|
return {};
|
|
}
|
|
|
|
BAN::ErrorOr<void> ACPI::initialize_acpi_devices()
|
|
{
|
|
ASSERT(m_namespace);
|
|
TRY(BatterySystem::initialize(*m_namespace));
|
|
return {};
|
|
}
|
|
|
|
void ACPI::acpi_event_task()
|
|
{
|
|
auto get_fixed_event =
|
|
[&](uint16_t sts_port)
|
|
{
|
|
if (sts_port == 0)
|
|
return 0;
|
|
auto sts = IO::inw(sts_port);
|
|
auto en = IO::inw(sts_port + fadt().pm1_evt_len / 2);
|
|
if (auto pending = sts & en)
|
|
return pending & ~(pending - 1);
|
|
return 0;
|
|
};
|
|
|
|
while (true)
|
|
{
|
|
uint16_t sts_port;
|
|
uint16_t pending;
|
|
|
|
sts_port = fadt().pm1a_evt_blk;
|
|
if (pending = get_fixed_event(sts_port); pending)
|
|
goto handle_event;
|
|
|
|
sts_port = fadt().pm1b_evt_blk;
|
|
if (pending = get_fixed_event(sts_port); pending)
|
|
goto handle_event;
|
|
|
|
{
|
|
bool handled_event = false;
|
|
uint8_t gpe0_bytes = fadt().gpe0_blk_len / 2;
|
|
for (uint8_t i = 0; i < gpe0_bytes; i++)
|
|
{
|
|
uint8_t sts = IO::inb(fadt().gpe0_blk + i);
|
|
uint8_t en = IO::inb(fadt().gpe0_blk + gpe0_bytes + i);
|
|
pending = sts & en;
|
|
if (pending == 0)
|
|
continue;
|
|
|
|
auto index = i * 8 + (pending & ~(pending - 1));
|
|
if (m_gpe_methods[index])
|
|
if (auto ret = AML::method_call(m_gpe_scope, m_gpe_methods[index]->node, {}); ret.is_error())
|
|
dwarnln("Failed to evaluate _GPE {}: ", index, ret.error());
|
|
|
|
handled_event = true;
|
|
IO::outb(fadt().gpe0_blk + i, 1 << index);
|
|
}
|
|
if (handled_event)
|
|
continue;
|
|
}
|
|
|
|
|
|
// FIXME: this can cause missing of event if it happens between
|
|
// reading the status and blocking
|
|
m_event_thread_blocker.block_with_timeout_ms(100);
|
|
continue;
|
|
|
|
handle_event:
|
|
if (pending & PM1_EVN_PWRBTN)
|
|
{
|
|
dprintln("Power button pressed");
|
|
if (auto ret = Process::clean_poweroff(POWEROFF_SHUTDOWN); ret.is_error())
|
|
dwarnln("Failed to poweroff: {}", ret.error());
|
|
}
|
|
else
|
|
{
|
|
dwarnln("Unhandled ACPI fixed event {H}", pending);
|
|
}
|
|
|
|
IO::outw(sts_port, pending);
|
|
}
|
|
}
|
|
|
|
void ACPI::handle_irq()
|
|
{
|
|
m_event_thread_blocker.unblock();
|
|
}
|
|
|
|
}
|