blob: 13ce7c516061e94adcf9ab2e3c4fb0b92fc3a816 [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/tpm/tpm.h"
#include <fuchsia/hardware/tpmimpl/cpp/banjo.h>
#include <fuchsia/hardware/tpmimpl/llcpp/fidl.h>
#include <lib/ddk/debug.h>
#include <lib/fit/defer.h>
#include <zircon/errors.h>
#include <zircon/status.h>
#include "src/devices/tpm/drivers/tpm/commands.h"
#include "src/devices/tpm/drivers/tpm/registers.h"
#include "src/devices/tpm/drivers/tpm/tpm_bind.h"
namespace tpm {
using fuchsia_hardware_tpmimpl::wire::RegisterAddress;
zx_status_t TpmDevice::Create(void *ctx, zx_device_t *parent) {
ddk::TpmImplProtocolClient tpm(parent);
if (!tpm.is_valid()) {
zxlogf(ERROR, "Failed to get TPM impl!");
return ZX_ERR_NOT_SUPPORTED;
}
auto endpoints = fidl::CreateEndpoints<fuchsia_hardware_tpmimpl::TpmImpl>();
if (endpoints.is_error()) {
return endpoints.error_value();
}
tpm.ConnectServer(endpoints->server.TakeChannel());
fidl::WireSyncClient<fuchsia_hardware_tpmimpl::TpmImpl> client(std::move(endpoints->client));
auto device = std::make_unique<TpmDevice>(parent, std::move(client));
zx_status_t status = device->DdkAdd(ddk::DeviceAddArgs("tpm"));
if (status == ZX_OK) {
__UNUSED auto unused = device.release();
}
return status;
}
void TpmDevice::DdkInit(ddk::InitTxn txn) {
zx_status_t status = ZX_OK;
auto replier = fit::defer([&status, &txn]() { txn.Reply(status); });
StsReg sts;
if ((status = sts.ReadFrom(tpm_)) != ZX_OK) {
return;
}
if (sts.tpm_family() != TpmFamily::kTpmFamily20) {
zxlogf(ERROR, "unsupported TPM family, expected 2.0");
status = ZX_ERR_NOT_SUPPORTED;
return;
}
}
void TpmDevice::DdkSuspend(ddk::SuspendTxn txn) {
std::optional<TpmShutdownCmd> cmd;
switch (txn.suspend_reason()) {
case DEVICE_SUSPEND_REASON_REBOOT:
case DEVICE_SUSPEND_REASON_REBOOT_BOOTLOADER:
case DEVICE_SUSPEND_REASON_REBOOT_RECOVERY:
case DEVICE_SUSPEND_REASON_POWEROFF:
cmd = TpmShutdownCmd(TPM_SU_CLEAR);
break;
case DEVICE_SUSPEND_REASON_SUSPEND_RAM:
cmd = TpmShutdownCmd(TPM_SU_STATE);
break;
default:
zxlogf(WARNING, "Unknown suspend state %d", txn.requested_state());
txn.Reply(ZX_OK, DEV_POWER_STATE_D0);
return;
}
if (cmd.has_value()) {
// TODO(fxbug.dev/81433): move this onto a command-processing thread.
zx_status_t result = DoCommand(&cmd.value().hdr);
if (result != ZX_OK) {
zxlogf(ERROR, "Error sending TPM shutdown command: %s", zx_status_get_string(result));
txn.Reply(result, DEV_POWER_STATE_D0);
return;
}
}
txn.Reply(ZX_OK, txn.requested_state());
}
zx_status_t TpmDevice::DoCommand(TpmCmdHeader *cmd) {
// See section 5.5.2.2 of the client platform spec.
zx_status_t status = StsReg().set_command_ready(1).WriteTo(tpm_);
if (status != ZX_OK) {
return status;
}
StsReg sts;
do {
status = sts.ReadFrom(tpm_);
if (status != ZX_OK) {
return status;
}
} while (!sts.command_ready());
auto finish_command = fit::defer([this]() {
zx_status_t status = StsReg().set_command_ready(1).WriteTo(tpm_);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to write to TPM while finishing command.");
return;
}
});
size_t command_size = betoh32(cmd->command_size);
uint8_t *buf = reinterpret_cast<uint8_t *>(cmd);
while (command_size > 1) {
status = sts.ReadFrom(tpm_);
if (status != ZX_OK) {
return status;
}
size_t burst_count = sts.burst_count();
size_t burst = std::min(burst_count, command_size - 1);
zxlogf(DEBUG, "Writing burst of %zu bytes, burst_count = %zu cmd_size = %zu", burst,
burst_count, command_size);
if (burst == 0) {
zxlogf(WARNING, "TPM burst is zero when it shouldn't be.");
continue;
}
auto view = fidl::VectorView<uint8_t>::FromExternal(buf, burst);
auto result = tpm_.Write(0, RegisterAddress::kTpmDataFifo, view);
if (!result.ok()) {
zxlogf(ERROR, "FIDL call failed!");
return result.status();
}
if (result->result.is_err()) {
zxlogf(ERROR, "Failed to write: %d", result->result.err());
return result->result.err();
}
buf += burst;
command_size -= burst;
}
// There should be exactly one byte left.
if (command_size != 1) {
return ZX_ERR_BAD_STATE;
}
do {
status = sts.ReadFrom(tpm_);
if (status != ZX_OK) {
return status;
}
} while (sts.sts_valid() == 0);
if (sts.expect() != 1) {
zxlogf(ERROR, "TPM should expect more data!");
return ZX_ERR_BAD_STATE;
}
auto result =
tpm_.Write(0, RegisterAddress::kTpmDataFifo, fidl::VectorView<uint8_t>::FromExternal(buf, 1));
if (!result.ok()) {
zxlogf(ERROR, "FIDL call failed!");
return result.status();
}
if (result->result.is_err()) {
zxlogf(ERROR, "Failed to write: %d", result->result.err());
return result->result.err();
}
status = sts.ReadFrom(tpm_);
if (status != ZX_OK) {
return status;
}
if (sts.expect() == 1) {
zxlogf(ERROR, "TPM expected more bytes than we wrote.");
return ZX_ERR_INTERNAL;
}
status = StsReg().set_tpm_go(1).WriteTo(tpm_);
if (status != ZX_OK) {
return status;
}
status = sts.ReadFrom(tpm_);
if (status != ZX_OK) {
return status;
}
while (sts.data_avail() == 0) {
// Wait for a response.
status = sts.ReadFrom(tpm_);
if (status != ZX_OK) {
return status;
}
zx::nanosleep(zx::deadline_after(zx::usec(500)));
}
while (sts.data_avail()) {
// TODO(fxbug.dev/76095): actually return the result of the command.
size_t burst_count = sts.burst_count();
if (burst_count != 0) {
auto result = tpm_.Read(0, RegisterAddress::kTpmDataFifo, burst_count);
if (!result.ok()) {
zxlogf(ERROR, "FIDL call failed!");
return result.status();
}
if (result->result.is_err()) {
zxlogf(ERROR, "Failed to read: %d", result->result.err());
return result->result.err();
}
}
status = sts.ReadFrom(tpm_);
if (status != ZX_OK) {
return status;
}
}
return ZX_OK;
}
static const zx_driver_ops_t driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = TpmDevice::Create;
return ops;
}();
} // namespace tpm
// clang-format off
ZIRCON_DRIVER(tpm, tpm::driver_ops, "zircon", "0.1");