blob: cc0690683681bf8c426424f4a9e3233328e55599 [file] [log] [blame]
// Copyright 2021 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/tpm/drivers/cr50-spi/cr50-spi.h"
#include <fidl/fuchsia.hardware.tpmimpl/cpp/wire.h>
#include <lib/async/cpp/task.h>
#include <lib/ddk/binding_driver.h>
#include <lib/ddk/debug.h>
#include <lib/ddk/device.h>
#include <lib/ddk/driver.h>
#include <lib/fit/defer.h>
#include <lib/zx/clock.h>
#include <zircon/status.h>
#include "src/devices/lib/acpi/client.h"
namespace cr50::spi {
// The best resource for how this driver should work
// is the Cr50 TPM source code. It's available here:
// https://chromium.googlesource.com/chromiumos/platform/ec/+/refs/heads/cr50_stab/chip/g/spp_tpm.c
zx_status_t Cr50SpiDevice::Create(void *ctx, zx_device_t *parent) {
auto acpi = acpi::Client::Create(parent);
if (acpi.is_error()) {
zxlogf(ERROR, "Failed to get ACPI client: %s", acpi.status_string());
return acpi.error_value();
}
auto endpoints = fidl::CreateEndpoints<fuchsia_hardware_spi::Device>();
if (endpoints.is_error()) {
return endpoints.error_value();
}
zx::result client_end =
ddk::Device<void>::DdkConnectFragmentFidlProtocol<fuchsia_hardware_spi::Service::Device>(
parent, "spi000");
if (client_end.is_error()) {
zxlogf(ERROR, "Could not connect to SPI FIDL protocol: %s", client_end.status_string());
return client_end.status_value();
}
fidl::WireSyncClient<fuchsia_hardware_spi::Device> client(std::move(*client_end));
auto dev = std::make_unique<Cr50SpiDevice>(parent, std::move(acpi.value()), std::move(client));
return dev->Bind(&dev);
}
zx_status_t Cr50SpiDevice::Bind(std::unique_ptr<Cr50SpiDevice> *dev) {
auto result = acpi_.borrow()->MapInterrupt(0);
if (!result.ok() || result->is_error()) {
zxlogf(WARNING, "Failed to get IRQ: %s",
result.ok() ? zx_status_get_string(result->error_value())
: result.FormatDescription().data());
} else {
irq_ = std::move(result->value()->irq);
irq_thread_ = std::thread(&Cr50SpiDevice::IrqThread, this);
}
zx_status_t status = loop_.StartThread("cr50-spi-fidl-thread");
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to start FIDL thread: %d", status);
return status;
}
auto can_assert = spi_->CanAssertCs();
if (!can_assert.ok()) {
zxlogf(ERROR, "Failed to send FIDL request to SPI driver: %s",
can_assert.FormatDescription().data());
return can_assert.status();
}
if (!can_assert.value().can) {
zxlogf(
ERROR,
"cr50-spi needs the ability to explicitly assert and deassert CS, which is not supported.");
return ZX_ERR_NOT_SUPPORTED;
}
status = DdkAdd(ddk::DeviceAddArgs("cr50-spi").set_inspect_vmo(inspect_.DuplicateVmo()));
[[maybe_unused]] auto unused = dev->release();
return status;
}
void Cr50SpiDevice::DdkInit(ddk::InitTxn txn) {
// Post onto the FIDL thread, since nobody will be trying to do FIDL transactions until we reply
// to the InitTxn anyway.
async::PostTask(loop_.dispatcher(), [this, txn = std::move(txn)]() mutable {
LogFirmwareVersion();
txn.Reply(ZX_OK);
});
}
void Cr50SpiDevice::DdkUnbind(ddk::UnbindTxn txn) {
unbind_txn_ = std::move(txn);
sync_completion_signal(&unbind_txn_ready_);
irq_.destroy();
if (!irq_thread_.joinable()) {
// If the IRQ thread is not joinable, it was probably never started.
// Reply ourselves.
unbind_txn_->Reply();
unbind_txn_ = std::nullopt;
}
}
void Cr50SpiDevice::DdkRelease() {
if (irq_thread_.joinable()) {
irq_thread_.join();
}
delete this;
}
void Cr50SpiDevice::TpmImplConnectServer(zx::channel server) {
fidl::BindServer(loop_.dispatcher(),
fidl::ServerEnd<fuchsia_hardware_tpmimpl::TpmImpl>(std::move(server)), this);
}
void Cr50SpiDevice::Read(ReadRequestView request, ReadCompleter::Sync &completer) {
// Cr50 ignores locality, so we do too. See section 33 of
// https://trustedcomputinggroup.org/wp-content/uploads/TCG_TPM2_r1p59_Part1_Architecture_pub.pdf.
fidl::Arena<fuchsia_hardware_tpmimpl::wire::kTpmMaxDataTransfer> alloc;
if (request->count > fuchsia_hardware_tpmimpl::wire::kTpmMaxDataTransfer) {
completer.ReplyError(ZX_ERR_INVALID_ARGS);
return;
}
WaitForReady();
fidl::VectorView<uint8_t> buffer(alloc, request->count);
auto result = DoXfer(fidl::ToUnderlying(request->address), buffer, false);
if (result.is_error()) {
completer.ReplyError(result.error_value());
} else {
completer.ReplySuccess(buffer);
}
}
void Cr50SpiDevice::Write(WriteRequestView request, WriteCompleter::Sync &completer) {
WaitForReady();
auto result = DoXfer(fidl::ToUnderlying(request->address), request->data, true);
if (result.is_error()) {
completer.ReplyError(result.error_value());
} else {
completer.ReplySuccess();
}
}
void Cr50SpiDevice::LogFirmwareVersion() {
static constexpr uint32_t kTpmFwVersionReg = 0x00000F90;
// Start reading the firmware version.
// First, write nothing to the register so we go back to the start of the FW version.
uint8_t empty[1] = {0};
auto view = fidl::VectorView<uint8_t>::FromExternal(empty);
auto status = DoXfer(kTpmFwVersionReg, view, true);
if (status.is_error()) {
zxlogf(ERROR, "failed to get tpm version :(");
return;
}
// Read in 32-byte chunks.
uint8_t fw_version[96] = {0};
uint8_t chunk[32] = {0};
size_t non_null_bytes = 0;
size_t i = 0;
do {
non_null_bytes = 0;
view = fidl::VectorView<uint8_t>::FromExternal(chunk);
WaitForReady();
status = DoXfer(kTpmFwVersionReg, view, false);
if (status.is_error()) {
zxlogf(ERROR, "failed to read firmware version: %s", status.status_string());
return;
}
for (size_t j = 0; j < std::size(chunk); j++) {
fw_version[i] = chunk[j];
i++;
non_null_bytes++;
if (chunk[j] == 0 || i >= std::size(fw_version))
break;
}
} while (non_null_bytes == std::size(chunk) && i < std::size(fw_version));
zxlogf(INFO, "TPM firmware version: %s", fw_version);
// Add an inspect node with the firmware version.
inspect_.GetRoot().CreateString("fw-version", std::string(reinterpret_cast<char *>(fw_version)),
&inspect_);
}
void Cr50SpiDevice::IrqThread() {
while (true) {
zx_status_t status = irq_.wait(nullptr);
if (status != ZX_OK) {
zxlogf(ERROR, "failed to wait for IRQ: %d", status);
break;
}
sync_completion_signal(&tpm_ready_);
}
sync_completion_wait(&unbind_txn_ready_, ZX_TIME_INFINITE);
if (unbind_txn_ != std::nullopt) {
unbind_txn_->Reply();
unbind_txn_ = std::nullopt;
}
}
void Cr50SpiDevice::WaitForReady() {
static constexpr zx_duration_t kReadyTimeout = ZX_MSEC(750); // TPM_TIMEOUT_A
if (irq_.is_valid()) {
zx_status_t status = sync_completion_wait(&tpm_ready_, kReadyTimeout);
sync_completion_reset(&tpm_ready_);
if (status != ZX_OK) {
zxlogf(WARNING, "timeout waiting for tpm");
}
} else {
// Sleep for 2ms, b/80481396
zx::nanosleep(zx::deadline_after(zx::msec(2)));
}
}
void Cr50SpiDevice::WakeUp() {
static constexpr zx::duration kSleepTime(ZX_MSEC(1000));
zx::time sleep_time = last_command_time_ + kSleepTime;
if (zx::clock::get_monotonic() >= sleep_time) {
zxlogf(INFO, "asleep for too long, waking up!");
// Wake the cr50 by asserting CS.
auto result = spi_->AssertCs();
if (!result.ok() || result.value().status != ZX_OK) {
zxlogf(ERROR, "Failed to assert SPI CS to wakeup cr50: %s",
result.ok() ? zx_status_get_string(result.value().status)
: result.FormatDescription().data());
}
auto deassert = spi_->DeassertCs();
if (!deassert.ok() || deassert.value().status != ZX_OK) {
zxlogf(ERROR, "Failed to deassert SPI CS to wakeup cr50: %s",
deassert.ok() ? zx_status_get_string(deassert.value().status)
: deassert.FormatDescription().data());
}
// Let the H1 wake up.
zx::nanosleep(zx::deadline_after(zx::usec(100)));
}
last_command_time_ = zx::clock::get_monotonic();
}
zx::result<> Cr50SpiDevice::SendHeader(uint16_t address, size_t msg_length, bool writing) {
// Start the transaction with the 4-byte magic header required by the TPM SPI protocol.
uint8_t header[4];
header[0] = msg_length - 1;
if (!writing) {
header[0] |= 0x80;
}
header[1] = 0xd4; // Addresses are always '0xd4xxxx'
header[2] = (address >> 8) & 0xff;
header[3] = address & 0xff;
auto result = spi_->ExchangeVector(fidl::VectorView<uint8_t>::FromExternal(header));
if (!result.ok()) {
zxlogf(ERROR, "send FIDL request failed: %s", result.FormatDescription().data());
return zx::error(result.status());
}
if (result.value().status != ZX_OK) {
zxlogf(ERROR, "spi xfer failed: %s", zx_status_get_string(result.value().status));
return zx::error(result.value().status);
}
// The TPM will send back a 0x1 in the last byte if it's ready, otherwise we have to do flow
// control.
uint8_t ready = result.value().rxdata[3] & 0x1;
if (!ready) {
return FlowControl();
}
return zx::ok();
}
zx::result<> Cr50SpiDevice::FlowControl() {
static constexpr zx::duration kFlowControlTimeout(ZX_MSEC(750 /* TPM_TIMEOUT_A */));
// The TPM isn't ready until we get back a 1 in the last bit.
// The Cr50 in practice always does at least 1 byte of flow control.
auto deadline = zx::deadline_after(kFlowControlTimeout);
uint8_t ready = 0;
while (!ready && zx::clock::get_monotonic() < deadline) {
auto result = spi_->ReceiveVector(1);
if (!result.ok()) {
zxlogf(ERROR, "send FIDL request failed: %s", result.FormatDescription().data());
return zx::error(result.status());
}
if (result.value().status != ZX_OK) {
zxlogf(ERROR, "spi xfer failed: %s", zx_status_get_string(result.value().status));
return zx::error(result.value().status);
}
if (result.value().data.count() != 1) {
zxlogf(ERROR, "spi returned incorrect number of bytes: %zu", result.value().data.count());
return zx::error(ZX_ERR_INTERNAL);
}
ready = result.value().data[0] & 0x1;
}
return zx::ok();
}
zx::result<> Cr50SpiDevice::DoSpiWrite(fidl::VectorView<uint8_t> &buf) {
auto result = spi_->TransmitVector(buf);
if (!result.ok()) {
return zx::error(result.status());
}
return zx::make_result(result.value().status);
}
zx::result<> Cr50SpiDevice::DoSpiRead(fidl::VectorView<uint8_t> &buf) {
auto ret_vec = spi_->ReceiveVector(buf.count());
if (!ret_vec.ok()) {
return zx::error(ret_vec.status());
}
if (ret_vec.value().status != ZX_OK) {
return zx::error(ret_vec.value().status);
}
// Put returned data in the output buffer.
memcpy(buf.data(), ret_vec.value().data.data(), ret_vec.value().data.count());
return zx::ok();
}
zx::result<> Cr50SpiDevice::DoXfer(uint16_t address, fidl::VectorView<uint8_t> &buf,
bool do_write) {
zxlogf(DEBUG, "%sing %zu bytes at 0x%x", do_write ? "writ" : "read", buf.count(), address);
WakeUp();
auto assert = spi_->AssertCs();
if (!assert.ok() || assert.value().status != ZX_OK) {
zxlogf(ERROR, "asserting spi bus failed");
return zx::error(ZX_ERR_UNAVAILABLE);
}
// TODO(https://fxbug.dev/42180237) Consider handling the error instead of ignoring it.
auto deasserter = fit::defer([this]() { (void)spi_->DeassertCs(); });
auto status = SendHeader(address, buf.count(), do_write);
if (status.is_error()) {
return status.take_error();
}
// TPM is ready - do the actual exchange.
if (do_write) {
status = DoSpiWrite(buf);
} else {
status = DoSpiRead(buf);
}
return status;
}
static const zx_driver_ops_t driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = Cr50SpiDevice::Create;
return ops;
}();
} // namespace cr50::spi
// clang-format off
ZIRCON_DRIVER(cr50-spi, cr50::spi::driver_ops, "zircon", "0.1");