From 58ad83913692aab1343b0a74308fe81552f5df39 Mon Sep 17 00:00:00 2001 From: Bananymous Date: Mon, 11 Aug 2025 20:32:24 +0300 Subject: [PATCH] Kernel: Add support for ACPI Embedded Controllers --- kernel/CMakeLists.txt | 1 + kernel/include/kernel/ACPI/ACPI.h | 28 +- .../include/kernel/ACPI/EmbeddedController.h | 69 ++++ kernel/kernel/ACPI/ACPI.cpp | 378 +++++++++++++----- kernel/kernel/ACPI/AML/Namespace.cpp | 25 ++ kernel/kernel/ACPI/AML/OpRegion.cpp | 38 ++ kernel/kernel/ACPI/EmbeddedController.cpp | 251 ++++++++++++ 7 files changed, 696 insertions(+), 94 deletions(-) create mode 100644 kernel/include/kernel/ACPI/EmbeddedController.h create mode 100644 kernel/kernel/ACPI/EmbeddedController.cpp diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index 8cf6c494..b32f5d1e 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -5,6 +5,7 @@ set(KERNEL_SOURCES kernel/ACPI/AML/Node.cpp kernel/ACPI/AML/OpRegion.cpp kernel/ACPI/BatterySystem.cpp + kernel/ACPI/EmbeddedController.cpp kernel/APIC.cpp kernel/Audio/AC97/Controller.cpp kernel/Audio/Controller.cpp diff --git a/kernel/include/kernel/ACPI/ACPI.h b/kernel/include/kernel/ACPI/ACPI.h index a401d4f7..57f16dea 100644 --- a/kernel/include/kernel/ACPI/ACPI.h +++ b/kernel/include/kernel/ACPI/ACPI.h @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -35,8 +36,12 @@ namespace Kernel::ACPI BAN::ErrorOr poweroff(); BAN::ErrorOr reset(); + BAN::ErrorOr register_gpe_handler(uint8_t gpe, void (*callback)(void*), void* argument); + void handle_irq() override; + BAN::Span> embedded_controllers() { return m_embedded_controllers.span(); } + private: ACPI() = default; BAN::ErrorOr initialize_impl(); @@ -50,6 +55,12 @@ namespace Kernel::ACPI BAN::ErrorOr route_interrupt_link_device(const AML::Scope& device, uint64_t& routed_irq_mask); + BAN::ErrorOr initialize_embedded_controller(const AML::Scope& embedded_controller); + BAN::ErrorOr initialize_embedded_controllers(); + + BAN::Optional find_gpe_block(size_t index); + bool enable_gpe(uint8_t gpe); + private: paddr_t m_header_table_paddr = 0; vaddr_t m_header_table_vaddr = 0; @@ -68,8 +79,23 @@ namespace Kernel::ACPI ThreadBlocker m_event_thread_blocker; + BAN::Vector> m_embedded_controllers; + + struct GPEHandler + { + bool has_callback { false }; + union { + AML::Reference* method; + struct + { + void (*callback)(void*); + void* argument; + }; + }; + }; + bool m_has_any_gpes { false }; AML::Scope m_gpe_scope; - BAN::Array m_gpe_methods { nullptr }; + BAN::Array m_gpe_methods; bool m_hardware_reduced { false }; AML::Namespace* m_namespace { nullptr }; diff --git a/kernel/include/kernel/ACPI/EmbeddedController.h b/kernel/include/kernel/ACPI/EmbeddedController.h new file mode 100644 index 00000000..81136983 --- /dev/null +++ b/kernel/include/kernel/ACPI/EmbeddedController.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include + +#include +#include +#include + +namespace Kernel::ACPI +{ + + class EmbeddedController + { + public: + static BAN::ErrorOr> create(AML::Scope&& scope, uint16_t command_port, uint16_t data_port, BAN::Optional gpe); + ~EmbeddedController(); + + BAN::ErrorOr read_byte(uint8_t offset); + BAN::ErrorOr write_byte(uint8_t offset, uint8_t value); + + const AML::Scope& scope() const { return m_scope; } + + private: + EmbeddedController(AML::Scope&& scope, uint16_t command_port, uint16_t data_port, bool has_gpe) + : m_scope(BAN::move(scope)) + , m_command_port(command_port) + , m_data_port(data_port) + , m_has_gpe(has_gpe) + { } + + private: + void wait_status_bit(uint8_t bit, uint8_t value); + + uint8_t read_one(uint16_t port); + void write_one(uint16_t port, uint8_t value); + + static void handle_gpe_wrapper(void*); + void handle_gpe(); + + BAN::ErrorOr call_query_method(uint8_t notification); + + void thread_task(); + + struct Command + { + uint8_t command; + BAN::Optional data1; + BAN::Optional data2; + uint8_t* response; + BAN::Atomic done; + }; + BAN::ErrorOr send_command(Command& command); + + private: + const AML::Scope m_scope; + const uint16_t m_command_port; + const uint16_t m_data_port; + const bool m_has_gpe; + + Mutex m_mutex; + ThreadBlocker m_thread_blocker; + + BAN::Optional m_queued_command; + + Thread* m_thread { nullptr }; + }; + +} diff --git a/kernel/kernel/ACPI/ACPI.cpp b/kernel/kernel/ACPI/ACPI.cpp index 5a3b066e..4a53e361 100644 --- a/kernel/kernel/ACPI/ACPI.cpp +++ b/kernel/kernel/ACPI/ACPI.cpp @@ -1,11 +1,12 @@ #include #include #include -#include #include +#include #include #include #include +#include #include #include #include @@ -637,7 +638,7 @@ acpi_release_global_lock: 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)))); + 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"); @@ -747,6 +748,196 @@ acpi_release_global_lock: } #pragma GCC diagnostic pop + BAN::Optional 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(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 ACPI::initialize_embedded_controller(const AML::Scope& embedded_controller) + { + BAN::Optional 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 + { + 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, 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 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 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 ACPI::enter_acpi_mode(uint8_t mode) { ASSERT(!m_namespace); @@ -792,6 +983,25 @@ acpi_release_global_lock: 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()); @@ -841,45 +1051,43 @@ acpi_release_global_lock: 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()) { - 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); + 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 + // 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()) { - 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); - + 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); @@ -915,7 +1123,7 @@ acpi_release_global_lock: void ACPI::acpi_event_task() { - auto get_fixed_event = + const auto get_fixed_event = [&](uint16_t sts_port) { if (sts_port == 0) @@ -927,78 +1135,62 @@ acpi_release_global_lock: return 0; }; -#define FIND_GPE(idx) \ - BAN::Optional 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(fadt().x_gpe##idx##_blk); \ - if (!gas.read().is_error()) \ - gpe0 = gas; \ - } \ - \ - if (!gpe##idx.has_value() && fadt().gpe##idx##_blk) { \ - gpe##idx = GAS { \ - .address_space_id = GAS::AddressSpaceID::SystemIO, \ - .register_bit_width = 8, \ - .register_bit_offset = 0, \ - .access_size = 1, \ - .address = fadt().gpe##idx##_blk, \ - }; \ - } \ + 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{}); ret.is_error()) + dwarnln("Failed to evaluate _GPE {}: ", gpe, ret.error()); + else + dprintln("handled _GPE {}", gpe); + } + } + + MUST(status.write(pending)); + handled = true; } - FIND_GPE(0); - FIND_GPE(1); + return handled; + }; + + const auto gpe0 = m_has_any_gpes ? find_gpe_block(0) : BAN::Optional{}; + const auto gpe1 = m_has_any_gpes ? find_gpe_block(1) : BAN::Optional{}; while (true) { uint16_t sts_port; uint16_t pending; - const auto read_gpe = [this](GAS gpe, uint8_t gpe_blk_len, uint32_t base) -> bool { - for (uint8_t i = 0; i < gpe_blk_len / 2; i++) - { - auto status = ({ auto tmp = gpe; tmp.address += i; tmp; }); - auto enabled = ({ auto tmp = gpe; 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 index = base + i * 8 + bit; - if (auto* method = m_gpe_methods[index]; method == nullptr) - dwarnln("No handler for _GPE {}", index); - else if (auto ret = AML::method_call(m_gpe_scope, method->node, BAN::Array{}); ret.is_error()) - dwarnln("Failed to evaluate _GPE {}: ", index, ret.error()); - else - dprintln("handled _GPE {}", index); - } - - MUST(status.write(pending)); - return true; - } - - return false; - }; - sts_port = fadt().pm1a_evt_blk; - if ((pending = get_fixed_event(sts_port))) + if (sts_port && (pending = get_fixed_event(sts_port))) goto handle_event; sts_port = fadt().pm1b_evt_blk; - if ((pending = get_fixed_event(sts_port))) + if (sts_port && (pending = get_fixed_event(sts_port))) goto handle_event; - if (gpe0.has_value() && read_gpe(gpe0.value(), fadt().gpe0_blk_len, 0)) + if (gpe0.has_value() && try_handle_gpe(gpe0.value(), fadt().gpe0_blk_len, 0)) continue; - if (gpe1.has_value() && read_gpe(gpe1.value(), fadt().gpe1_blk_len, fadt().gpe1_base)) + 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 diff --git a/kernel/kernel/ACPI/AML/Namespace.cpp b/kernel/kernel/ACPI/AML/Namespace.cpp index d160cbdc..8c777d06 100644 --- a/kernel/kernel/ACPI/AML/Namespace.cpp +++ b/kernel/kernel/ACPI/AML/Namespace.cpp @@ -274,6 +274,31 @@ namespace Kernel::ACPI::AML const auto address_space = opregion.as.opregion.address_space; if (address_space == GAS::AddressSpaceID::SystemIO || address_space == GAS::AddressSpaceID::SystemMemory) return {}; + + switch (address_space) + { + case GAS::AddressSpaceID::SystemMemory: + case GAS::AddressSpaceID::SystemIO: + // always enabled + return {}; + case GAS::AddressSpaceID::PCIConfig: + case GAS::AddressSpaceID::EmbeddedController: + // supported address spaces + break; + case GAS::AddressSpaceID::SMBus: + case GAS::AddressSpaceID::SystemCMOS: + case GAS::AddressSpaceID::PCIBarTarget: + case GAS::AddressSpaceID::IPMI: + case GAS::AddressSpaceID::GeneralPurposeIO: + case GAS::AddressSpaceID::GenericSerialBus: + case GAS::AddressSpaceID::PlatformCommunicationChannel: + // unsupported address spaces + return BAN::Error::from_errno(ENOTSUP); + default: + // unknown address space + return BAN::Error::from_errno(EINVAL); + } + const uint32_t address_space_u32 = static_cast(address_space); if (address_space_u32 >= 32) return {}; diff --git a/kernel/kernel/ACPI/AML/OpRegion.cpp b/kernel/kernel/ACPI/AML/OpRegion.cpp index f30a1e62..402562f0 100644 --- a/kernel/kernel/ACPI/AML/OpRegion.cpp +++ b/kernel/kernel/ACPI/AML/OpRegion.cpp @@ -4,6 +4,7 @@ #include #pragma GCC pop_options +#include #include #include #include @@ -410,6 +411,18 @@ namespace Kernel::ACPI::AML return {}; } + static BAN::ErrorOr get_embedded_controller(const OpRegion& opregion) + { + ASSERT(opregion.address_space == GAS::AddressSpaceID::EmbeddedController); + + auto all_embedded_controllers = ACPI::get().embedded_controllers(); + for (auto& embedded_controller : all_embedded_controllers) + if (embedded_controller->scope() == opregion.scope()) + return embedded_controller.ptr(); + + return BAN::Error::from_errno(ENOENT); + } + static BAN::ErrorOr perform_opregion_read(const OpRegion& opregion, uint8_t access_size, uint64_t offset) { ASSERT(offset % access_size == 0); @@ -473,6 +486,18 @@ namespace Kernel::ACPI::AML ASSERT_NOT_REACHED(); } case GAS::AddressSpaceID::EmbeddedController: + { + auto* embedded_controller = TRY(get_embedded_controller(opregion)); + ASSERT(embedded_controller); + + if (access_size != 1) + { + dwarnln("{} byte read from embedded controller", access_size); + return BAN::Error::from_errno(EINVAL); + } + + return TRY(embedded_controller->read_byte(offset)); + } case GAS::AddressSpaceID::SMBus: case GAS::AddressSpaceID::SystemCMOS: case GAS::AddressSpaceID::PCIBarTarget: @@ -547,6 +572,19 @@ namespace Kernel::ACPI::AML return {}; } case GAS::AddressSpaceID::EmbeddedController: + { + auto* embedded_controller = TRY(get_embedded_controller(opregion)); + ASSERT(embedded_controller); + + if (access_size != 1) + { + dwarnln("{} byte write to embedded controller", access_size); + return BAN::Error::from_errno(EINVAL); + } + + TRY(embedded_controller->write_byte(offset, value)); + return {}; + } case GAS::AddressSpaceID::SMBus: case GAS::AddressSpaceID::SystemCMOS: case GAS::AddressSpaceID::PCIBarTarget: diff --git a/kernel/kernel/ACPI/EmbeddedController.cpp b/kernel/kernel/ACPI/EmbeddedController.cpp new file mode 100644 index 00000000..18605b2b --- /dev/null +++ b/kernel/kernel/ACPI/EmbeddedController.cpp @@ -0,0 +1,251 @@ +#include + +#include +#include +#include +#include +#include + +namespace Kernel::ACPI +{ + + enum Status + { + STS_OBF = 1 << 0, + STS_IBF = 1 << 1, + STS_CMD = 1 << 3, + STS_BURST = 1 << 4, + STS_SCI_EVT = 1 << 5, + STS_SMI_EVT = 1 << 6, + }; + + enum Command + { + CMD_READ = 0x80, + CMD_WRITE = 0x81, + CMD_BURST_EN = 0x82, + CMD_BURST_DIS = 0x83, + CMD_QUERY = 0x84, + }; + + BAN::ErrorOr> EmbeddedController::create(AML::Scope&& scope, uint16_t command_port, uint16_t data_port, BAN::Optional gpe) + { + auto* embedded_controller_ptr = new EmbeddedController(BAN::move(scope), command_port, data_port, gpe.has_value()); + if (embedded_controller_ptr == nullptr) + return BAN::Error::from_errno(ENOMEM); + + auto* thread = TRY(Thread::create_kernel([](void* ec) { static_cast(ec)->thread_task(); }, embedded_controller_ptr)); + TRY(Processor::scheduler().add_thread(thread)); + + auto embedded_controller = BAN::UniqPtr::adopt(embedded_controller_ptr); + embedded_controller->m_thread = thread; + + if (gpe.has_value()) + TRY(ACPI::get().register_gpe_handler(gpe.value(), &handle_gpe_wrapper, embedded_controller.ptr())); + else + { + // FIXME: Restructure EC such that SCI_EVT can be polled. + // Simple solution would be spawning another thread, + // but that feels too hacky. + dwarnln("TODO: SCI_EVT polling without GPE"); + } + + return embedded_controller; + } + + EmbeddedController::~EmbeddedController() + { + if (m_thread) + m_thread->add_signal(SIGKILL); + m_thread = nullptr; + } + + BAN::ErrorOr EmbeddedController::read_byte(uint8_t offset) + { + uint8_t response; + Command command { + .command = 0x80, + .data1 = offset, + .data2 = {}, + .response = &response, + .done = false, + }; + + TRY(send_command(command)); + + return response; + } + + BAN::ErrorOr EmbeddedController::write_byte(uint8_t offset, uint8_t value) + { + Command command { + .command = 0x81, + .data1 = offset, + .data2 = value, + .response = nullptr, + .done = false, + }; + + TRY(send_command(command)); + + return {}; + } + + uint8_t EmbeddedController::read_one(uint16_t port) + { + wait_status_bit(STS_OBF, 1); + return IO::inb(port); + } + + void EmbeddedController::write_one(uint16_t port, uint8_t value) + { + wait_status_bit(STS_IBF, 0); + IO::outb(port, value); + } + + void EmbeddedController::wait_status_bit(uint8_t bit, uint8_t value) + { + // FIXME: timeouts + const uint8_t mask = 1 << bit; + const uint8_t comp = value ? mask : 0; + while ((IO::inb(m_command_port) & mask) != comp) + continue; + } + + void EmbeddedController::handle_gpe_wrapper(void* embedded_controller) + { + static_cast(embedded_controller)->handle_gpe(); + } + + void EmbeddedController::handle_gpe() + { + for (;;) + { + if (!(IO::inb(m_command_port) & STS_SCI_EVT)) + break; + + uint8_t response; + Command command = { + .command = 0x84, + .data1 = {}, + .data2 = {}, + .response = &response, + .done = false, + }; + + if (auto ret = send_command(command); ret.is_error()) + { + dwarnln("Failed to send SC_QUERY: {}", ret.error()); + break; + } + + if (response == 0) + { + dprintln(" No query"); + break; + } + + if (auto ret = call_query_method(response); ret.is_error()) + dwarnln("Failed to call _Q{2H}: {}", response, ret.error()); + } + } + + BAN::ErrorOr EmbeddedController::call_query_method(uint8_t notification) + { + const auto nibble_to_hex_char = + [](uint8_t nibble) -> char + { + if (nibble < 10) + return '0' + nibble; + return 'A' + nibble - 10; + }; + + const char query_method[] { + '_', 'Q', + nibble_to_hex_char(notification >> 4), + nibble_to_hex_char(notification & 0xF), + '\0' + }; + + auto [method_path, method_obj] = TRY(ACPI::get().acpi_namespace()->find_named_object(m_scope, TRY(AML::NameString::from_string(query_method)), true)); + if (method_obj == nullptr) + { + dwarnln("{} not found", query_method); + return {}; + } + const auto& method = method_obj->node; + + if (method.type != AML::Node::Type::Method) + { + dwarnln("{} is not a method", query_method); + return {}; + } + if (method.as.method.arg_count != 0) + { + dwarnln("{} takes {} arguments", query_method, method.as.method.arg_count); + return {}; + } + + TRY(AML::method_call(method_path, method)); + return {}; + } + + BAN::ErrorOr EmbeddedController::send_command(Command& command) + { + LockGuard _(m_mutex); + + const uint64_t wake_time_ms = SystemTimer::get().ms_since_boot() + 1000; + while (m_queued_command.has_value() && SystemTimer::get().ms_since_boot() <= wake_time_ms) + m_thread_blocker.block_with_wake_time_ms(wake_time_ms, &m_mutex); + if (m_queued_command.has_value()) + return BAN::Error::from_errno(ETIMEDOUT); + + m_queued_command = &command; + m_thread_blocker.unblock(); + + while (!command.done) + m_thread_blocker.block_indefinite(&m_mutex); + + return {}; + } + + void EmbeddedController::thread_task() + { + m_mutex.lock(); + + for (;;) + { + Command* const command = m_queued_command.has_value() ? m_queued_command.value() : nullptr; + m_queued_command.clear(); + + if (command == nullptr) + { + m_thread_blocker.block_indefinite(&m_mutex); + continue; + } + + m_mutex.unlock(); + + if (command) + { + // TODO: use burst mode + + write_one(m_command_port, command->command); + if (command->data1.has_value()) + write_one(m_data_port, command->data1.value()); + if (command->data2.has_value()) + write_one(m_data_port, command->data2.value()); + if (command->response) + *command->response = read_one(m_data_port); + + m_mutex.lock(); + command->done = true; + m_thread_blocker.unblock(); + m_mutex.unlock(); + } + + m_mutex.lock(); + } + } + +}