// 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 #include #include #include #include #include #include #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 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 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 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(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(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\n", 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