[tpm] Initial TPM driver implementation.
All this does is send a shutdown command to the TPM when its suspend
hook is called.
Bug: 76095
Bug: 76072
Change-Id: I033ee762e58b9e425ee7b42810ff5425f6f39cf2
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/554386
Commit-Queue: Simon Shields <simonshields@google.com>
Reviewed-by: Abdulla Kamar <abdulla@google.com>
Reviewed-by: Alex Legg <alexlegg@google.com>
diff --git a/boards/common/x64-common.gni b/boards/common/x64-common.gni
index cec152b..7452b45 100644
--- a/boards/common/x64-common.gni
+++ b/boards/common/x64-common.gni
@@ -24,6 +24,7 @@
"//src/devices/rtc/drivers/intel-rtc",
"//src/devices/serial/drivers/uart16550",
"//src/devices/spi/drivers/intel-gspi",
+ "//src/devices/tpm/drivers/tpm",
"//src/devices/usb/drivers/xhci-rewrite:xhci",
"//src/graphics/display/drivers/intel-i915",
"//src/graphics/display/drivers/simple:simple.amd-kaveri",
diff --git a/src/devices/bind/fuchsia.tpm/BUILD.gn b/src/devices/bind/fuchsia.tpm/BUILD.gn
new file mode 100644
index 0000000..844bba4
--- /dev/null
+++ b/src/devices/bind/fuchsia.tpm/BUILD.gn
@@ -0,0 +1,9 @@
+# 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.
+
+import("//build/bind/bind.gni")
+
+bind_library("fuchsia.tpm") {
+ source = "fuchsia.tpm.bind"
+}
diff --git a/src/devices/bind/fuchsia.tpm/fuchsia.tpm.bind b/src/devices/bind/fuchsia.tpm/fuchsia.tpm.bind
new file mode 100644
index 0000000..394b87dc
--- /dev/null
+++ b/src/devices/bind/fuchsia.tpm/fuchsia.tpm.bind
@@ -0,0 +1,9 @@
+// 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.
+
+library fuchsia.tpm;
+
+extend uint fuchsia.BIND_PROTOCOL {
+ IMPL = 159,
+};
diff --git a/src/devices/tpm/BUILD.gn b/src/devices/tpm/BUILD.gn
index 6419049..f6db470 100644
--- a/src/devices/tpm/BUILD.gn
+++ b/src/devices/tpm/BUILD.gn
@@ -4,10 +4,16 @@
group("drivers") {
testonly = true
- deps = [ "drivers/cr50-spi" ]
+ deps = [
+ "drivers/cr50-spi",
+ "drivers/tpm",
+ ]
}
group("tests") {
testonly = true
- deps = [ "drivers/cr50-spi:tests" ]
+ deps = [
+ "drivers/cr50-spi:tests",
+ "drivers/tpm:tests",
+ ]
}
diff --git a/src/devices/tpm/drivers/tpm/BUILD.gn b/src/devices/tpm/drivers/tpm/BUILD.gn
new file mode 100644
index 0000000..ab4a8c7
--- /dev/null
+++ b/src/devices/tpm/drivers/tpm/BUILD.gn
@@ -0,0 +1,66 @@
+# 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.
+
+import("//build/bind/bind.gni")
+import("//build/components.gni")
+import("//build/config/fuchsia/rules.gni")
+import("//build/test.gni")
+
+driver_bind_rules("tpm_bind") {
+ rules = "tpm.bind"
+ header_output = "tpm_bind.h"
+ tests = "bind_tests.json"
+ deps = [ "//src/devices/bind/fuchsia.tpm" ]
+}
+
+common_deps = [
+ ":tpm_bind",
+ "//sdk/banjo/fuchsia.hardware.tpmimpl:fuchsia.hardware.tpmimpl_banjo_cpp",
+ "//sdk/fidl/fuchsia.hardware.tpmimpl:fuchsia.hardware.tpmimpl_llcpp",
+ "//src/devices/lib/driver",
+ "//src/lib/ddktl",
+ "//zircon/system/ulib/async-loop:async-loop-cpp",
+ "//zircon/system/ulib/hwreg",
+ "//zircon/system/ulib/zx",
+]
+
+driver_module("tpm-driver") {
+ sources = [ "tpm.cc" ]
+ deps = common_deps
+}
+
+fuchsia_system_package("tpm-pkg") {
+ package_name = "tpm"
+ deps = [ ":tpm-driver" ]
+
+ allowed_in_extra_deps = true
+}
+
+test("tpm-driver-test-bin") {
+ output_name = "tpm-driver-test"
+ sources = [
+ "tpm-test.cc",
+ "tpm.cc",
+ ]
+ deps = common_deps + [
+ "//src/devices/testing/mock-ddk",
+ "//zircon/system/ulib/zxtest",
+ ]
+}
+
+fuchsia_unittest_package("tpm-driver-test") {
+ deps = [ ":tpm-driver-test-bin" ]
+}
+
+group("tpm") {
+ deps = [ ":tpm-pkg" ]
+}
+
+group("tests") {
+ testonly = true
+ deps = [
+ ":tpm-driver-test",
+ ":tpm_bind_test",
+ ]
+}
diff --git a/src/devices/tpm/drivers/tpm/bind_tests.json b/src/devices/tpm/drivers/tpm/bind_tests.json
new file mode 100644
index 0000000..7077348
--- /dev/null
+++ b/src/devices/tpm/drivers/tpm/bind_tests.json
@@ -0,0 +1,16 @@
+[
+ {
+ "device": {
+ "fuchsia.BIND_PROTOCOL": "fuchsia.spi.BIND_PROTOCOL.IMPL"
+ },
+ "expected": "abort",
+ "name": "Wrong protocol"
+ },
+ {
+ "device": {
+ "fuchsia.BIND_PROTOCOL": "fuchsia.tpm.BIND_PROTOCOL.IMPL"
+ },
+ "expected": "match",
+ "name": "Match"
+ }
+]
diff --git a/src/devices/tpm/drivers/tpm/commands.h b/src/devices/tpm/drivers/tpm/commands.h
new file mode 100644
index 0000000..eae3126
--- /dev/null
+++ b/src/devices/tpm/drivers/tpm/commands.h
@@ -0,0 +1,48 @@
+// 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.
+
+#ifndef SRC_DEVICES_TPM_DRIVERS_TPM_COMMANDS_H_
+#define SRC_DEVICES_TPM_DRIVERS_TPM_COMMANDS_H_
+
+#include <endian.h>
+#include <zircon/compiler.h>
+#include <zircon/types.h>
+
+// The definitions here are spread across two parts of the TPM2 spec:
+// - part 2, "structures".
+// - part 3, "commands".
+//
+// https://trustedcomputinggroup.org/wp-content/uploads/TCG_TPM2_r1p59_Part2_Structures_pub.pdf
+// https://trustedcomputinggroup.org/wp-content/uploads/TCG_TPM2_r1p59_Part3_Commands_pub.pdf
+
+#define TPM_ST_NO_SESSIONS (0x8001)
+#define TPM_CC_SHUTDOWN (0x0145)
+
+#define TPM_SU_CLEAR 0x00
+#define TPM_SU_STATE 0x01
+
+struct TpmCmdHeader {
+ uint16_t tag;
+ uint32_t command_size;
+ uint32_t command_code;
+} __PACKED;
+
+struct TpmShutdownCmd {
+ TpmCmdHeader hdr;
+ uint16_t shutdown_type;
+
+ explicit TpmShutdownCmd(uint16_t type) : shutdown_type(htobe16(type)) {
+ hdr = {.tag = htobe16(TPM_ST_NO_SESSIONS),
+ .command_size = htobe32(sizeof(TpmShutdownCmd)),
+ .command_code = htobe32(TPM_CC_SHUTDOWN)};
+ }
+} __PACKED;
+
+struct TpmShutdownResponse {
+ uint16_t tag;
+ uint32_t response_size;
+ uint32_t response_code;
+};
+
+#endif // SRC_DEVICES_TPM_DRIVERS_TPM_COMMANDS_H_
diff --git a/src/devices/tpm/drivers/tpm/registers.h b/src/devices/tpm/drivers/tpm/registers.h
new file mode 100644
index 0000000..b07733e
--- /dev/null
+++ b/src/devices/tpm/drivers/tpm/registers.h
@@ -0,0 +1,111 @@
+// 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.
+
+#ifndef SRC_DEVICES_TPM_DRIVERS_TPM_REGISTERS_H_
+#define SRC_DEVICES_TPM_DRIVERS_TPM_REGISTERS_H_
+
+#include <fuchsia/hardware/tpmimpl/llcpp/fidl.h>
+#include <lib/ddk/debug.h>
+#include <lib/zx/status.h>
+
+#include <hwreg/bitfields.h>
+
+namespace tpm {
+
+using fuchsia_hardware_tpmimpl::wire::RegisterAddress;
+
+enum TpmFamily {
+ // TPM v1.2
+ kTpmFamily12 = 0,
+ // TPM v2.0
+ kTpmFamily20 = 1,
+};
+
+// This base class just implements convenience |ReadFrom| and |WriteTo| methods that use a supplied
+// FIDL client to write/read a TPM register.
+template <class SelfType, uint16_t address>
+class TpmReg : public hwreg::RegisterBase<SelfType, uint32_t, hwreg::EnablePrinter> {
+ public:
+ zx_status_t ReadFrom(fidl::WireSyncClient<fuchsia_hardware_tpmimpl::TpmImpl>& client) {
+ auto result = client.Read(0, RegisterAddress(address), 4);
+ if (!result.ok()) {
+ zxlogf(ERROR, "Failed to send read FIDL request: %s", result.FormatDescription().data());
+ return result.status();
+ }
+ if (result->result.is_err()) {
+ zxlogf(ERROR, "Failed to read: %d", result->result.err());
+ return result->result.err();
+ }
+ auto& data = result->result.response().data;
+ if (data.count() != 4) {
+ zxlogf(ERROR, "Incorrect response size");
+ return ZX_ERR_BAD_STATE;
+ }
+
+ uint32_t val = 0;
+ memcpy(&val, data.data(), sizeof(val));
+ this->set_reg_value(val);
+ return ZX_OK;
+ }
+
+ zx_status_t WriteTo(fidl::WireSyncClient<fuchsia_hardware_tpmimpl::TpmImpl>& client) {
+ uint32_t value = this->reg_value();
+ auto data =
+ fidl::VectorView<uint8_t>::FromExternal(reinterpret_cast<uint8_t*>(&value), sizeof(value));
+ auto result = client.Write(0, RegisterAddress(address), data);
+ if (!result.ok()) {
+ zxlogf(ERROR, "Failed to send write FIDL request: %s", result.FormatDescription().data());
+ return result.status();
+ }
+ if (result->result.is_err()) {
+ zxlogf(ERROR, "Failed to write: %d", result->result.err());
+ return result->result.err();
+ }
+ return ZX_OK;
+ }
+};
+
+// All of these registers are defined in the TPM PC client platform spec.
+// https://www.trustedcomputinggroup.org/wp-content/uploads/PCClientPlatform-TPM-Profile-for-TPM-2-0-v1-03-20-161114_public-review.pdf
+// The PC client platform spec defines separate registers for SPI/LPC and I2C, however, the two are
+// mostly compatible.
+
+// TPM_STS: 5.5.2.5, "Status Register" and 7.3.5.6, "TPM_STS".
+class StsReg : public TpmReg<StsReg, RegisterAddress::kTpmSts> {
+ public:
+ DEF_ENUM_FIELD(TpmFamily, 27, 26, tpm_family);
+ DEF_BIT(25, reset_establishment);
+ DEF_BIT(24, command_cancel);
+ DEF_FIELD(23, 8, burst_count);
+ DEF_BIT(7, sts_valid);
+ DEF_BIT(6, command_ready);
+ DEF_BIT(5, tpm_go);
+ DEF_BIT(4, data_avail);
+ DEF_BIT(3, expect);
+ DEF_BIT(2, self_test_done);
+ DEF_BIT(1, response_retry);
+};
+
+// TPM_INTF_CAPABILITY: 5.5.2.7, "Interface Capability" and 7.3.5.5, "TPM_INT_CAPABILITY".
+//
+// Note that the I2C version of the interface only defines bits 0, 1, 2, and 7.
+// Reads of other fields will always return zero.
+class IntfCapabilityReg : public TpmReg<IntfCapabilityReg, RegisterAddress::kTpmIntCapability> {
+ public:
+ DEF_FIELD(30, 28, interface_version);
+ DEF_FIELD(10, 9, data_transfer_size_support);
+ DEF_BIT(8, burst_count_static);
+ DEF_BIT(7, command_ready_int_support);
+ DEF_BIT(6, interrupt_edge_falling);
+ DEF_BIT(5, interrupt_edge_rising);
+ DEF_BIT(4, interrupt_level_low);
+ DEF_BIT(3, interrupt_level_high);
+ DEF_BIT(2, locality_change_int_supported);
+ DEF_BIT(1, sts_valid_int_support);
+ DEF_BIT(0, data_avail_int_support);
+};
+
+} // namespace tpm
+
+#endif // SRC_DEVICES_TPM_DRIVERS_TPM_REGISTERS_H_
diff --git a/src/devices/tpm/drivers/tpm/tpm-test.cc b/src/devices/tpm/drivers/tpm/tpm-test.cc
new file mode 100644
index 0000000..2788363
--- /dev/null
+++ b/src/devices/tpm/drivers/tpm/tpm-test.cc
@@ -0,0 +1,252 @@
+// 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/llcpp/fidl.h>
+#include <lib/async-loop/cpp/loop.h>
+#include <lib/sync/condition.h>
+
+#include <condition_variable>
+#include <mutex>
+
+#include <ddktl/device.h>
+#include <zxtest/zxtest.h>
+
+#include "src/devices/testing/mock-ddk/mock-device.h"
+#include "src/devices/tpm/drivers/tpm/commands.h"
+#include "src/devices/tpm/drivers/tpm/registers.h"
+
+// See Table 22,
+// https://www.trustedcomputinggroup.org/wp-content/uploads/PCClientPlatform-TPM-Profile-for-TPM-2-0-v1-03-20-161114_public-review.pdf
+enum TpmState {
+ kIdle,
+ kReady,
+ kReception,
+ kExecution,
+ kCompletion,
+ // Special state to indicate test is finished and thread should stop.
+ kTeardownTest,
+};
+
+using fuchsia_hardware_tpmimpl::wire::RegisterAddress;
+class TpmTest : public zxtest::Test, public fidl::WireServer<fuchsia_hardware_tpmimpl::TpmImpl> {
+ public:
+ TpmTest()
+ : loop_(&kAsyncLoopConfigNeverAttachToThread), fake_root_(MockDevice::FakeRootParent()) {}
+
+ void SetUp() override {
+ status_.set_tpm_family(tpm::kTpmFamily20).set_sts_valid(1);
+ ASSERT_OK(loop_.StartThread("tpm-test-thread"));
+
+ exec_thread_ = std::thread(&TpmTest::ExecThread, this);
+
+ fidl::WireSyncClient<fuchsia_hardware_tpmimpl::TpmImpl> client;
+ auto server = fidl::CreateEndpoints(&client.client_end());
+ ASSERT_TRUE(server.is_ok());
+ fidl::BindServer(loop_.dispatcher(), std::move(server.value()), this);
+
+ ASSERT_TRUE(client.client_end().is_valid());
+ auto device = std::make_unique<tpm::TpmDevice>(fake_root_.get(), std::move(client));
+ ASSERT_OK(device->DdkAdd(ddk::DeviceAddArgs("tpm")));
+ device.release();
+ }
+
+ void TearDown() override {
+ {
+ std::scoped_lock lock(state_mutex_);
+ state_ = kTeardownTest;
+ }
+ state_change_.notify_all();
+ if (exec_thread_.joinable()) {
+ exec_thread_.join();
+ }
+ }
+
+ void Read(ReadRequestView request, ReadCompleter::Sync& completer) override {
+ switch (request->address) {
+ case RegisterAddress::kTpmSts: {
+ ASSERT_EQ(request->count, 4);
+ uint32_t value = status_.reg_value();
+ completer.ReplySuccess(
+ fidl::VectorView<uint8_t>::FromExternal(reinterpret_cast<uint8_t*>(&value), 4));
+ break;
+ }
+ case RegisterAddress::kTpmDataFifo: {
+ ASSERT_LE(request->count, status_.burst_count());
+ ASSERT_GT(request->count, 0);
+ std::scoped_lock lock(state_mutex_);
+ auto amount = std::min(static_cast<size_t>(request->count), fifo_.size());
+ completer.ReplySuccess(fidl::VectorView<uint8_t>::FromExternal(fifo_.data(), amount));
+ fifo_.erase(fifo_.begin(), fifo_.begin() + amount);
+ if (fifo_.empty()) {
+ status_.set_data_avail(0);
+ }
+ break;
+ }
+ default: {
+ ASSERT_TRUE(false, "unexpected register");
+ }
+ }
+ }
+
+ void HandleStsWrite(uint32_t value) __TA_NO_THREAD_SAFETY_ANALYSIS {
+ tpm::StsReg st;
+ st.set_reg_value(value);
+ if (st.command_ready()) {
+ std::scoped_lock lock(state_mutex_);
+ if (state_ == kIdle) {
+ state_ = kReady;
+ fifo_.clear();
+ status_.set_command_ready(1).set_burst_count(64);
+ } else {
+ state_ = kIdle;
+ status_.set_command_ready(0);
+ }
+ state_change_.notify_all();
+ } else if (st.tpm_go()) {
+ // The spec technically defines setting TPM_GO for all states, but receiving it in any state
+ // except reception probably indicates a bug.
+ std::scoped_lock lock(state_mutex_);
+ ASSERT_EQ(state_, kReception);
+ ASSERT_EQ(status_.expect(), 0);
+ state_ = kExecution;
+ state_change_.notify_all();
+ } else {
+ ASSERT_FALSE(true, "Unknown bit set in TPM STS register!");
+ }
+ }
+
+ void Write(WriteRequestView request, WriteCompleter::Sync& completer) override {
+ switch (request->address) {
+ case RegisterAddress::kTpmSts: {
+ ASSERT_EQ(request->data.count(), 4);
+ uint32_t value = *reinterpret_cast<uint32_t*>(request->data.mutable_data());
+ // There should only be 1 bit set on a TPM write.
+ // Leading zeros + trailing zeros should equal 31.
+ ASSERT_EQ(__builtin_clz(value) + __builtin_ctz(value), 31);
+ HandleStsWrite(value);
+ break;
+ }
+ case RegisterAddress::kTpmDataFifo: {
+ std::scoped_lock lock(state_mutex_);
+ if (state_ == kReady) {
+ state_ = kReception;
+ status_.set_expect(1);
+ state_change_.notify_all();
+ }
+ fifo_.insert(fifo_.end(), request->data.begin(), request->data.end());
+ UpdateExpect();
+ break;
+ }
+ default: {
+ ASSERT_FALSE(true, "Unsupported register");
+ break;
+ }
+ }
+ completer.ReplySuccess();
+ }
+
+ void UpdateExpect() __TA_REQUIRES(state_mutex_) {
+ TpmCmdHeader* hdr = reinterpret_cast<TpmCmdHeader*>(fifo_.data());
+ if (be32toh(hdr->command_size) == fifo_.size()) {
+ status_.set_expect(0);
+ }
+ }
+
+ void ExecThread() {
+ auto cur_state = TpmState::kIdle;
+ std::scoped_lock lock(state_mutex_);
+ while (state_ != kTeardownTest) {
+ state_change_.wait(state_mutex_, [&cur_state, this]() __TA_REQUIRES(state_mutex_) {
+ return state_ != cur_state;
+ });
+ cur_state = state_;
+ switch (state_) {
+ case kExecution: {
+ std::vector<uint8_t> out;
+ TpmCmdHeader* hdr = reinterpret_cast<TpmCmdHeader*>(fifo_.data());
+ handle_command_(hdr, out);
+ fifo_ = std::move(out);
+ state_ = kCompletion;
+ status_.set_data_avail(1);
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ }
+ }
+
+ protected:
+ async::Loop loop_;
+ tpm::StsReg status_;
+ TpmState state_ __TA_GUARDED(state_mutex_) = TpmState::kIdle;
+ std::vector<uint8_t> fifo_ __TA_GUARDED(state_mutex_);
+ std::mutex state_mutex_;
+ std::condition_variable_any state_change_;
+ std::thread exec_thread_;
+
+ std::shared_ptr<MockDevice> fake_root_;
+ std::function<void(TpmCmdHeader*, std::vector<uint8_t>&)> handle_command_;
+};
+
+TEST_F(TpmTest, TestDdkInit) {
+ auto dev = fake_root_->GetLatestChild();
+ dev->InitOp();
+ ASSERT_OK(dev->WaitUntilInitReplyCalled());
+}
+
+TEST_F(TpmTest, TestDdkSuspendToRam) {
+ auto dev = fake_root_->GetLatestChild();
+ dev->InitOp();
+ ASSERT_OK(dev->WaitUntilInitReplyCalled());
+
+ uint16_t shutdown_type = -1;
+ handle_command_ = [&shutdown_type](TpmCmdHeader* c, std::vector<uint8_t>& out) {
+ ASSERT_EQ(be32toh(c->command_code), TPM_CC_SHUTDOWN);
+ ASSERT_EQ(be32toh(c->command_size), sizeof(TpmShutdownCmd));
+ TpmShutdownCmd* shutdown = reinterpret_cast<TpmShutdownCmd*>(c);
+ shutdown_type = be16toh(shutdown->shutdown_type);
+
+ TpmShutdownResponse r{
+ .tag = htobe16(TPM_ST_NO_SESSIONS),
+ .response_size = htobe32(sizeof(TpmShutdownResponse)),
+ .response_code = 0,
+ };
+ out.resize(sizeof(r), 0);
+ memcpy(out.data(), &r, sizeof(r));
+ };
+
+ dev->SuspendNewOp(DEV_POWER_STATE_D0, false, DEVICE_SUSPEND_REASON_SUSPEND_RAM);
+ dev->WaitUntilSuspendReplyCalled();
+ ASSERT_EQ(shutdown_type, TPM_SU_STATE);
+}
+
+TEST_F(TpmTest, TestDdkSuspendShutdown) {
+ auto dev = fake_root_->GetLatestChild();
+ dev->InitOp();
+ ASSERT_OK(dev->WaitUntilInitReplyCalled());
+
+ uint16_t shutdown_type = -1;
+ handle_command_ = [&shutdown_type](TpmCmdHeader* c, std::vector<uint8_t>& out) {
+ ASSERT_EQ(be32toh(c->command_code), TPM_CC_SHUTDOWN);
+ ASSERT_EQ(be32toh(c->command_size), sizeof(TpmShutdownCmd));
+ TpmShutdownCmd* shutdown = reinterpret_cast<TpmShutdownCmd*>(c);
+ shutdown_type = be16toh(shutdown->shutdown_type);
+
+ TpmShutdownResponse r{
+ .tag = htobe16(TPM_ST_NO_SESSIONS),
+ .response_size = htobe32(sizeof(TpmShutdownResponse)),
+ .response_code = 0,
+ };
+ out.resize(sizeof(r), 0);
+ memcpy(out.data(), &r, sizeof(r));
+ };
+
+ dev->SuspendNewOp(DEV_POWER_STATE_D0, false, DEVICE_SUSPEND_REASON_POWEROFF);
+ dev->WaitUntilSuspendReplyCalled();
+ ASSERT_EQ(shutdown_type, TPM_SU_CLEAR);
+}
diff --git a/src/devices/tpm/drivers/tpm/tpm.bind b/src/devices/tpm/drivers/tpm/tpm.bind
new file mode 100644
index 0000000..1dd00de
--- /dev/null
+++ b/src/devices/tpm/drivers/tpm/tpm.bind
@@ -0,0 +1,7 @@
+// 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.
+
+using fuchsia.tpm;
+
+fuchsia.BIND_PROTOCOL == fuchsia.tpm.BIND_PROTOCOL.IMPL;
diff --git a/src/devices/tpm/drivers/tpm/tpm.cc b/src/devices/tpm/drivers/tpm/tpm.cc
new file mode 100644
index 0000000..13ce7c5
--- /dev/null
+++ b/src/devices/tpm/drivers/tpm/tpm.cc
@@ -0,0 +1,232 @@
+// 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");
diff --git a/src/devices/tpm/drivers/tpm/tpm.h b/src/devices/tpm/drivers/tpm/tpm.h
new file mode 100644
index 0000000..d27d74c
--- /dev/null
+++ b/src/devices/tpm/drivers/tpm/tpm.h
@@ -0,0 +1,40 @@
+// 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.
+
+#ifndef SRC_DEVICES_TPM_DRIVERS_TPM_TPM_H_
+#define SRC_DEVICES_TPM_DRIVERS_TPM_TPM_H_
+
+#include <fuchsia/hardware/tpmimpl/llcpp/fidl.h>
+#include <lib/ddk/device.h>
+
+#include <ddktl/device.h>
+
+#include "src/devices/tpm/drivers/tpm/commands.h"
+
+namespace tpm {
+
+class TpmDevice;
+using DeviceType = ddk::Device<TpmDevice, ddk::Initializable, ddk::Suspendable>;
+
+class TpmDevice : public DeviceType {
+ public:
+ TpmDevice(zx_device_t* parent, fidl::WireSyncClient<fuchsia_hardware_tpmimpl::TpmImpl> client)
+ : DeviceType(parent), tpm_(std::move(client)) {}
+
+ static zx_status_t Create(void* ctx, zx_device_t* parent);
+
+ // DDK mixins
+ void DdkInit(ddk::InitTxn txn);
+ void DdkRelease() { delete this; }
+ void DdkSuspend(ddk::SuspendTxn txn);
+
+ private:
+ zx_status_t DoCommand(TpmCmdHeader* cmd);
+
+ fidl::WireSyncClient<fuchsia_hardware_tpmimpl::TpmImpl> tpm_;
+};
+
+} // namespace tpm
+
+#endif // SRC_DEVICES_TPM_DRIVERS_TPM_TPM_H_