blob: 525c1c2ab84d4f38d73e9777efdbddb66abc76b0 [file] [log] [blame]
// Copyright 2016 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/zx/time.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <zircon/syscalls.h>
#include <ddk/debug.h>
#include "tpm.h"
// TPM_ACCESS bitmasks
#define TPM_ACCESS_REG_VALID 0x80
#define TPM_ACCESS_ACTIVE_LOCALITY 0x20
#define TPM_ACCESS_BEEN_SEIZED 0x10
#define TPM_ACCESS_SEIZE 0x08
#define TPM_ACCESS_PENDING_REQ 0x04
#define TPM_ACCESS_REQUEST_USE 0x02
#define TPM_ACCESS_ESTABLISHMENT 0x01
// TPM_INTF_CAP bitmasks
#define TPM_INTF_CAP_IFACE_VER_MASK 0x70000000
#define TPM_INTF_CAP_IFACE_VER_1_3 0x20000000
#define TPM_INTF_CAP_IFACE_VER_1_2 0x00000000
// TPM_STS bitmasks
#define TPM_STS_FAMILY 0x0c000000
#define TPM_STS_RESET_ESTABLISHMENT 0x02000000
#define TPM_STS_CMD_CANCEL 0x01000000
#define TPM_STS_BURST_COUNT 0x00ffff00
#define TPM_STS_VALID 0x00000080
#define TPM_STS_CMD_RDY 0x00000040
#define TPM_STS_TPM_GO 0x00000020
#define TPM_STS_DATA_AVAIL 0x00000010
#define TPM_STS_EXPECT 0x00000008
#define TPM_STS_SELF_TEST_DONE 0x00000004
#define TPM_STS_RESPONSE_RETRY 0x00000002
#define TPM_STS_EXTRACT_BURST_COUNT(sts) (((sts)&TPM_STS_BURST_COUNT) >> 8)
#define TPM_STS_EXTRACT_FAMILY(sts) (((sts)&TPM_STS_FAMILY) >> 26)
// TPM_INT_ENABLE bitmasks
#define TPM_INT_ENABLE_GLOBAL_ENABLE 0x80000000
#define TPM_INT_ENABLE_HIGH_LEVEL (0 << 3)
#define TPM_INT_ENABLE_LOW_LEVEL (1 << 3)
#define TPM_INT_ENABLE_RISING_EDGE (2 << 3)
#define TPM_INT_ENABLE_FALLING_EDGE (3 << 3)
// TPM_INTERFACE_ID bitmasks
#define TPM_INTERFACE_ID_TYPE_MASK 0xf
#define TPM_INTERFACE_ID_TYPE_FIFO_2_0 0x0
#define TPM_INTERFACE_ID_TYPE_CRB 0x1
#define TPM_INTERFACE_ID_TYPE_FIFO_1_3 0xf
namespace {
constexpr zx::duration TIMEOUT_A = zx::msec(750);
constexpr zx::duration kWaitForProgressDelay = zx::msec(2);
} // namespace
namespace tpm {
constexpr size_t kNumRegisterTries = 3;
static zx_status_t GetAccessField(HardwareInterface* iface, Locality loc, uint8_t* val) {
for (size_t attempt = 0; attempt < kNumRegisterTries; ++attempt) {
if (attempt) {
zx::nanosleep(zx::deadline_after(TIMEOUT_A));
}
uint8_t access;
zx_status_t status = iface->ReadAccess(loc, &access);
if (status != ZX_OK) {
return status;
}
if (access & TPM_ACCESS_REG_VALID) {
*val = access;
return ZX_OK;
}
}
return ZX_ERR_TIMED_OUT;
}
zx_status_t Device::RequestLocalityLocked(Locality loc) {
uint8_t val;
zx_status_t status = GetAccessField(iface_.get(), loc, &val);
if (status != ZX_OK) {
return status;
}
if (!(val & TPM_ACCESS_REG_VALID)) {
return ZX_ERR_BAD_STATE;
}
if (val & TPM_ACCESS_REQUEST_USE) {
return ZX_ERR_UNAVAILABLE;
}
if (val & TPM_ACCESS_ACTIVE_LOCALITY) {
// We're already the active locality
return ZX_ERR_BAD_STATE;
}
return iface_->WriteAccess(loc, TPM_ACCESS_REQUEST_USE);
}
zx_status_t Device::ReleaseLocalityLocked(Locality loc) {
uint8_t val;
zx_status_t status = GetAccessField(iface_.get(), loc, &val);
if (status != ZX_OK) {
return status;
}
if (!(val & TPM_ACCESS_REG_VALID)) {
return ZX_ERR_BAD_STATE;
}
if (val & TPM_ACCESS_REQUEST_USE) {
return ZX_ERR_BAD_STATE;
}
if (!(val & TPM_ACCESS_ACTIVE_LOCALITY)) {
// We're not the active locality
return ZX_ERR_BAD_STATE;
}
// Writing this bit triggers the release.
return iface_->WriteAccess(loc, TPM_ACCESS_ACTIVE_LOCALITY);
}
zx_status_t Device::WaitForLocalityLocked(Locality loc) {
uint8_t val;
zx_status_t status = GetAccessField(iface_.get(), loc, &val);
if (status != ZX_OK) {
return status;
}
if (val & TPM_ACCESS_ACTIVE_LOCALITY) {
return ZX_OK;
}
if (!(val & TPM_ACCESS_REQUEST_USE)) {
return ZX_ERR_BAD_STATE;
}
// We assume we're the only one using the TPM, so we need to wait at most
// TIMEOUT_A
zx::nanosleep(zx::deadline_after(TIMEOUT_A));
status = GetAccessField(iface_.get(), loc, &val);
if (status != ZX_OK) {
return status;
}
if (val & TPM_ACCESS_ACTIVE_LOCALITY) {
return ZX_OK;
}
if (val & TPM_ACCESS_REQUEST_USE) {
return ZX_ERR_TIMED_OUT;
}
return ZX_ERR_BAD_STATE;
}
static zx_status_t GetStatusField(HardwareInterface* iface, Locality loc, uint32_t* val) {
for (size_t attempt = 0; attempt < kNumRegisterTries; ++attempt) {
if (attempt) {
zx::nanosleep(zx::deadline_after(TIMEOUT_A));
}
uint32_t sts;
zx_status_t status = iface->ReadStatus(loc, &sts);
if (status != ZX_OK) {
return status;
}
if (sts & TPM_STS_VALID) {
*val = sts;
return ZX_OK;
}
}
return ZX_ERR_TIMED_OUT;
}
static zx_status_t GetBurstCount(HardwareInterface* iface, Locality loc, uint16_t* val) {
for (size_t attempt = 0; attempt < kNumRegisterTries; ++attempt) {
if (attempt) {
zx::nanosleep(zx::deadline_after(TIMEOUT_A));
}
uint32_t sts;
zx_status_t status = iface->ReadStatus(loc, &sts);
if (status != ZX_OK) {
return status;
}
uint16_t burst = TPM_STS_EXTRACT_BURST_COUNT(sts);
if (burst > 0) {
*val = burst;
return ZX_OK;
}
}
return ZX_ERR_TIMED_OUT;
}
// Returns the true/false value of the STS.EXPECT bit, or < 0 on error
static zx_status_t GetStatusExpect(HardwareInterface* iface, Locality loc, bool* expect) {
uint32_t status_field;
zx_status_t status = GetStatusField(iface, loc, &status_field);
if (status != ZX_OK) {
return status;
}
*expect = !!(status_field & TPM_STS_EXPECT);
return ZX_OK;
}
// Returns the true/false value of the STS.DATA_AVAIL bit, or < 0 on error
static zx_status_t GetStatusDataAvail(HardwareInterface* iface, Locality loc, bool* data_avail) {
uint32_t status_field;
zx_status_t status = GetStatusField(iface, loc, &status_field);
if (status != ZX_OK) {
return status;
}
*data_avail = !!(status_field & TPM_STS_DATA_AVAIL);
return ZX_OK;
}
static zx_status_t WaitForDataAvail(HardwareInterface* iface, Locality loc) {
// TODO(teisenbe): Add a timeout to this?
while (1) {
bool data_avail = false;
zx_status_t status = GetStatusDataAvail(iface, loc, &data_avail);
if (status != ZX_OK) {
return status;
}
if (data_avail) {
return ZX_OK;
}
zx::nanosleep(zx::deadline_after(kWaitForProgressDelay));
}
}
static zx_status_t AbortCommand(HardwareInterface* iface, Locality loc) {
return iface->WriteStatus(loc, TPM_STS_CMD_RDY);
}
// Returns the true/false value of the ACCESS.ACTIVE bit, or < 0 on error
static zx_status_t GetActiveLocality(HardwareInterface* iface, Locality loc, bool* active) {
uint8_t val;
zx_status_t status = GetAccessField(iface, loc, &val);
if (status != ZX_OK) {
return status;
}
*active = !!(val & TPM_ACCESS_ACTIVE_LOCALITY);
return ZX_OK;
}
static zx_status_t CheckExpectedState(zx_status_t status, bool actual, bool expected) {
if (status < 0) {
return status;
}
if (actual != expected) {
return ZX_ERR_BAD_STATE;
}
return ZX_OK;
}
zx_status_t Device::SendCmdLocked(Locality loc, const uint8_t* cmd, size_t len) {
if (len <= 1) {
return ZX_ERR_INVALID_ARGS;
}
bool active = false;
zx_status_t status = GetActiveLocality(iface_.get(), loc, &active);
status = CheckExpectedState(status, active, true);
if (status < 0) {
return status;
}
// This procedure is described in section 5.5.2.2.1 of the TCG PC Client
// Platform TPM profile spec (family 2.0, which also describes 1.2)
status = iface_->WriteStatus(loc, TPM_STS_CMD_RDY);
if (status != ZX_OK) {
return status;
}
size_t bytes_sent = 0;
// Write the command to the FIFO, while respecting flow control
while (bytes_sent < len) {
uint16_t burst_count;
status = GetBurstCount(iface_.get(), loc, &burst_count);
if (status != ZX_OK) {
AbortCommand(iface_.get(), loc);
return status;
}
if (burst_count == 0) {
AbortCommand(iface_.get(), loc);
return ZX_ERR_IO;
}
// Write up to len - 1 bytes, since we should watch the EXPECT bit
// transition on the final byte
uint16_t to_write = burst_count;
if (to_write > len - 1) {
to_write = static_cast<uint16_t>(len - 1);
}
status = iface_->WriteDataFifo(loc, &cmd[bytes_sent], to_write);
if (status != ZX_OK) {
AbortCommand(iface_.get(), loc);
return status;
}
bytes_sent += to_write;
burst_count = static_cast<uint16_t>(burst_count - to_write);
if (burst_count > 0 && bytes_sent == len - 1) {
bool expect = false;
// Watch the EXPECT bit as we write the last byte, it should
// transition.
status = GetStatusExpect(iface_.get(), loc, &expect);
status = CheckExpectedState(status, expect, true);
if (status < 0) {
AbortCommand(iface_.get(), loc);
return status;
}
status = iface_->WriteDataFifo(loc, &cmd[bytes_sent], 1);
if (status != ZX_OK) {
AbortCommand(iface_.get(), loc);
return status;
}
++bytes_sent;
status = GetStatusExpect(iface_.get(), loc, &expect);
status = CheckExpectedState(status, expect, false);
if (status < 0) {
AbortCommand(iface_.get(), loc);
return status;
}
}
}
// Run the command
status = iface_->WriteStatus(loc, TPM_STS_TPM_GO);
if (status != ZX_OK) {
AbortCommand(iface_.get(), loc);
return status;
}
return ZX_OK;
}
zx_status_t Device::RecvRespLocked(Locality loc, uint8_t* resp, size_t max_len, size_t* actual) {
bool active = false;
zx_status_t status = GetActiveLocality(iface_.get(), loc, &active);
status = CheckExpectedState(status, active, true);
if (status != ZX_OK) {
AbortCommand(iface_.get(), loc);
return status;
}
// This procedure is described in section 5.5.2.2.2 of the TCG PC Client
// Platform TPM profile spec (family 2.0, which also describes 1.2)
// Wait for data to be available
status = WaitForDataAvail(iface_.get(), loc);
if (status != ZX_OK) {
AbortCommand(iface_.get(), loc);
return status;
}
bool more_data = true;
size_t bytes_recvd = 0;
while (more_data) {
zxlogf(TRACE, "Reading response, %zu bytes read", bytes_recvd);
uint16_t burst_count;
status = GetBurstCount(iface_.get(), loc, &burst_count);
if (status != ZX_OK) {
AbortCommand(iface_.get(), loc);
return status;
}
size_t to_read = burst_count;
size_t remaining = max_len - bytes_recvd;
if (to_read > remaining) {
to_read = remaining;
}
status = iface_->ReadDataFifo(loc, &resp[bytes_recvd], to_read);
if (status != ZX_OK) {
AbortCommand(iface_.get(), loc);
return status;
}
bytes_recvd += to_read;
// See if there is any more data to read
bool data_avail = false;
status = GetStatusDataAvail(iface_.get(), loc, &data_avail);
if (status < 0) {
AbortCommand(iface_.get(), loc);
return status;
} else if (!data_avail) {
more_data = false;
break;
}
// If we have filled the buffer and there is more data, exit
// the loop so we will send a CMD_RDY (which doubles as an abort).
if (bytes_recvd >= max_len) {
more_data = false;
}
}
// Either abort a response if we filled our buffer, or acknowledge that
// we've finished receiving the data. (Transitions 30 and 37 in Table 22
// (State Transition Table)).
AbortCommand(iface_.get(), loc);
*actual = bytes_recvd;
return ZX_OK;
}
} // namespace tpm