| // |
| // 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 |