trunks: improve FIFO read processing

When reading the FIFO contents in FTDI mode, the host polls the status
register to find out if there is more data available and keeps reading
one byte of FIFO data at a time. This makes FIFO read transactions
very slow.

The typical TPM response always includes a 6 byte header and then some
more data. The actual size of the response is stored in the network
byte order in the last 4 bytes of the header.

To speed up FIFO reads, we now always start with reading the first six
bytes. Once they are read, the total response size can be determined,
and the rest of the payload less one byte can be read. At this point
the 'data available' bit in the TPM_STS register must still be
asserted. Then we read the last byte from the FIFO and verify that the
'data available' is deasserted after that. This verifies that the FIFO
contents and the TPM__STS registers are in sync.

BUG=chrome-os-partner:43025
TEST=verified that trunksd starts up successfully when initializing
     CR50 over the FTDI interface.

Change-Id: I5d9ccf0df94eb7935667feff9e9a00caf447a5ff
Signed-off-by: Vadim Bendebury <vbendeb@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/293864
Reviewed-by: Utkarsh Sanghi <usanghi@chromium.org>
diff --git a/trunks_ftdi_spi.cc b/trunks_ftdi_spi.cc
index ea99f9a..a025054 100644
--- a/trunks_ftdi_spi.cc
+++ b/trunks_ftdi_spi.cc
@@ -235,20 +235,54 @@
   if (!WaitForStatus(expected_status_bits, expected_status_bits))
       return rv;
 
-  // The response is ready, read it out byte by byte for now. Will be
-  // optimized to read the length first an then the entire remaining data in
-  // one shot.
-  do {
-    uint8_t c;
+  // 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];
+  uint32_t payload_size;
 
-    FtdiReadReg(TPM_DATA_FIFO_REG, sizeof(c), &c);
-    rv.push_back(c);
-    ReadTpmSts(&status);
-  } while ((status & expected_status_bits) == expected_status_bits);
+  // Let's read the header first.
+  FtdiReadReg(TPM_DATA_FIFO_REG, sizeof(data_header), data_header);
+  rv = std::string(data_header, sizeof(data_header));
+
+  // Figure out the total payload size.
+  memcpy(&payload_size, data_header + 2, sizeof(payload_size));
+  payload_size = be32toh(payload_size);
+  LOG(INFO) << "Total payload size " << payload_size;
+
+  // Calculate how much to read in the next shot and read: all but the last
+  // byte.
+  payload_size = payload_size - sizeof(data_header) - 1;
+  char *payload = new char[payload_size];
+  FtdiReadReg(TPM_DATA_FIFO_REG, payload_size, payload);
+
+  // 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 std::string("");
+  }
+  rv += std::string(payload, payload_size);
+
+  // Now, read the last byte of the payload.
+  uint8_t last_char;
+  FtdiReadReg(TPM_DATA_FIFO_REG, sizeof(last_char), &last_char);
+
+  // 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 std::string("");
+  }
+  rv.push_back(last_char);
 
   /* Move the TPM back to idle state. */
   WriteTpmSts(commandReady);
 
+  delete[] payload;
   return rv;
 }