Kernel: Add support for ACPI Embedded Controllers
This commit is contained in:
parent
8ed5a71c45
commit
58ad839136
|
@ -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
|
||||||
|
|
|
@ -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 };
|
||||||
|
|
|
@ -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 };
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -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,8 +1051,6 @@ 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"))));
|
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())
|
if (gpe_obj && gpe_obj->node.is_scope())
|
||||||
{
|
{
|
||||||
|
@ -859,28 +1067,28 @@ acpi_release_global_lock:
|
||||||
if (!name.starts_with("_L"_sv) && !name.starts_with("_E"_sv))
|
if (!name.starts_with("_L"_sv) && !name.starts_with("_E"_sv))
|
||||||
return BAN::Iteration::Continue;
|
return BAN::Iteration::Continue;
|
||||||
|
|
||||||
auto index = hex_sv_to_int(name.substring(2));
|
auto opt_index = hex_sv_to_int(name.substring(2));
|
||||||
if (!index.has_value())
|
if (!opt_index.has_value())
|
||||||
{
|
{
|
||||||
dwarnln("invalid GPE number '{}'", name);
|
dwarnln("invalid GPE number '{}'", name);
|
||||||
return BAN::Iteration::Continue;
|
return BAN::Iteration::Continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto byte = index.value() / 8;
|
const auto index = opt_index.value();
|
||||||
auto bit = index.value() % 8;
|
if (enable_gpe(index))
|
||||||
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] = {
|
||||||
|
.has_callback = false,
|
||||||
m_gpe_methods[index.value()] = node_ref;
|
.method = node_ref
|
||||||
|
};
|
||||||
node_ref->ref_count++;
|
node_ref->ref_count++;
|
||||||
|
dprintln("Enabled {}", name);
|
||||||
dprintln("Enabled GPE {}", index.value(), byte, bit);
|
}
|
||||||
|
|
||||||
return BAN::Iteration::Continue;
|
return BAN::Iteration::Continue;
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
set_irq(irq);
|
set_irq(irq);
|
||||||
InterruptController::get().enable_irq(irq);
|
InterruptController::get().enable_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,41 +1135,12 @@ 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;
|
||||||
{ \
|
|
||||||
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.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, \
|
|
||||||
}; \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
|
|
||||||
FIND_GPE(0);
|
|
||||||
FIND_GPE(1);
|
|
||||||
|
|
||||||
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++)
|
for (uint8_t i = 0; i < gpe_blk_len / 2; i++)
|
||||||
{
|
{
|
||||||
auto status = ({ auto tmp = gpe; tmp.address += i; tmp; });
|
auto status = ({ auto tmp = gpe_blk; tmp.address += i; tmp; });
|
||||||
auto enabled = ({ auto tmp = gpe; tmp.address += (gpe_blk_len / 2) + 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());
|
const uint8_t pending = MUST(status.read()) & MUST(enabled.read());
|
||||||
if (pending == 0)
|
if (pending == 0)
|
||||||
continue;
|
continue;
|
||||||
|
@ -971,34 +1150,47 @@ acpi_release_global_lock:
|
||||||
if (!(pending & (1 << bit)))
|
if (!(pending & (1 << bit)))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
const auto index = base + i * 8 + bit;
|
const auto gpe = base + i * 8 + bit;
|
||||||
if (auto* method = m_gpe_methods[index]; method == nullptr)
|
if (auto& method = m_gpe_methods[gpe]; method.method == nullptr)
|
||||||
dwarnln("No handler for _GPE {}", index);
|
dwarnln("No handler for _GPE {}", gpe);
|
||||||
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
|
else
|
||||||
dprintln("handled _GPE {}", index);
|
{
|
||||||
|
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));
|
MUST(status.write(pending));
|
||||||
return true;
|
handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
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;
|
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
|
||||||
|
|
|
@ -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 {};
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue