Files
banan-os/kernel/kernel/ACPI/ACPI.cpp
T
Bananymous d46d7a5123 Kernel: Cleanup entering acpi mode
There is no need to pass the current mode as an argument as ACPI has
access to that info either way.

Only route INTx objects when using ACPI, they won't be used otherwise
2026-07-20 09:02:18 +03:00

1222 lines
35 KiB
C++

#include <BAN/ScopeGuard.h>
#include <BAN/StringView.h>
#include <kernel/ACPI/ACPI.h>
#include <kernel/ACPI/AML/OpRegion.h>
#include <kernel/ACPI/BatterySystem.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)
{
if (!m_namespace)
return BAN::Error::from_errno(EFAULT);
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);
}
TRY(AML::resolve_package_element(s5_node.as.package->elements[0], true));
TRY(AML::resolve_package_element(s5_node.as.package->elements[1], true));
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
panic("ACPI shutdown failed. You can now safely shutdown your computer.");
}
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);
}
if (!m_namespace)
dwarnln("ACPI namespace not initialized, will not evaluate \\_S5");
else
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;
}
#pragma GCC diagnostic push
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic ignored "-Wstack-usage="
#endif
BAN::ErrorOr<void> ACPI::route_interrupt_link_device(const AML::Scope& device, uint64_t& routed_irq_mask)
{
ASSERT(m_namespace);
auto prs_node = TRY(AML::convert_node(TRY(m_namespace->evaluate(device, "_PRS"_sv)), AML::ConvBuffer, -1));
auto prs_span = BAN::ConstByteSpan(prs_node.as.str_buf->bytes, prs_node.as.str_buf->size);
auto [srs_path, srs_node] = TRY(m_namespace->find_named_object(device, TRY(AML::NameString::from_string("_SRS"_sv)), true));
if (srs_node == nullptr || srs_node->node.type != AML::Node::Type::Method)
{
dwarnln("interrupt link device does not have _SRS method");
return BAN::Error::from_errno(EINVAL);
}
while (!prs_span.empty())
{
if (!(prs_span[0] & 0x80))
{
const uint8_t name = (prs_span[0] >> 3) & 0x0F;
const uint8_t length = prs_span[0] & 0x07;
if (prs_span.size() < static_cast<size_t>(1 + length))
return BAN::Error::from_errno(EINVAL);
if (name == 0x04)
{
if (length < 2)
return BAN::Error::from_errno(EINVAL);
const uint16_t irq_mask = prs_span[1] | (prs_span[2] << 8);
for (uint8_t pass = 0; pass < 2; pass++)
{
for (uint8_t irq = 0; irq < 16; irq++)
{
if (!(irq_mask & (1 << irq)))
continue;
if (pass == 0 && (routed_irq_mask & (static_cast<uint64_t>(1) << irq)))
continue;
BAN::Array<uint8_t, 4> setting;
setting[0] = 0x22 | (length > 2); // small, irq, data len
setting[1] = (1 << irq) >> 0; // irq low
setting[2] = (1 << irq) >> 8; // irq high
if (length > 2)
setting[3] = prs_span[3]; // flags
auto setting_span = BAN::ConstByteSpan(setting.data(), (length > 2) ? 4 : 3);
TRY(AML::method_call(srs_path, srs_node->node, TRY(AML::Node::create_buffer(setting_span))));
dprintln("routed {} -> irq {}", device, irq);
routed_irq_mask |= static_cast<uint64_t>(1) << irq;
return {};
}
}
}
prs_span = prs_span.slice(1 + length);
}
else
{
if (prs_span.size() < 3)
return BAN::Error::from_errno(EINVAL);
const uint8_t name = prs_span[0] & 0x7F;
const uint16_t length = (prs_span[2] << 8) | prs_span[1];
if (prs_span.size() < static_cast<size_t>(3 + length))
return BAN::Error::from_errno(EINVAL);
// Extended Interrupt Descriptor
if (name == 0x09)
{
const uint8_t irq_count = prs_span[4];
if (irq_count == 0 || length < 2 + 4*irq_count)
return BAN::Error::from_errno(EINVAL);
for (uint8_t pass = 0; pass < 2; pass++)
{
for (uint32_t i = 0; i < irq_count; i++)
{
// TODO: support irq over 64 irqs?
if (prs_span[6 + 4*i] || prs_span[7 + 4*i] || prs_span[8 + 4*i])
continue;
const uint8_t irq = prs_span[5 + 4*i];
if (irq >= 64)
continue;
if (pass == 0 && (routed_irq_mask & (static_cast<uint64_t>(1) << irq)))
continue;
BAN::Array<uint8_t, 9> setting;
setting[0] = 0x89; // large, irq
setting[1] = 0x06; // data len
setting[2] = 0x00;
setting[3] = prs_span[3]; // flags
setting[4] = 0x01; // table size
setting[5] = irq; // irq
setting[6] = 0x00;
setting[7] = 0x00;
setting[8] = 0x00;
TRY(AML::method_call(srs_path, srs_node->node, TRY(AML::Node::create_buffer(setting.span()))));
dprintln("routed {} -> irq {}", device, irq);
routed_irq_mask |= static_cast<uint64_t>(1) << irq;
return {};
}
}
}
prs_span = prs_span.slice(3 + length);
}
}
dwarnln("No routable interrupt found in _PRS");
return {};
}
#pragma GCC diagnostic pop
BAN::Optional<GAS> ACPI::find_gpe_block(size_t index)
{
#define FIND_GPE(idx) \
{ \
const uint8_t null[sizeof(GAS)] {}; \
if (fadt().length > offsetof(FADT, x_gpe##idx##_blk) \
&& memcmp(fadt().x_gpe##idx##_blk, null, sizeof(GAS)) == 0) { \
auto gas = *reinterpret_cast<GAS*>(fadt().x_gpe##idx##_blk); \
if (gas.address != 0) { \
gas.register_bit_width = 8; \
gas.access_size = 1; \
if (!gas.read().is_error()) \
return gas; \
} \
} \
\
if (fadt().gpe##idx##_blk) { \
return GAS { \
.address_space_id = GAS::AddressSpaceID::SystemIO, \
.register_bit_width = 8, \
.register_bit_offset = 0, \
.access_size = 1, \
.address = fadt().gpe##idx##_blk, \
}; \
} \
return {}; \
}
switch (index)
{
case 0: FIND_GPE(0);
case 1: FIND_GPE(1);
default: ASSERT_NOT_REACHED();
}
#undef FIND_GPE
}
BAN::ErrorOr<void> ACPI::initialize_embedded_controller(const AML::Scope& embedded_controller)
{
BAN::Optional<uint8_t> gpe_int;
do {
auto [gpe_path, gpe_obj] = TRY(m_namespace->find_named_object(embedded_controller, TRY(AML::NameString::from_string("_GPE"_sv)), true));
if (gpe_obj == nullptr)
{
dwarnln("EC {} does have _GPE", embedded_controller);
break;
}
auto gpe = TRY(AML::evaluate_node(gpe_path, gpe_obj->node));
if (gpe.type == AML::Node::Type::Package)
{
dwarnln("TODO: EC {} has package _GPE");
break;
}
gpe_int = TRY(AML::convert_node(BAN::move(gpe), AML::ConvInteger, -1)).as.integer.value;
} while (false);
auto [crs_path, crs_obj] = TRY(m_namespace->find_named_object(embedded_controller, TRY(AML::NameString::from_string("_CRS"_sv)), true));
if (crs_obj == nullptr)
{
dwarnln("EC {} does have _CRS", embedded_controller);
return BAN::Error::from_errno(ENOENT);
}
const auto crs = TRY(AML::evaluate_node(crs_path, crs_obj->node));
if (crs.type != AML::Node::Type::Buffer)
{
dwarnln("EC {} _CRS is not a buffer, but {}", embedded_controller, crs);
return BAN::Error::from_errno(EINVAL);
}
const auto extract_io_port =
[](BAN::ConstByteSpan& buffer) -> BAN::ErrorOr<uint16_t>
{
if (buffer.empty())
return BAN::Error::from_errno(ENODATA);
uint16_t result;
bool decode_16;
switch (buffer[0])
{
case 0x47: // IO Port Descriptor
if (buffer.size() < 8)
return BAN::Error::from_errno(ENODATA);
decode_16 = !!(buffer[1] & (1 << 0));
result = (buffer[3] << 8) | buffer[2];
buffer = buffer.slice(8);
break;
case 0x4B: // Fixed Location IO Port Descriptor
if (buffer.size() < 4)
return BAN::Error::from_errno(ENODATA);
decode_16 = false;
result = (buffer[2] << 8) | buffer[1];
buffer = buffer.slice(4);
break;
default:
dwarnln("EC _CRS has unhandled resouce descriptor 0x{2H}", buffer[0]);
return BAN::Error::from_errno(EINVAL);
}
const uint16_t mask = decode_16 ? 0xFFFF : 0x03FF;
return result & mask;
};
// TODO: EC can also reside in memory space
auto crs_buffer = BAN::ConstByteSpan { crs.as.str_buf->bytes, static_cast<size_t>(crs.as.str_buf->size) };
const auto data_port = TRY(extract_io_port(crs_buffer));
const auto command_port = TRY(extract_io_port(crs_buffer));
TRY(m_embedded_controllers.push_back(TRY(EmbeddedController::create(TRY(embedded_controller.copy()), command_port, data_port, gpe_int))));
return {};
}
BAN::ErrorOr<void> ACPI::initialize_embedded_controllers()
{
auto embedded_controllers = TRY(m_namespace->find_device_with_eisa_id("PNP0C09"));
for (auto& embedded_controller : embedded_controllers)
if (auto ret = initialize_embedded_controller(embedded_controller); ret.is_error())
dwarnln("Failed to initialize embedded controller: {}", ret.error());
dprintln("Initialized {}/{} embedded controllers",
m_embedded_controllers.size(),
embedded_controllers.size()
);
return {};
}
BAN::ErrorOr<void> ACPI::register_gpe_handler(uint8_t gpe, void (*callback)(void*), void* argument)
{
if (m_gpe_methods[gpe].method)
return BAN::Error::from_errno(EEXIST);
m_gpe_methods[gpe].has_callback = true;
m_gpe_methods[gpe] = {
.has_callback = true,
.callback = callback,
.argument = argument,
};
if (!enable_gpe(gpe))
{
m_gpe_methods[gpe] = {};
return BAN::Error::from_errno(EFAULT);
}
dprintln("Enabled _GPE {}", gpe);
return {};
}
bool ACPI::enable_gpe(uint8_t gpe)
{
const auto enable_gpe_impl =
[](const GAS& gpe_block, size_t gpe, size_t base, size_t blk_len) -> bool
{
if (gpe < base || gpe >= base + blk_len / 2 * 8)
return false;
const auto byte = (gpe - base) / 8;
const auto bit = (gpe - base) % 8;
auto enabled = ({ auto tmp = gpe_block; tmp.address += (blk_len / 2) + byte; tmp; });
MUST(enabled.write(MUST(enabled.read()) | (1 << bit)));
return true;
};
const auto gpe0 = find_gpe_block(0);
const size_t gpe0_base = 0;
const size_t gpe0_blk_len = gpe0.has_value() ? fadt().gpe0_blk_len : 0;
if (gpe0.has_value() && enable_gpe_impl(gpe0.value(), gpe, gpe0_base, gpe0_blk_len))
{
m_has_any_gpes = true;
return true;
}
const auto gpe1 = find_gpe_block(1);
const size_t gpe1_base = fadt().gpe1_base;
const size_t gpe1_blk_len = gpe1.has_value() ? fadt().gpe1_blk_len : 0;
if (gpe1.has_value() && enable_gpe_impl(gpe1.value(), gpe, gpe1_base, gpe1_blk_len))
{
m_has_any_gpes = true;
return true;
}
return false;
}
BAN::ErrorOr<void> ACPI::enter_acpi_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_for_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");
{
const auto disable_gpe_block =
[](const GAS& gpe, size_t blk_len) {
for (size_t i = 0; i < blk_len / 2; i++)
MUST(({ auto tmp = gpe; tmp.address += blk_len / 2 + i; tmp; }).write(0));
};
if (auto gpe0 = find_gpe_block(0); gpe0.has_value())
disable_gpe_block(gpe0.value(), fadt().gpe0_blk_len);
if (auto gpe1 = find_gpe_block(1); gpe1.has_value())
disable_gpe_block(gpe1.value(), fadt().gpe1_blk_len);
// FIXME: add support for GPE blocks inside the ACPI namespace
}
if (auto ret = initialize_embedded_controllers(); ret.is_error())
dwarnln("Failed to initialize Embedded Controllers: {}", ret.error());
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 = InterruptController::get().is_using_apic() ? 1 : 0;
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({})", InterruptController::get().is_using_apic() ? 1 : 0);
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;
};
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 opt_index = hex_sv_to_int(name.substring(2));
if (!opt_index.has_value())
{
dwarnln("invalid GPE number '{}'", name);
return BAN::Iteration::Continue;
}
const auto index = opt_index.value();
if (enable_gpe(index))
{
m_gpe_methods[index] = {
.has_callback = false,
.method = node_ref
};
node_ref->ref_count++;
dprintln("Enabled {}", name);
}
return BAN::Iteration::Continue;
}
));
}
set_irq(irq);
InterruptController::get().enable_irq(irq);
if (auto thread_or_error = Thread::create_kernel([](void*) { get().acpi_event_task(); }, nullptr); thread_or_error.is_error())
dwarnln("Failed to create ACPI thread, power button will not work: {}", thread_or_error.error());
else if (auto ret = Processor::scheduler().add_thread(thread_or_error.value()); ret.is_error())
dwarnln("Failed to create ACPI thread, power button will not work: {}", ret.error());
}
dprintln("Initialized ACPI interrupts");
if (InterruptController::get().is_using_apic())
{
if (auto interrupt_link_devices_or_error = m_namespace->find_device_with_eisa_id("PNP0C0F"_sv); !interrupt_link_devices_or_error.is_error())
{
uint64_t routed_irq_mask = 0;
auto interrupt_link_devices = interrupt_link_devices_or_error.release_value();
for (const auto& device : interrupt_link_devices)
if (auto ret = route_interrupt_link_device(device, routed_irq_mask); ret.is_error())
dwarnln("failed to route interrupt link device: {}", ret.error());
dprintln("Routed interrupt link devices");
}
}
return {};
}
BAN::ErrorOr<void> ACPI::initialize_acpi_devices()
{
ASSERT(m_namespace);
TRY(BatterySystem::initialize(*m_namespace));
return {};
}
void ACPI::acpi_event_task()
{
const 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;
};
const auto try_handle_gpe = [this](GAS gpe_blk, uint8_t gpe_blk_len, uint32_t base) -> bool {
bool handled = false;
for (uint8_t i = 0; i < gpe_blk_len / 2; i++)
{
auto status = ({ auto tmp = gpe_blk; tmp.address += i; tmp; });
auto enabled = ({ auto tmp = gpe_blk; tmp.address += (gpe_blk_len / 2) + i; tmp; });
const uint8_t pending = MUST(status.read()) & MUST(enabled.read());
if (pending == 0)
continue;
for (size_t bit = 0; bit < 8; bit++)
{
if (!(pending & (1 << bit)))
continue;
const auto gpe = base + i * 8 + bit;
if (auto& method = m_gpe_methods[gpe]; method.method == nullptr)
dwarnln("No handler for _GPE {}", gpe);
else
{
if (method.has_callback)
method.callback(method.argument);
else if (auto ret = AML::method_call(m_gpe_scope, method.method->node, BAN::Array<AML::Reference*, 7>{}); ret.is_error())
dwarnln("Failed to evaluate _GPE {}: ", gpe, ret.error());
else
dprintln("handled _GPE {}", gpe);
}
}
MUST(status.write(pending));
handled = true;
}
return handled;
};
const auto gpe0 = m_has_any_gpes ? find_gpe_block(0) : BAN::Optional<GAS>{};
const auto gpe1 = m_has_any_gpes ? find_gpe_block(1) : BAN::Optional<GAS>{};
while (true)
{
uint16_t sts_port;
uint16_t pending;
sts_port = fadt().pm1a_evt_blk;
if (sts_port && (pending = get_fixed_event(sts_port)))
goto handle_event;
sts_port = fadt().pm1b_evt_blk;
if (sts_port && (pending = get_fixed_event(sts_port)))
goto handle_event;
if (gpe0.has_value() && try_handle_gpe(gpe0.value(), fadt().gpe0_blk_len, 0))
continue;
if (gpe1.has_value() && try_handle_gpe(gpe1.value(), fadt().gpe1_blk_len, fadt().gpe1_base))
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, nullptr);
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();
}
}