// // Copyright (C) 2015 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #include <algorithm> #include <ctime> #include <string> #include <unistd.h> #include <base/logging.h> #include "trunks/tpm_generated.h" #include "trunks/trunks_ftdi_spi.h" // Assorted TPM2 registers for interface type FIFO. #define TPM_ACCESS_REG 0 #define TPM_STS_REG 0x18 #define TPM_DATA_FIFO_REG 0x24 #define TPM_DID_VID_REG 0xf00 #define TPM_RID_REG 0xf04 namespace trunks { // Locality management bits (in TPM_ACCESS_REG) enum TpmAccessBits { tpmRegValidSts = (1 << 7), activeLocality = (1 << 5), requestUse = (1 << 1), tpmEstablishment = (1 << 0), }; enum TpmStsBits { tpmFamilyShift = 26, tpmFamilyMask = ((1 << 2) - 1), // 2 bits wide tpmFamilyTPM2 = 1, resetEstablishmentBit = (1 << 25), commandCancel = (1 << 24), burstCountShift = 8, burstCountMask = ((1 << 16) -1), // 16 bits wide stsValid = (1 << 7), commandReady = (1 << 6), tpmGo = (1 << 5), dataAvail = (1 << 4), Expect = (1 << 3), selfTestDone = (1 << 2), responseRetry = (1 << 1), }; // SPI frame header for TPM transactions is 4 bytes in size, it is described // in section "6.4.6 Spi Bit Protocol" of the TCG issued "TPM Profile (PTP) // Specification Revision 00.43. struct SpiFrameHeader { unsigned char body[4]; }; TrunksFtdiSpi::~TrunksFtdiSpi() { if (mpsse_) Close(mpsse_); mpsse_ = NULL; } bool TrunksFtdiSpi::ReadTpmSts(uint32_t *status) { return FtdiReadReg(TPM_STS_REG, sizeof(*status), status); } bool TrunksFtdiSpi::WriteTpmSts(uint32_t status) { return FtdiWriteReg(TPM_STS_REG, sizeof(status), &status); } void TrunksFtdiSpi::StartTransaction(bool read_write, size_t bytes, unsigned addr) { unsigned char *response; SpiFrameHeader header; usleep(10000); // give it 10 ms. TODO(vbendeb): remove this once // cr50 SPS TPM driver performance is fixed. // The first byte of the frame header encodes the transaction type (read or // write) and size (set to lenth - 1). header.body[0] = (read_write ? 0x80 : 0) | 0x40 | (bytes - 1); // The rest of the frame header is the internal address in the TPM for (int i = 0; i < 3; i++) header.body[i + 1] = (addr >> (8 * (2 - i))) & 0xff; Start(mpsse_); response = Transfer(mpsse_, header.body, sizeof(header.body)); // The TCG TPM over SPI specification itroduces the notion of SPI flow // control (Section "6.4.5 Flow Control" of the TCG issued "TPM Profile // (PTP) Specification Revision 00.43). // The slave (TPM device) expects each transaction to start with a 4 byte // header trasmitted by master. If the slave needs to stall the transaction, // it sets the MOSI bit to 0 during the last clock of the 4 byte header. In // this case the master is supposed to start polling the line, byte at time, // until the last bit in the received byte (transferred during the last // clock of the byte) is set to 1. while (!(response[3] & 1)) { unsigned char *poll_state; poll_state = Read(mpsse_, 1); response[3] = *poll_state; free(poll_state); } free(response); } bool TrunksFtdiSpi::FtdiWriteReg(unsigned reg_number, size_t bytes, const void *buffer) { if (!mpsse_) return false; StartTransaction(false, bytes, reg_number + locality_ * 0x10000); Write(mpsse_, buffer, bytes); Stop(mpsse_); return true; } bool TrunksFtdiSpi::FtdiReadReg(unsigned reg_number, size_t bytes, void *buffer) { unsigned char *value; if (!mpsse_) return false; StartTransaction(true, bytes, reg_number + locality_ * 0x10000); value = Read(mpsse_, bytes); if (buffer) memcpy(buffer, value, bytes); free(value); Stop(mpsse_); return true; } size_t TrunksFtdiSpi::GetBurstCount(void) { uint32_t status; ReadTpmSts(&status); return (size_t)((status >> burstCountShift) & burstCountMask); } bool TrunksFtdiSpi::Init() { uint32_t did_vid, status; uint8_t cmd; if (mpsse_) return true; mpsse_ = MPSSE(SPI0, ONE_MHZ, MSB); if (!mpsse_) return false; // Reset the TPM using GPIOL0, issue a 100 ms long pulse. PinLow(mpsse_, GPIOL0); usleep(100000); PinHigh(mpsse_, GPIOL0); FtdiReadReg(TPM_DID_VID_REG, sizeof(did_vid), &did_vid); uint16_t vid = did_vid & 0xffff; if ((vid != 0x15d1) && (vid != 0x1ae0)) { LOG(ERROR) << "unknown did_vid: 0x" << std::hex << did_vid; return false; } // Try claiming locality zero. FtdiReadReg(TPM_ACCESS_REG, sizeof(cmd), &cmd); // tpmEstablishment can be either set or not. if ((cmd & ~tpmEstablishment) != tpmRegValidSts) { LOG(ERROR) << "invalid reset status: 0x" << std::hex << (unsigned)cmd; return false; } cmd = requestUse; FtdiWriteReg(TPM_ACCESS_REG, sizeof(cmd), &cmd); FtdiReadReg(TPM_ACCESS_REG, sizeof(cmd), &cmd); if ((cmd & ~tpmEstablishment) != (tpmRegValidSts | activeLocality)) { LOG(ERROR) << "failed to claim locality, status: 0x" << std::hex << (unsigned)cmd; return false; } ReadTpmSts(&status); if (((status >> tpmFamilyShift) & tpmFamilyMask) != tpmFamilyTPM2) { LOG(ERROR) << "unexpected TPM family value, status: 0x" << std::hex << status; return false; } FtdiReadReg(TPM_RID_REG, sizeof(cmd), &cmd); printf("Connected to device vid:did:rid of %4.4x:%4.4x:%2.2x\n", did_vid & 0xffff, did_vid >> 16, cmd); return true; } void TrunksFtdiSpi::SendCommand(const std::string& command, const ResponseCallback& callback) { printf("%s invoked\n", __func__); } bool TrunksFtdiSpi::WaitForStatus(uint32_t statusMask, uint32_t statusExpected, int timeout_ms) { uint32_t status; time_t target_time; target_time = time(NULL) + timeout_ms / 1000; do { usleep(10000); // 10 ms polling period. if (time(NULL) >= target_time) { LOG(ERROR) << "failed to get expected status " << std::hex << statusExpected; return false; } ReadTpmSts(&status); } while ((status & statusMask) != statusExpected); return true; } std::string TrunksFtdiSpi::SendCommandAndWait(const std::string& command) { uint32_t status; uint32_t expected_status_bits; size_t transaction_size, handled_so_far(0); std::string rv(""); if (!mpsse_) { LOG(ERROR) << "attempt to use an uninitialized FTDI TPM!"; return rv; } WriteTpmSts(commandReady); // No need to wait for the sts.Expect bit to be set, at least with the // 15d1:001b device, let's just write the command into FIFO, not exceeding // the minimum of the two values - burst_count and 64 (which is the protocol // limitation) do { transaction_size = std::min(std::min(command.size() - handled_so_far, GetBurstCount()), (size_t)64); if (transaction_size) { LOG(INFO) << "will transfer " << transaction_size << " bytes"; FtdiWriteReg(TPM_DATA_FIFO_REG, transaction_size, command.c_str() + handled_so_far); handled_so_far += transaction_size; } } while (handled_so_far != command.size()); // And tell the device it can start processing it. WriteTpmSts(tpmGo); expected_status_bits = stsValid | dataAvail; if (!WaitForStatus(expected_status_bits, expected_status_bits)) return rv; // The response is ready, let's read it. // First we read the FIFO payload header, to see how much data to expect. // The header size is fixed to six bytes, the total payload size is stored // in network order in the last four bytes of the header. char data_header[6]; // Let's read the header first. FtdiReadReg(TPM_DATA_FIFO_REG, sizeof(data_header), data_header); // Figure out the total payload size. uint32_t payload_size; memcpy(&payload_size, data_header + 2, sizeof(payload_size)); payload_size = be32toh(payload_size); // A FIFO message with the minimum required header and contents can not be // less than 10 bytes long. It also should never be more than 4096 bytes // long. if ((payload_size < 10) || (payload_size > MAX_RESPONSE_SIZE)) { // Something must be wrong... LOG(ERROR) << "Bad total payload size value: " << payload_size; return rv; } LOG(INFO) << "Total payload size " << payload_size; // Let's read all but the last byte in the FIFO to make sure the status // register is showing correct flow control bits: 'more data' until the last // byte and then 'no more data' once the last byte is read. handled_so_far = 0; payload_size = payload_size - sizeof(data_header) - 1; // Allow room for the last byte too. uint8_t *payload = new uint8_t[payload_size + 1]; do { transaction_size = std::min(std::min(payload_size - handled_so_far, GetBurstCount()), (size_t)64); if (transaction_size) { FtdiReadReg(TPM_DATA_FIFO_REG, transaction_size, payload + handled_so_far); handled_so_far += transaction_size; } } while (handled_so_far != payload_size); // Verify that there is still data to come. ReadTpmSts(&status); if ((status & expected_status_bits) != expected_status_bits) { LOG(ERROR) << "unexpected status 0x" << std::hex << status; delete[] payload; return rv; } // Now, read the last byte of the payload. FtdiReadReg(TPM_DATA_FIFO_REG, sizeof(uint8_t), payload + payload_size); // Verify that 'data available' is not asseretd any more. ReadTpmSts(&status); if ((status & expected_status_bits) != stsValid) { LOG(ERROR) << "unexpected status 0x" << std::hex << status; delete[] payload; return rv; } rv = std::string(data_header, sizeof(data_header)) + std::string(reinterpret_cast<char *>(payload), payload_size + 1); /* Move the TPM back to idle state. */ WriteTpmSts(commandReady); delete[] payload; return rv; } } // namespace trunks