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