Kernel: Add support for ACPI Embedded Controllers

This commit is contained in:
Bananymous 2025-08-11 20:32:24 +03:00
parent 8ed5a71c45
commit 58ad839136
7 changed files with 696 additions and 94 deletions

View File

@ -5,6 +5,7 @@ set(KERNEL_SOURCES
kernel/ACPI/AML/Node.cpp kernel/ACPI/AML/Node.cpp
kernel/ACPI/AML/OpRegion.cpp kernel/ACPI/AML/OpRegion.cpp
kernel/ACPI/BatterySystem.cpp kernel/ACPI/BatterySystem.cpp
kernel/ACPI/EmbeddedController.cpp
kernel/APIC.cpp kernel/APIC.cpp
kernel/Audio/AC97/Controller.cpp kernel/Audio/AC97/Controller.cpp
kernel/Audio/Controller.cpp kernel/Audio/Controller.cpp

View File

@ -2,6 +2,7 @@
#include <BAN/Vector.h> #include <BAN/Vector.h>
#include <kernel/ACPI/AML/Namespace.h> #include <kernel/ACPI/AML/Namespace.h>
#include <kernel/ACPI/EmbeddedController.h>
#include <kernel/ACPI/Headers.h> #include <kernel/ACPI/Headers.h>
#include <kernel/Memory/Types.h> #include <kernel/Memory/Types.h>
#include <kernel/ThreadBlocker.h> #include <kernel/ThreadBlocker.h>
@ -35,8 +36,12 @@ namespace Kernel::ACPI
BAN::ErrorOr<void> poweroff(); BAN::ErrorOr<void> poweroff();
BAN::ErrorOr<void> reset(); BAN::ErrorOr<void> reset();
BAN::ErrorOr<void> register_gpe_handler(uint8_t gpe, void (*callback)(void*), void* argument);
void handle_irq() override; void handle_irq() override;
BAN::Span<BAN::UniqPtr<EmbeddedController>> embedded_controllers() { return m_embedded_controllers.span(); }
private: private:
ACPI() = default; ACPI() = default;
BAN::ErrorOr<void> initialize_impl(); BAN::ErrorOr<void> initialize_impl();
@ -50,6 +55,12 @@ namespace Kernel::ACPI
BAN::ErrorOr<void> route_interrupt_link_device(const AML::Scope& device, uint64_t& routed_irq_mask); BAN::ErrorOr<void> route_interrupt_link_device(const AML::Scope& device, uint64_t& routed_irq_mask);
BAN::ErrorOr<void> initialize_embedded_controller(const AML::Scope& embedded_controller);
BAN::ErrorOr<void> initialize_embedded_controllers();
BAN::Optional<GAS> find_gpe_block(size_t index);
bool enable_gpe(uint8_t gpe);
private: private:
paddr_t m_header_table_paddr = 0; paddr_t m_header_table_paddr = 0;
vaddr_t m_header_table_vaddr = 0; vaddr_t m_header_table_vaddr = 0;
@ -68,8 +79,23 @@ namespace Kernel::ACPI
ThreadBlocker m_event_thread_blocker; ThreadBlocker m_event_thread_blocker;
BAN::Vector<BAN::UniqPtr<EmbeddedController>> 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; AML::Scope m_gpe_scope;
BAN::Array<AML::Reference*, 0xFF> m_gpe_methods { nullptr }; BAN::Array<GPEHandler, 0xFF> m_gpe_methods;
bool m_hardware_reduced { false }; bool m_hardware_reduced { false };
AML::Namespace* m_namespace { nullptr }; AML::Namespace* m_namespace { nullptr };

View File

@ -0,0 +1,69 @@
#pragma once
#include <BAN/Atomic.h>
#include <BAN/UniqPtr.h>
#include <kernel/ACPI/AML/Scope.h>
#include <kernel/Lock/Mutex.h>
#include <kernel/Thread.h>
namespace Kernel::ACPI
{
class EmbeddedController
{
public:
static BAN::ErrorOr<BAN::UniqPtr<EmbeddedController>> create(AML::Scope&& scope, uint16_t command_port, uint16_t data_port, BAN::Optional<uint8_t> gpe);
~EmbeddedController();
BAN::ErrorOr<uint8_t> read_byte(uint8_t offset);
BAN::ErrorOr<void> 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<void> call_query_method(uint8_t notification);
void thread_task();
struct Command
{
uint8_t command;
BAN::Optional<uint8_t> data1;
BAN::Optional<uint8_t> data2;
uint8_t* response;
BAN::Atomic<bool> done;
};
BAN::ErrorOr<void> 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<Command*> m_queued_command;
Thread* m_thread { nullptr };
};
}

View File

@ -1,11 +1,12 @@
#include <BAN/ScopeGuard.h> #include <BAN/ScopeGuard.h>
#include <BAN/StringView.h> #include <BAN/StringView.h>
#include <kernel/ACPI/ACPI.h> #include <kernel/ACPI/ACPI.h>
#include <kernel/ACPI/BatterySystem.h>
#include <kernel/ACPI/AML/OpRegion.h> #include <kernel/ACPI/AML/OpRegion.h>
#include <kernel/ACPI/BatterySystem.h>
#include <kernel/BootInfo.h> #include <kernel/BootInfo.h>
#include <kernel/InterruptController.h> #include <kernel/InterruptController.h>
#include <kernel/IO.h> #include <kernel/IO.h>
#include <kernel/Lock/SpinLockAsMutex.h>
#include <kernel/Memory/PageTable.h> #include <kernel/Memory/PageTable.h>
#include <kernel/Process.h> #include <kernel/Process.h>
#include <kernel/Timer/Timer.h> #include <kernel/Timer/Timer.h>
@ -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_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 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) if (srs_node == nullptr || srs_node->node.type != AML::Node::Type::Method)
{ {
dwarnln("interrupt link device does not have _SRS method"); dwarnln("interrupt link device does not have _SRS method");
@ -747,6 +748,196 @@ acpi_release_global_lock:
} }
#pragma GCC diagnostic pop #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, 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(uint8_t mode) BAN::ErrorOr<void> ACPI::enter_acpi_mode(uint8_t mode)
{ {
ASSERT(!m_namespace); ASSERT(!m_namespace);
@ -792,6 +983,25 @@ acpi_release_global_lock:
dprintln("Loaded ACPI tables"); 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()) if (auto ret = m_namespace->post_load_initialize(); ret.is_error())
dwarnln("Failed to initialize ACPI namespace: {}", ret.error()); dwarnln("Failed to initialize ACPI namespace: {}", ret.error());
@ -841,45 +1051,43 @@ acpi_release_global_lock:
return ret; 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")))); m_gpe_scope = BAN::move(gpe_scope);
if (gpe_obj && gpe_obj->node.is_scope())
{
m_gpe_scope = BAN::move(gpe_scope);
// Enable all events in _GPE (_Lxx or _Exx) // Enable all events in _GPE (_Lxx or _Exx)
TRY(m_namespace->for_each_child(m_gpe_scope, TRY(m_namespace->for_each_child(m_gpe_scope,
[&](BAN::StringView name, AML::Reference* node_ref) -> BAN::Iteration [&](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) dwarnln("invalid GPE number '{}'", name);
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; 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); set_irq(irq);
@ -915,7 +1123,7 @@ acpi_release_global_lock:
void ACPI::acpi_event_task() void ACPI::acpi_event_task()
{ {
auto get_fixed_event = const auto get_fixed_event =
[&](uint16_t sts_port) [&](uint16_t sts_port)
{ {
if (sts_port == 0) if (sts_port == 0)
@ -927,78 +1135,62 @@ acpi_release_global_lock:
return 0; return 0;
}; };
#define FIND_GPE(idx) \ const auto try_handle_gpe = [this](GAS gpe_blk, uint8_t gpe_blk_len, uint32_t base) -> bool {
BAN::Optional<GAS> gpe##idx; \ bool handled = false;
{ \ for (uint8_t i = 0; i < gpe_blk_len / 2; i++)
const uint8_t null[sizeof(GAS)] {}; \ {
if (fadt().length > offsetof(FADT, x_gpe##idx##_blk) \ auto status = ({ auto tmp = gpe_blk; tmp.address += i; tmp; });
&& memcmp(fadt().x_gpe##idx##_blk, null, sizeof(GAS)) == 0) { \ auto enabled = ({ auto tmp = gpe_blk; tmp.address += (gpe_blk_len / 2) + i; tmp; });
auto gas = *reinterpret_cast<GAS*>(fadt().x_gpe##idx##_blk); \ const uint8_t pending = MUST(status.read()) & MUST(enabled.read());
if (!gas.read().is_error()) \ if (pending == 0)
gpe0 = gas; \ continue;
} \
\ for (size_t bit = 0; bit < 8; bit++)
if (!gpe##idx.has_value() && fadt().gpe##idx##_blk) { \ {
gpe##idx = GAS { \ if (!(pending & (1 << bit)))
.address_space_id = GAS::AddressSpaceID::SystemIO, \ continue;
.register_bit_width = 8, \
.register_bit_offset = 0, \ const auto gpe = base + i * 8 + bit;
.access_size = 1, \ if (auto& method = m_gpe_methods[gpe]; method.method == nullptr)
.address = fadt().gpe##idx##_blk, \ 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;
} }
FIND_GPE(0); return handled;
FIND_GPE(1); };
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) while (true)
{ {
uint16_t sts_port; uint16_t sts_port;
uint16_t pending; 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<AML::Reference*, 7>{}); 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; 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; goto handle_event;
sts_port = fadt().pm1b_evt_blk; 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; 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; 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; continue;
// FIXME: this can cause missing of event if it happens between // FIXME: this can cause missing of event if it happens between

View File

@ -274,6 +274,31 @@ namespace Kernel::ACPI::AML
const auto address_space = opregion.as.opregion.address_space; const auto address_space = opregion.as.opregion.address_space;
if (address_space == GAS::AddressSpaceID::SystemIO || address_space == GAS::AddressSpaceID::SystemMemory) if (address_space == GAS::AddressSpaceID::SystemIO || address_space == GAS::AddressSpaceID::SystemMemory)
return {}; 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<uint32_t>(address_space); const uint32_t address_space_u32 = static_cast<uint32_t>(address_space);
if (address_space_u32 >= 32) if (address_space_u32 >= 32)
return {}; return {};

View File

@ -4,6 +4,7 @@
#include <BAN/Errors.h> #include <BAN/Errors.h>
#pragma GCC pop_options #pragma GCC pop_options
#include <kernel/ACPI/ACPI.h>
#include <kernel/ACPI/AML/Bytes.h> #include <kernel/ACPI/AML/Bytes.h>
#include <kernel/ACPI/AML/Namespace.h> #include <kernel/ACPI/AML/Namespace.h>
#include <kernel/ACPI/AML/OpRegion.h> #include <kernel/ACPI/AML/OpRegion.h>
@ -410,6 +411,18 @@ namespace Kernel::ACPI::AML
return {}; return {};
} }
static BAN::ErrorOr<EmbeddedController*> 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<uint64_t> perform_opregion_read(const OpRegion& opregion, uint8_t access_size, uint64_t offset) static BAN::ErrorOr<uint64_t> perform_opregion_read(const OpRegion& opregion, uint8_t access_size, uint64_t offset)
{ {
ASSERT(offset % access_size == 0); ASSERT(offset % access_size == 0);
@ -473,6 +486,18 @@ namespace Kernel::ACPI::AML
ASSERT_NOT_REACHED(); ASSERT_NOT_REACHED();
} }
case GAS::AddressSpaceID::EmbeddedController: 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::SMBus:
case GAS::AddressSpaceID::SystemCMOS: case GAS::AddressSpaceID::SystemCMOS:
case GAS::AddressSpaceID::PCIBarTarget: case GAS::AddressSpaceID::PCIBarTarget:
@ -547,6 +572,19 @@ namespace Kernel::ACPI::AML
return {}; return {};
} }
case GAS::AddressSpaceID::EmbeddedController: 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::SMBus:
case GAS::AddressSpaceID::SystemCMOS: case GAS::AddressSpaceID::SystemCMOS:
case GAS::AddressSpaceID::PCIBarTarget: case GAS::AddressSpaceID::PCIBarTarget:

View File

@ -0,0 +1,251 @@
#include <BAN/ScopeGuard.h>
#include <kernel/ACPI/ACPI.h>
#include <kernel/ACPI/EmbeddedController.h>
#include <kernel/IO.h>
#include <kernel/Lock/LockGuard.h>
#include <kernel/Timer/Timer.h>
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<BAN::UniqPtr<EmbeddedController>> EmbeddedController::create(AML::Scope&& scope, uint16_t command_port, uint16_t data_port, BAN::Optional<uint8_t> 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<EmbeddedController*>(ec)->thread_task(); }, embedded_controller_ptr));
TRY(Processor::scheduler().add_thread(thread));
auto embedded_controller = BAN::UniqPtr<EmbeddedController>::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<uint8_t> 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<void> 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<EmbeddedController*>(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<void> 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<void> 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();
}
}
}