blob: 0936f1c38543ad48c10a7576234d244c62c57bd0 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/devices/board/drivers/x86/acpi-dev/dev-ec.h"
#include <lib/ddk/hw/inout.h>
#include <zircon/errors.h>
#include <ddktl/device.h>
namespace acpi_ec {
namespace {
class RealIoPort : public IoPortInterface {
public:
uint8_t inp(uint16_t port) override { return ::inp(port); }
void outp(uint16_t port, uint8_t value) override { ::outp(port, value); }
zx_status_t Map(uint16_t port) override {
return zx_ioports_request(get_ioport_resource(parent_), port, 1);
}
explicit RealIoPort(zx_device_t* parent) : parent_(parent) {}
~RealIoPort() override = default;
private:
zx_device_t* parent_;
};
} // namespace
zx_status_t EcDevice::Create(zx_device_t* parent, acpi::Acpi* acpi, ACPI_HANDLE handle) {
auto device =
std::make_unique<EcDevice>(parent, acpi, handle, std::make_unique<RealIoPort>(parent));
zx_status_t status = device->Init();
if (status == ZX_OK) {
// The DDK takes ownership of the device.
[[maybe_unused]] auto unused = device.release();
zxlogf(INFO, "initialised acpi-ec");
} else {
zxlogf(ERROR, "Failed to init acpi-ec: %s", zx_status_get_string(status));
}
return status;
}
zx_status_t EcDevice::Init() {
// Do we need global lock?
zx::result<bool> use_glk = NeedsGlobalLock();
if (use_glk.is_error()) {
return use_glk.error_value();
}
use_global_lock_ = *use_glk;
inspect_.GetRoot().CreateBool("use-global-lock", use_global_lock_, &inspect_);
// Create event.
zx_status_t status = zx::event::create(0, &irq_);
if (status != ZX_OK) {
return status;
}
// Find GPE info.
zx::result<std::pair<ACPI_HANDLE, uint32_t>> gpe_info = GetGpeInfo();
if (gpe_info.is_error()) {
return gpe_info.error_value();
}
gpe_info_ = *gpe_info;
// Find I/O ports and map them.
zx::result<> io_status = SetupIo();
if (io_status.is_error()) {
return io_status.error_value();
}
// Set up GPE handler.
acpi::status<> gpe_status = acpi_->InstallGpeHandler(
gpe_info->first, gpe_info->second, ACPI_GPE_EDGE_TRIGGERED, EcDevice::GpeHandlerThunk, this);
if (gpe_status.is_error()) {
return gpe_status.zx_status_value();
}
gpe_status = acpi_->EnableGpe(gpe_info->first, gpe_info->second);
if (gpe_status.is_error()) {
return gpe_status.zx_status_value();
}
// Start transaction thread -- some boards seem to call into the address space handler
// from AML bytecode when you call InstallAddressSpaceHandler(), so we need to do this first.
txn_thread_ = std::thread([this]() { TransactionThread(); });
// Install address space handler.
acpi::status<> addr_space_status = acpi_->InstallAddressSpaceHandler(
handle_, ACPI_ADR_SPACE_EC, EcDevice::AddressSpaceThunk, nullptr, this);
if (addr_space_status.is_error()) {
return ZX_ERR_INTERNAL;
}
// Start query thread now that we're fully ready to service queries.
query_thread_ = std::thread([this]() { QueryThread(); });
status = DdkAdd(ddk::DeviceAddArgs("ec")
.set_proto_id(ZX_PROTOCOL_MISC)
.set_inspect_vmo(inspect_.DuplicateVmo()));
return status;
}
void EcDevice::DdkUnbind(ddk::UnbindTxn txn) {
irq_.signal(0, EcSignal::kEcShutdown);
acpi::status<> status = acpi_->DisableGpe(gpe_info_.first, gpe_info_.second);
if (status.is_error()) {
zxlogf(WARNING, "Failed to disable GPE: %d", status.status_value());
}
status = acpi_->RemoveGpeHandler(gpe_info_.first, gpe_info_.second, EcDevice::GpeHandlerThunk);
if (status.is_error()) {
zxlogf(WARNING, "Failed to remove GPE handler: %d", status.status_value());
}
status =
acpi_->RemoveAddressSpaceHandler(handle_, ACPI_ADR_SPACE_EC, EcDevice::AddressSpaceThunk);
if (status.is_error()) {
zxlogf(WARNING, "failed to remove address space handler: %d", status.status_value());
}
txn.Reply();
}
void EcDevice::HandleGpe() {
uint8_t data = io_ports_->inp(cmd_port_);
zx_signals_t pending = 0;
zx_signals_t clear = 0;
// IBF:1 = EC is yet to read last byte we wrote, so we can't write another.
if ((data & EcStatus::kIbf)) {
clear |= EcSignal::kCanWrite;
} else {
pending |= EcSignal::kCanWrite;
}
// OBF:1 = EC has some data ready for us to read.
if ((data & EcStatus::kObf)) {
pending |= EcSignal::kCanRead;
} else {
clear |= EcSignal::kCanRead;
}
// SCI_EVT:1 = EC wants us to run a query command.
if ((data & EcStatus::kSciEvt)) {
pending |= EcSignal::kPendingEvent;
} else {
clear |= EcSignal::kPendingEvent;
}
irq_.signal(clear, pending);
}
ACPI_STATUS EcDevice::SpaceRequest(uint32_t function, ACPI_PHYSICAL_ADDRESS paddr, uint32_t width,
UINT64* value) {
if (width != 8 && width != 16 && width != 32 && width != 64) {
return AE_BAD_PARAMETER;
}
if (paddr > UINT8_MAX || paddr - 1 + (width / 8) > UINT8_MAX) {
return AE_BAD_PARAMETER;
}
uint8_t addr = paddr & UINT8_MAX;
uint8_t bytes = static_cast<uint8_t>(width / 8);
uint8_t* value_bytes = reinterpret_cast<uint8_t*>(value);
if (function == ACPI_WRITE) {
for (uint8_t i = 0; i < bytes; i++) {
zx_status_t status = Write(addr + i, value_bytes[i]);
if (status != ZX_OK) {
return AE_ERROR;
}
}
} else {
for (uint8_t i = 0; i < bytes; i++) {
zx::result<uint8_t> result = Read(addr + i);
if (result.is_error()) {
return AE_ERROR;
}
value_bytes[i] = *result;
}
}
return AE_OK;
}
void EcDevice::TransactionThread() {
zx_signals_t pending = 0;
do {
zx::result<zx_signals_t> status = WaitForIrq(EcSignal::kTransactionReady);
if (status.is_error()) {
if (status.error_value() != ZX_ERR_CANCELED) {
zxlogf(ERROR, "irq wait failed: %s", status.status_string());
}
break;
}
irq_.signal(EcSignal::kTransactionReady, 0);
// Take the current transaction queue so we can operate on it.
std::vector<Transaction*> txns;
{
std::scoped_lock lock(transaction_lock_);
txns.swap(transaction_queue_);
}
// If we use the global lock, grab it before executing transactions.
uint32_t global_lock;
if (use_global_lock_) {
acpi::status<uint32_t> ret = acpi_->AcquireGlobalLock(0xffff);
if (ret.is_error()) {
zxlogf(ERROR, "failed to acquire global lock: %d", ret.status_value());
// Fail this batch of transactions.
for (auto txn : txns) {
txn->status = ret.zx_status_value();
sync_completion_signal(&txn->done);
}
continue;
}
global_lock = *ret;
}
for (auto txn : txns) {
txn->status = DoTransaction(txn);
sync_completion_signal(&txn->done);
finished_txns_.Add(1);
}
if (use_global_lock_) {
acpi::status<> ret = acpi_->ReleaseGlobalLock(global_lock);
if (ret.is_error()) {
zxlogf(ERROR, "failed to release global lock: %d", ret.status_value());
// Not a lot we can do here.
}
}
} while ((pending & kEcShutdown) == 0);
}
zx_status_t EcDevice::DoTransaction(Transaction* txn) {
// Clear "can write", so that we don't spuriously write data before the command has been received.
irq_.signal(EcSignal::kCanWrite, 0);
// Issue the command.
io_ports_->outp(cmd_port_, txn->op);
switch (txn->op) {
case EcCmd::kRead: {
// Wait until we can write the address.
zx::result<zx_signals_t> status = WaitForIrq(EcSignal::kCanWrite);
if (status.is_error()) {
return status.error_value();
}
irq_.signal(EcSignal::kCanWrite, 0);
// Specify the address
io_ports_->outp(data_port_, txn->addr);
// Wait until we can read the value.
status = WaitForIrq(EcSignal::kCanRead);
if (status.is_error()) {
return status.error_value();
}
irq_.signal(EcSignal::kCanRead, 0);
// Return the value.
txn->value = io_ports_->inp(data_port_);
break;
}
case EcCmd::kWrite: {
// Wait until we can write the address.
zx::result<zx_signals_t> status = WaitForIrq(EcSignal::kCanWrite);
if (status.is_error()) {
return status.error_value();
}
irq_.signal(EcSignal::kCanWrite, 0);
// Specify the address
io_ports_->outp(data_port_, txn->addr);
// Wait until we can write the value.
status = WaitForIrq(EcSignal::kCanWrite);
if (status.is_error()) {
return status.error_value();
}
irq_.signal(EcSignal::kCanWrite, 0);
// Specify the value
io_ports_->outp(data_port_, txn->value);
// Wait for EC to read the value.
status = WaitForIrq(EcSignal::kCanWrite);
if (status.is_error()) {
return status.error_value();
}
irq_.signal(EcSignal::kCanWrite, 0);
break;
}
case EcCmd::kQuery: {
// Wait for the EC to respond.
zx::result<zx_signals_t> status = WaitForIrq(EcSignal::kCanRead);
if (status.is_error()) {
return status.error_value();
}
irq_.signal(EcSignal::kCanRead, 0);
txn->value = io_ports_->inp(data_port_);
break;
}
}
return ZX_OK;
}
zx::result<zx_signals_t> EcDevice::WaitForIrq(zx_signals_t signals) {
zx_signals_t pending;
signals |= EcSignal::kEcShutdown;
zx_status_t status = irq_.wait_one(signals, zx::time::infinite(), &pending);
if (status != ZX_OK) {
return zx::error(status);
}
if (pending & EcSignal::kEcShutdown) {
return zx::error(ZX_ERR_CANCELED);
}
return zx::ok(pending);
}
void EcDevice::QueryThread() {
while (true) {
zx::result<zx_signals_t> status = WaitForIrq(EcSignal::kPendingEvent);
if (status.is_error()) {
if (status.error_value() != ZX_ERR_CANCELED) {
zxlogf(ERROR, "irq wait failed: %s", status.status_string());
}
break;
}
uint8_t event;
while (io_ports_->inp(cmd_port_) & EcStatus::kSciEvt) {
zx::result<uint8_t> ret = Query();
if (ret.is_error()) {
break;
}
event = *ret;
if (event == 0) {
break;
}
char method[5] = {0};
snprintf(method, sizeof(method), "_Q%02x", event);
last_query_.Set(method);
// Log lid close events.
// TODO(https://fxbug.dev/42062794): Remove this logging when bug is fixed.
if (event == 1) {
zxlogf(INFO, "received event: %s", method);
}
// Don't care about return value.
[[maybe_unused]] auto status = acpi_->EvaluateObject(handle_, method, std::nullopt);
}
irq_.signal(EcSignal::kPendingEvent, 0);
}
}
zx_status_t EcDevice::Write(uint8_t addr, uint8_t val) {
Transaction txn{
.op = EcCmd::kWrite,
.addr = addr,
.value = val,
};
zx_status_t status = QueueTransactionAndWait(&txn);
return status;
}
zx::result<uint8_t> EcDevice::Read(uint8_t addr) {
Transaction txn{
.op = EcCmd::kRead,
.addr = addr,
};
zx_status_t status = QueueTransactionAndWait(&txn);
if (status == ZX_OK) {
return zx::ok(txn.value);
}
return zx::error(status);
}
zx::result<uint8_t> EcDevice::Query() {
Transaction txn{
.op = EcCmd::kQuery,
};
zx_status_t status = QueueTransactionAndWait(&txn);
if (status == ZX_OK) {
return zx::ok(txn.value);
}
return zx::error(status);
}
zx_status_t EcDevice::QueueTransactionAndWait(Transaction* txn) {
{
std::scoped_lock lock(transaction_lock_);
transaction_queue_.emplace_back(txn);
zx_status_t status = irq_.signal(0, EcSignal::kTransactionReady);
if (status != ZX_OK) {
zxlogf(ERROR, "failed to signal transaction ready");
return status;
}
}
sync_completion_wait(&txn->done, ZX_TIME_INFINITE);
return txn->status;
}
zx::result<bool> EcDevice::NeedsGlobalLock() {
acpi::status<acpi::UniquePtr<ACPI_OBJECT>> ret =
acpi_->EvaluateObject(handle_, "_GLK", std::nullopt);
if (ret.is_error()) {
if (ret.error_value() == AE_NOT_FOUND) {
// Not found means no global lock.
return zx::ok(false);
}
zxlogf(ERROR, "EvaluateObject for _GLK failed: %d", ret.error_value());
return zx::error(ret.zx_status_value());
}
auto obj = std::move(*ret);
if (obj->Type != ACPI_TYPE_INTEGER) {
zxlogf(ERROR, "_GLK had wrong type: %d", obj->Type);
return zx::error(ZX_ERR_WRONG_TYPE);
}
return zx::ok(obj->Integer.Value != 0);
}
zx::result<std::pair<ACPI_HANDLE, uint32_t>> EcDevice::GetGpeInfo() {
acpi::status<acpi::UniquePtr<ACPI_OBJECT>> ret =
acpi_->EvaluateObject(handle_, "_GPE", std::nullopt);
if (ret.is_error()) {
return zx::error(ret.zx_status_value());
}
ACPI_HANDLE block = nullptr;
uint32_t number = 0;
/* According to section 12.11 of ACPI v6.1, a _GPE object on this device
* evaluates to either an integer specifying bit in the GPEx_STS blocks
* to use, or a package specifying which GPE block and which bit inside
* that block to use. */
if (ret->Type == ACPI_TYPE_INTEGER) {
number = static_cast<uint32_t>(ret->Integer.Value);
} else if (ret->Type == ACPI_TYPE_PACKAGE) {
if (ret->Package.Count != 2) {
return zx::error(ZX_ERR_WRONG_TYPE);
}
ACPI_OBJECT* block_obj = &ret->Package.Elements[0];
ACPI_OBJECT* gpe_num_obj = &ret->Package.Elements[1];
if (block_obj->Type != ACPI_TYPE_LOCAL_REFERENCE) {
return zx::error(ZX_ERR_WRONG_TYPE);
}
if (gpe_num_obj->Type != ACPI_TYPE_INTEGER) {
return zx::error(ZX_ERR_WRONG_TYPE);
}
block = block_obj->Reference.Handle;
number = static_cast<uint32_t>(gpe_num_obj->Integer.Value);
} else {
return zx::error(ZX_ERR_WRONG_TYPE);
}
return zx::ok(std::make_pair(block, number));
}
zx::result<> EcDevice::SetupIo() {
size_t resource_count = 0;
acpi::status<> ret = acpi_->WalkResources(
handle_, "_CRS", [this, &resource_count](ACPI_RESOURCE* rsrc) -> acpi::status<> {
if (rsrc->Type == ACPI_RESOURCE_TYPE_END_TAG) {
return acpi::ok();
}
/* The spec says there will be at most 3 resources */
if (resource_count >= 3) {
return acpi::error(AE_BAD_DATA);
}
/* The third resource only exists on HW-Reduced platforms, which
* we don't support at the moment. */
if (resource_count == 2) {
return acpi::error(AE_NOT_IMPLEMENTED);
}
/* The two resources we're expecting are both address regions.
* First the data one, then the command one. We assume they're
* single IO ports. */
if (rsrc->Type != ACPI_RESOURCE_TYPE_IO) {
return acpi::error(AE_SUPPORT);
}
if (rsrc->Data.Io.Maximum != rsrc->Data.Io.Minimum) {
return acpi::error(AE_SUPPORT);
}
uint16_t port = rsrc->Data.Io.Minimum;
if (resource_count == 0) {
data_port_ = port;
} else {
cmd_port_ = port;
}
resource_count++;
return acpi::ok();
});
zx_status_t status = io_ports_->Map(data_port_);
if (status != ZX_OK) {
zxlogf(ERROR, "acpi-ec: Failed to map ec data port: %s", zx_status_get_string(status));
return zx::error(status);
}
status = io_ports_->Map(cmd_port_);
if (status != ZX_OK) {
zxlogf(ERROR, "acpi-ec: Failed to map ec cmd port: %s", zx_status_get_string(status));
return zx::error(status);
}
return zx::make_result(ret.zx_status_value());
}
} // namespace acpi_ec