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