blob: 748607ce10e22c3236f3aeed3e85aa8c5382b64a [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 <lib/inspect/cpp/inspector.h>
#include <lib/zx/event.h>
#include <zircon/types.h>
#include <condition_variable>
#include <ddktl/device.h>
#include "src/devices/board/lib/acpi/acpi.h"
namespace acpi_ec {
// Commands.
enum EcCmd {
kRead = 0x80,
kWrite = 0x81,
kQuery = 0x84,
};
// Status register bits.
enum EcStatus {
kSciEvt = 1 << 5,
kIbf = 1 << 1,
kObf = 1 << 0,
};
enum EcSignal {
// Status.IBF == 0, host can write next byte to EC.
kCanWrite = ZX_USER_SIGNAL_0,
// Status.OBF == 1, host can read byte from EC.
kCanRead = ZX_USER_SIGNAL_1,
// Status.SCI_EVT == 1, EC wants host to handle an event.
kPendingEvent = ZX_USER_SIGNAL_2,
// Driver is shutting down.
kEcShutdown = ZX_USER_SIGNAL_3,
// TXQ has items ready to be processed.
kTransactionReady = ZX_USER_SIGNAL_4,
};
// Represents a single transaction going to or from the EC.
struct Transaction {
// Operation to perform.
EcCmd op;
// For read or write, address to read/write. Ignored for query.
uint8_t addr = 0;
// For read: value that was read.
// For write: value to write.
// For query: event.
uint8_t value = 0;
// Status of the transaction.
zx_status_t status = ZX_OK;
// Signalled when this transaction is ready to be consumed by whatever initiated it.
// Transactions are usually performed in synchronous contexts (i.e. AML code), so this is OK.
sync_completion_t done;
};
class IoPortInterface {
public:
virtual uint8_t inp(uint16_t port) = 0;
virtual void outp(uint16_t port, uint8_t value) = 0;
virtual zx_status_t Map(uint16_t port) = 0;
virtual ~IoPortInterface() = default;
};
// The interface used by this driver is described in ACPI v6.4 section 12, "ACPI Embedded Controller
// Interface Specification".
class EcDevice;
using DeviceType = ddk::Device<EcDevice, ddk::Unbindable>;
class EcDevice : public DeviceType {
public:
EcDevice(zx_device_t* parent, acpi::Acpi* acpi, ACPI_HANDLE handle,
std::unique_ptr<IoPortInterface> interface)
: DeviceType(parent), acpi_(acpi), handle_(handle), io_ports_(std::move(interface)) {}
static zx_status_t Create(zx_device_t* parent, acpi::Acpi* acpi, ACPI_HANDLE handle);
zx_status_t Init();
void DdkRelease() {
txn_thread_.join();
query_thread_.join();
delete this;
}
void DdkUnbind(ddk::UnbindTxn txn);
// Space request handler.
ACPI_STATUS SpaceRequest(uint32_t function, ACPI_PHYSICAL_ADDRESS addr, uint32_t width,
UINT64* value);
// Called when a GPE is triggered.
void HandleGpe();
// Write |value| to |addr| on the EC.
zx_status_t Write(uint8_t addr, uint8_t val);
// Read |addr| from the EC.
zx::result<uint8_t> Read(uint8_t addr);
// Query the EC for pending events, and return the event code.
zx::result<uint8_t> Query();
private:
// Transaction thread. This is the only thread that handles I/O with the EC.
// There are two exceptions:
// * The Query thread checks the status register to see if there are more events pending.
// * The GPE handler (called from an ACPI interrupt thread) checks the status register to
// determine which bits to set on |irq_|.
void TransactionThread();
// Perform a transaction, only called from the transaction thread.
zx_status_t DoTransaction(Transaction* txn);
// Queue a transaction and block until it is complete.
zx_status_t QueueTransactionAndWait(Transaction* txn) __TA_EXCLUDES(transaction_lock_);
// Wait for the given signal(s) to be set.
// Returns which signals were set, or ZX_ERR_CANCELED if the driver is shutting down.
zx::result<zx_signals_t> WaitForIrq(zx_signals_t signals);
// This thread watches for kPendingEvent on |irq_| and then queues queries until SCI_EVT becomes
// unset.
void QueryThread();
// Returns true if we need to acquire the global lock when interacting with the EC.
zx::result<bool> NeedsGlobalLock();
// Returns information about the GPE we use.
zx::result<std::pair<ACPI_HANDLE, uint32_t>> GetGpeInfo();
// Configures and maps I/O ports.
zx::result<> SetupIo();
static uint32_t GpeHandlerThunk(ACPI_HANDLE, uint32_t, void* ctx) {
static_cast<EcDevice*>(ctx)->HandleGpe();
return ACPI_REENABLE_GPE;
}
static ACPI_STATUS AddressSpaceThunk(uint32_t func, ACPI_PHYSICAL_ADDRESS addr, uint32_t width,
UINT64* value, void* handler_ctx, void* region_ctx) {
return static_cast<EcDevice*>(handler_ctx)->SpaceRequest(func, addr, width, value);
}
std::mutex transaction_lock_;
std::vector<Transaction*> transaction_queue_ __TA_GUARDED(transaction_lock_);
std::thread txn_thread_;
std::thread query_thread_;
acpi::Acpi* acpi_;
ACPI_HANDLE handle_;
uint16_t data_port_ = 0;
uint16_t cmd_port_ = 0;
std::unique_ptr<IoPortInterface> io_ports_;
bool use_global_lock_ = false;
zx::event irq_;
std::pair<ACPI_HANDLE, uint32_t> gpe_info_;
inspect::Inspector inspect_;
inspect::UintProperty finished_txns_ = inspect_.GetRoot().CreateUint("finished-txns", 0);
inspect::StringProperty last_query_ = inspect_.GetRoot().CreateString("last-query", "N/A");
};
} // namespace acpi_ec