blob: 935da527b5c63ea97f0daccb1c64dfd450685f6f [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
use anyhow::Result;
use serde_annotate::Annotate;
use std::any::Any;
use std::fs::{self, File};
use std::io::{self, Write};
use std::path::PathBuf;
use std::time::{Duration, Instant};
use structopt::StructOpt;
use opentitanlib::app::command::CommandDispatch;
use opentitanlib::app::{self, TransportWrapper};
// Use PinMode if gpio is enabled
// use opentitanlib::io::gpio::PinMode;
use opentitanlib::io::spi::{SpiParams, Transfer};
use opentitanlib::spiflash::SpiFlash;
use opentitanlib::tpm;
use opentitanlib::transport::Capability;
/// Read and parse an SFDP table.
#[derive(Debug, StructOpt)]
pub struct SpiSfdp {
#[structopt(
short,
long,
help = "Display raw SFDP bytes rather than the parsed struct."
)]
raw: Option<usize>,
#[structopt(
short,
long,
help = "Start reading SFDP at offset. Only valid with --raw."
)]
offset: Option<u32>,
}
// Print a hexdump of a buffer to `writer`.
// The hexdump includes the offset, hex bytes and printable ASCII characters.
//
// 00000000: 53 46 44 50 06 01 02 ff 00 06 01 10 30 00 00 ff SFDP........0...
// 00000010: c2 00 01 04 10 01 00 ff 84 00 01 02 c0 00 00 ff ................
// 00000020: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
// 00000030: e5 20 fb ff ff ff ff 3f 44 eb 08 6b 08 3b 04 bb . .....?D..k.;..
//
// Note: This format can be consumed by `xxd -r` and converted back into binary.
fn hexdump(mut writer: impl Write, buf: &[u8]) -> Result<()> {
for (i, chunk) in buf.chunks(16).enumerate() {
let mut ascii = [b'.'; 16];
write!(writer, "{:08x}:", i * 16)?;
for (j, byte) in chunk.iter().copied().enumerate() {
write!(writer, " {:02x}", byte)?;
// For printable ASCII chars, place them in the ascii buffer.
if byte == b' ' || byte.is_ascii_graphic() {
ascii[j] = byte;
}
}
// Align and print the ascii buffer.
let j = chunk.len();
for _ in 0..(16 - j) {
write!(writer, " ")?;
}
writeln!(writer, " {}", std::str::from_utf8(&ascii[0..j]).unwrap())?;
}
Ok(())
}
impl CommandDispatch for SpiSfdp {
fn run(
&self,
context: &dyn Any,
transport: &TransportWrapper,
) -> Result<Option<Box<dyn Annotate>>> {
transport.capabilities()?.request(Capability::SPI).ok()?;
let context = context.downcast_ref::<SpiCommand>().unwrap();
let spi = context.params.create(transport, "BOOTSTRAP")?;
if let Some(length) = self.raw {
let offset = self.offset.unwrap_or(0);
let mut buffer = vec![0u8; length];
spi.run_transaction(&mut [
Transfer::Write(&[
SpiFlash::READ_SFDP,
(offset >> 16) as u8,
(offset >> 8) as u8,
(offset >> 0) as u8,
0, // Dummy byte.
]),
Transfer::Read(&mut buffer),
])?;
hexdump(io::stdout(), &buffer)?;
Ok(None)
} else {
let sfdp = SpiFlash::read_sfdp(&*spi)?;
Ok(Some(Box::new(sfdp)))
}
}
}
/// Read the JEDEC ID of a SPI EEPROM.
#[derive(Debug, StructOpt)]
pub struct SpiReadId {
#[structopt(
short = "n",
long,
default_value = "15",
help = "Number of JEDEC ID bytes to read."
)]
length: usize,
}
#[derive(Debug, serde::Serialize, Annotate)]
pub struct SpiReadIdResponse {
#[annotate(format = hex)]
jedec_id: Vec<u8>,
}
impl CommandDispatch for SpiReadId {
fn run(
&self,
context: &dyn Any,
transport: &TransportWrapper,
) -> Result<Option<Box<dyn Annotate>>> {
transport.capabilities()?.request(Capability::SPI).ok()?;
let context = context.downcast_ref::<SpiCommand>().unwrap();
let spi = context.params.create(transport, "BOOTSTRAP")?;
let jedec_id = SpiFlash::read_jedec_id(&*spi, self.length)?;
Ok(Some(Box::new(SpiReadIdResponse { jedec_id })))
}
}
/// Read data from a SPI EEPROM.
#[derive(Debug, StructOpt)]
pub struct SpiRead {
#[structopt(short, long, default_value = "0", help = "Start offset.")]
start: u32,
#[structopt(
short = "n",
long,
default_value = "4096",
help = "Number of bytes to read."
)]
length: usize,
#[structopt(short, long, help = "Hexdump the data.")]
hexdump: bool,
#[structopt(name = "FILE", default_value = "-")]
filename: PathBuf,
}
#[derive(Debug, serde::Serialize)]
pub struct SpiReadResponse {
length: usize,
bytes_per_second: f64,
}
impl SpiRead {
fn write_file(&self, mut writer: impl Write, buffer: &[u8]) -> Result<()> {
if self.hexdump {
hexdump(writer, buffer)?;
} else {
writer.write_all(buffer)?;
}
Ok(())
}
}
impl CommandDispatch for SpiRead {
fn run(
&self,
context: &dyn Any,
transport: &TransportWrapper,
) -> Result<Option<Box<dyn Annotate>>> {
transport.capabilities()?.request(Capability::SPI).ok()?;
let context = context.downcast_ref::<SpiCommand>().unwrap();
let spi = context.params.create(transport, "BOOTSTRAP")?;
let mut flash = SpiFlash::from_spi(&*spi)?;
flash.set_address_mode_auto(&*spi)?;
let mut buffer = vec![0u8; self.length];
let progress = app::progress_bar(self.length as u64);
let t0 = Instant::now();
flash.read_with_progress(&*spi, self.start, &mut buffer, |_, chunk| {
progress.inc(chunk as u64);
})?;
progress.finish();
let duration = t0.elapsed().as_secs_f64();
if self.filename.to_str() == Some("-") {
self.write_file(io::stdout(), &buffer)?;
Ok(None)
} else {
let file = File::create(&self.filename)?;
self.write_file(file, &buffer)?;
Ok(Some(Box::new(SpiReadResponse {
length: buffer.len(),
bytes_per_second: buffer.len() as f64 / duration,
})))
}
}
}
/// Erase sectors of a SPI EEPROM.
#[derive(Debug, StructOpt)]
pub struct SpiErase {
#[structopt(short, long, help = "Start offset.")]
start: u32,
#[structopt(short = "n", long, help = "Number of bytes to erase.")]
length: u32,
}
#[derive(Debug, serde::Serialize)]
pub struct SpiEraseResponse {
length: u32,
bytes_per_second: f64,
}
#[derive(Debug, StructOpt)]
pub struct SpiBlockErase {
#[structopt(short, long, help = "Start offset.")]
start: u32,
#[structopt(short = "n", long, help = "Number of bytes to erase.")]
length: u32,
}
#[derive(Debug, serde::Serialize)]
pub struct SpiBlockEraseResponse {
length: u32,
bytes_per_second: f64,
}
#[derive(Debug, StructOpt)]
pub struct SpiChipErase {}
#[derive(Debug, serde::Serialize)]
pub struct SpiChipEraseResponse {}
impl CommandDispatch for SpiErase {
fn run(
&self,
context: &dyn Any,
transport: &TransportWrapper,
) -> Result<Option<Box<dyn Annotate>>> {
transport.capabilities()?.request(Capability::SPI).ok()?;
let context = context.downcast_ref::<SpiCommand>().unwrap();
let spi = context.params.create(transport, "BOOTSTRAP")?;
let mut flash = SpiFlash::from_spi(&*spi)?;
flash.set_address_mode_auto(&*spi)?;
let progress = app::progress_bar(self.length as u64);
let t0 = Instant::now();
flash.erase_with_progress(&*spi, self.start, self.length, |_, chunk| {
progress.inc(chunk as u64);
})?;
progress.finish();
let duration = t0.elapsed().as_secs_f64();
Ok(Some(Box::new(SpiEraseResponse {
length: self.length,
bytes_per_second: self.length as f64 / duration,
})))
}
}
impl CommandDispatch for SpiBlockErase {
fn run(
&self,
context: &dyn Any,
transport: &TransportWrapper,
) -> Result<Option<Box<dyn Annotate>>> {
transport.capabilities()?.request(Capability::SPI).ok()?;
let context = context.downcast_ref::<SpiCommand>().unwrap();
let spi = context.params.create(transport, "BOOTSTRAP")?;
let mut flash = SpiFlash::from_spi(&*spi)?;
flash.set_address_mode_auto(&*spi)?;
let progress = app::progress_bar(self.length as u64);
let t0 = Instant::now();
flash.block_erase_with_progress(&*spi, self.start, self.length, |_, chunk| {
progress.inc(chunk as u64);
})?;
progress.finish();
let duration = t0.elapsed().as_secs_f64();
Ok(Some(Box::new(SpiBlockEraseResponse {
length: self.length,
bytes_per_second: self.length as f64 / duration,
})))
}
}
impl CommandDispatch for SpiChipErase {
fn run(
&self,
context: &dyn Any,
transport: &TransportWrapper,
) -> Result<Option<Box<dyn Annotate>>> {
transport.capabilities()?.request(Capability::SPI).ok()?;
let context = context.downcast_ref::<SpiCommand>().unwrap();
let spi = context.params.create(transport, "BOOTSTRAP")?;
let mut flash = SpiFlash::from_spi(&*spi)?;
flash.set_address_mode_auto(&*spi)?;
flash.chip_erase(&*spi)?;
Ok(Some(Box::new(SpiChipEraseResponse {})))
}
}
/// Program data into a SPI EEPROM.
#[derive(Debug, StructOpt)]
pub struct SpiProgram {
#[structopt(short, long, default_value = "0", help = "Start offset.")]
start: u32,
#[structopt(name = "FILE")]
filename: PathBuf,
}
#[derive(Debug, serde::Serialize)]
pub struct SpiProgramResponse {
length: usize,
bytes_per_second: f64,
}
/// Program data into a SPI EEPROM.
impl CommandDispatch for SpiProgram {
fn run(
&self,
context: &dyn Any,
transport: &TransportWrapper,
) -> Result<Option<Box<dyn Annotate>>> {
transport.capabilities()?.request(Capability::SPI).ok()?;
let context = context.downcast_ref::<SpiCommand>().unwrap();
let spi = context.params.create(transport, "BOOTSTRAP")?;
let mut flash = SpiFlash::from_spi(&*spi)?;
flash.set_address_mode_auto(&*spi)?;
let buffer = fs::read(&self.filename)?;
let progress = app::progress_bar(buffer.len() as u64);
let t0 = Instant::now();
flash.program_with_progress(&*spi, self.start, &buffer, |_, chunk| {
progress.inc(chunk as u64);
})?;
progress.finish();
let duration = t0.elapsed().as_secs_f64();
Ok(Some(Box::new(SpiProgramResponse {
length: buffer.len(),
bytes_per_second: buffer.len() as f64 / duration,
})))
}
}
#[derive(Debug, StructOpt)]
pub struct SpiReadMemory {
#[structopt(short, long, default_value = "0", help = "Start offset.")]
start: u32,
#[structopt(short, long, help = "Length to readback.")]
length: u32,
#[structopt(short, long, help = "Hexdump the data.")]
hexdump: bool,
#[structopt(name = "FILE", default_value = "-")]
filename: PathBuf,
}
#[derive(Debug, serde::Serialize)]
pub struct SpiReadMemoryResponse {
length: usize,
bytes_per_second: f64,
}
impl SpiReadMemory {
fn write_file(&self, mut writer: impl Write, buffer: &[u8]) -> Result<()> {
if self.hexdump {
hexdump(writer, buffer)?;
} else {
writer.write_all(buffer)?;
}
Ok(())
}
}
impl CommandDispatch for SpiReadMemory {
fn run(
&self,
context: &dyn Any,
transport: &TransportWrapper,
) -> Result<Option<Box<dyn Annotate>>> {
transport.capabilities()?.request(Capability::SPI).ok()?;
transport.capabilities()?.request(Capability::GPIO).ok()?;
let context = context.downcast_ref::<SpiCommand>().unwrap();
let spi = context.params.create(transport, "BOOTSTRAP")?;
// Rename to gpio if the gpio loop is used
let _gpio = transport.gpio_pin("SW_STRAP0")?;
spi.set_max_speed(1_000_000)?;
let mut buffer = vec![0u8; self.length as usize];
let mut write_buffer = Vec::with_capacity(8);
write_buffer.extend(self.start.to_be_bytes());
write_buffer.extend(self.length.to_be_bytes());
spi.run_transaction(&mut [Transfer::Write(&write_buffer)])?;
// TODO(atv): For some reason, the GPIO does not
// read back a changed value in this context (but does, if you try to read it separately)
// If we can resolve this, uncomment out this polling block. For now, however, wait 50ms.
// gpio.set_mode(PinMode::Input)?;
// loop {
// let val = gpio.read()?;
// if val {
// break;
// }
// std::thread::sleep(Duration::from_millis(50));
// }
// gpio.set_mode(PinMode::PushPull)?;
std::thread::sleep(Duration::from_millis(50));
let t0 = Instant::now();
let chunk_size = spi.max_chunk_size()?;
let mut remaining = buffer.len();
for i in 0..=(buffer.len() / chunk_size) {
let chunk_len = std::cmp::min(chunk_size, remaining);
let start = i * chunk_size;
let end = start + chunk_len;
spi.run_transaction(&mut [Transfer::Read(&mut buffer[start..end])])?;
remaining -= chunk_len;
}
let duration = t0.elapsed().as_secs_f64();
if self.filename.to_str() == Some("-") {
self.write_file(io::stdout(), &buffer)?;
Ok(None)
} else {
let file = File::create(&self.filename)?;
self.write_file(file, &buffer)?;
Ok(Some(Box::new(SpiReadMemoryResponse {
length: buffer.len(),
bytes_per_second: buffer.len() as f64 / duration,
})))
}
}
}
#[derive(Debug, StructOpt)]
pub struct SpiTpm {
#[structopt(subcommand)]
command: super::tpm::TpmSubCommand,
}
impl CommandDispatch for SpiTpm {
fn run(
&self,
context: &dyn Any,
transport: &TransportWrapper,
) -> Result<Option<Box<dyn Annotate>>> {
let context = context.downcast_ref::<SpiCommand>().unwrap();
let bus: Box<dyn tpm::Driver> = Box::new(tpm::SpiDriver::new(
context.params.create(transport, "TPM")?,
));
self.command.run(&bus, transport)
}
}
/// Commands for interacting with a SPI EEPROM.
#[derive(Debug, StructOpt, CommandDispatch)]
pub enum InternalSpiCommand {
Sfdp(SpiSfdp),
ReadId(SpiReadId),
Read(SpiRead),
Erase(SpiErase),
BlockErase(SpiBlockErase),
ChipErase(SpiChipErase),
Program(SpiProgram),
ReadMemory(SpiReadMemory),
Tpm(SpiTpm),
}
#[derive(Debug, StructOpt)]
pub struct SpiCommand {
#[structopt(flatten)]
params: SpiParams,
#[structopt(subcommand)]
command: InternalSpiCommand,
}
impl CommandDispatch for SpiCommand {
fn run(
&self,
_context: &dyn Any,
transport: &TransportWrapper,
) -> Result<Option<Box<dyn Annotate>>> {
// None of the SPI commands care about the prior context, but they do
// care about the `bus` parameter in the current node.
self.command.run(self, transport)
}
}