blob: fdfa21dee21185a90326de704ea22742ecd06117 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#include "sw/host/spiflash/ftdi_spi_interface.h"
#include <assert.h>
#include <chrono>
#include <cstring>
#include <fcntl.h>
#include <iostream>
#include <string>
#include <termios.h>
#include <unistd.h>
#include <vector>
#include "cryptoc/sha256.h"
// Include MPSSE SPI library
extern "C" {
#include "sw/host/vendor/mpsse/mpsse.h"
}
namespace opentitan {
namespace spiflash {
namespace {
/** FTDI MPSSE GPIO Mappings */
enum FtdiGpioMapping {
/** SRST_N reset. */
kGpioJtagSrstN = GPIOL1,
/** JTAG SPI_N select signal. */
kGpioJtagSpiN = GPIOL2,
/** Bootstrap pin. */
kBootstrapH = GPIOL3,
};
/**
* Resets the target to go back to boot ROM. Assumes boot ROM will enter
* bootstrap mode.
*/
void ResetTarget(struct mpsse_context *ctx) {
// Set bootstrap pin high
PinHigh(ctx, kBootstrapH);
// Enable JTAG mode by setting GPIOL2 high and toggle reset (GPIOL1)
PinHigh(ctx, kGpioJtagSpiN);
PinLow(ctx, kGpioJtagSrstN);
usleep(100000);
PinHigh(ctx, kGpioJtagSrstN);
// Switch from JTAG to SPI mode. The delay is needed to make sure we don't
// drop any frames.
usleep(100000);
PinLow(ctx, kGpioJtagSpiN);
}
} // namespace
/**
* Wrapper struct used to hide mpsse_context since incomplete C struct
* declarations don't play in forward declarations.
*/
struct MpsseHandle {
struct mpsse_context *ctx;
explicit MpsseHandle(struct mpsse_context *new_ctx) : ctx(new_ctx) {}
~MpsseHandle() {
if (ctx != nullptr) {
Close(ctx);
}
}
};
FtdiSpiInterface::FtdiSpiInterface(Options options)
: options_(options), spi_(nullptr) {}
FtdiSpiInterface::~FtdiSpiInterface() {
if (spi_ != nullptr) {
// TODO: Add interface to toggle bootstrap pin.
PinLow(spi_->ctx, kBootstrapH);
}
}
bool FtdiSpiInterface::Init() {
if (!options_.device_serial_number.empty() &&
(options_.device_vendor_id == 0 || options_.device_product_id == 0)) {
std::cerr << "FTDI device serial number requires the vendor_id and product "
"id to be set."
<< std::endl;
return false;
}
struct mpsse_context *ctx = nullptr;
if (options_.device_vendor_id == 0 || options_.device_product_id == 0) {
ctx = MPSSE(/*mode=*/SPI0, options_.spi_frequency, /*endianess=*/MSB);
} else {
const char *serial_number = options_.device_serial_number.empty()
? nullptr
: options_.device_serial_number.c_str();
ctx = Open(options_.device_vendor_id, options_.device_product_id,
/*mode=*/SPI0, options_.spi_frequency, /*endianess=*/MSB,
/*interface=*/IFACE_A, /*description=*/nullptr, serial_number);
}
if (ctx == nullptr) {
std::cerr << "Unable to open FTDI SPI interface." << std::endl;
return false;
}
spi_ = std::make_unique<MpsseHandle>(ctx);
ResetTarget(ctx);
return true;
}
bool FtdiSpiInterface::TransmitFrame(const uint8_t *tx, size_t size) {
assert(spi_ != nullptr);
// The mpsse library is more permissive than the SpiInteface. Copying tx
// to local buffer to handle issue internally.
std::vector<uint8_t> tx_local(tx, tx + size);
if (Start(spi_->ctx)) {
std::cerr << "Unable to start spi transaction." << std::endl;
return false;
}
uint8_t *tmp_rx = ::Transfer(spi_->ctx, tx_local.data(), size);
// We're not using the result of this read, so free it right away.
if (tmp_rx == nullptr) {
free(tmp_rx);
}
if (Stop(spi_->ctx)) {
std::cerr << "Unable to terminate spi transaction." << std::endl;
return false;
}
return true;
}
bool FtdiSpiInterface::CheckHash(const uint8_t *tx, size_t size) {
uint8_t hash[SHA256_DIGEST_SIZE];
SHA256_hash(tx, size, hash);
uint8_t *rx;
int hash_index = 0;
bool hash_correct = false;
if (Start(spi_->ctx)) {
std::cerr << "Unable to start spi transaction." << std::endl;
return false;
}
auto begin = std::chrono::steady_clock::now();
auto now = begin;
while (!hash_correct &&
std::chrono::duration_cast<std::chrono::microseconds>(now - begin)
.count() < options_.hash_read_timeout_us) {
usleep(options_.hash_read_delay_us);
rx = nullptr;
rx = ::Read(spi_->ctx, size);
if (!rx) {
std::cerr << "Read failed, did not allocate buffer." << std::endl;
break;
}
// It appears that the hash is always the first 32 bytes in practice, but in
// testing I've seen the hash appear at random locations in the message.
// Checking for the hash at any location or even split between messages may
// not be necessary, but it is probably safer.
usleep(options_.hash_check_delay_us);
for (int i = 0; !hash_correct && i < SHA256_DIGEST_SIZE; ++i) {
if (rx[i] == hash[hash_index]) {
++hash_index;
if (hash_index == SHA256_DIGEST_SIZE) {
hash_correct = true;
}
} else {
hash_index = 0;
}
}
free(rx);
now = std::chrono::steady_clock::now();
}
if (Stop(spi_->ctx)) {
std::cerr << "Unable to terminate spi transaction." << std::endl;
return false;
}
if (!hash_correct) {
std::cerr << "Didn't receive correct hash before timeout." << std::endl;
}
return hash_correct;
}
} // namespace spiflash
} // namespace opentitan