blob: a5c8fa9946c6c0ba06e1d2e6a61da2216c506a0a [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 std::time::Duration;
use structopt::StructOpt;
use opentitanlib::app::TransportWrapper;
use opentitanlib::execute_test;
use opentitanlib::io::eeprom::AddressMode;
use opentitanlib::io::spi::{Target, Transfer};
use opentitanlib::spiflash::SpiFlash;
use opentitanlib::test_utils::init::InitializeTest;
use opentitanlib::test_utils::spi_passthru::{ConfigJedecId, SfdpData, StatusRegister, UploadInfo};
use opentitanlib::uart::console::UartConsole;
const FLASH_STATUS_WIP: u32 = 0x01;
const FLASH_STATUS_WEL: u32 = 0x02;
const FLASH_STATUS_STD_BITS: u32 = FLASH_STATUS_WEL | FLASH_STATUS_WIP;
#[derive(Debug, StructOpt)]
struct Opts {
#[structopt(flatten)]
init: InitializeTest,
#[structopt(
long, parse(try_from_str=humantime::parse_duration),
default_value = "600s",
help = "Console receive timeout",
)]
timeout: Duration,
#[structopt(
long,
default_value = "BOOTSTRAP",
help = "Name of the debugger's SPI interface"
)]
spi: String,
}
fn test_jedec_id(opts: &Opts, transport: &TransportWrapper) -> Result<()> {
let uart = transport.uart("console")?;
let config = ConfigJedecId {
device_id: 0x1234,
manufacturer_id: 0x56,
continuation_code: 0x7f,
continuation_len: 3,
};
config.execute(&*uart)?;
let spi = transport.spi(&opts.spi)?;
let jedec_id = SpiFlash::read_jedec_id(&*spi, 16)?;
log::info!("jedec_id = {:x?}", jedec_id);
// Note: there is no specified bit pattern after the end of the JEDEC ID.
// OpenTitan returns zeros. Some devices return 0xFF or repeat the byte sequence.
assert_eq!(
jedec_id,
[
0x7f, 0x7f, 0x7f, // 3 continuation codes of 0x7F.
0x56, // Manufacturer ID of 0x56.
0x34, 0x12, // Device ID 0x1234 (in little endian order).
0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // All zeros up to read length.
]
);
Ok(())
}
fn test_enter_exit_4b_mode(opts: &Opts, transport: &TransportWrapper) -> Result<()> {
let uart = transport.uart("console")?;
let spi = transport.spi(&opts.spi)?;
log::info!("Entering 4B address mode");
spi.run_transaction(&mut [Transfer::Write(&[SpiFlash::ENTER_4B])])?;
let sr = StatusRegister::read(&*uart)?;
assert!(sr.addr_4b, "expected to be in 4b mode");
log::info!("Exiting 4B address mode");
spi.run_transaction(&mut [Transfer::Write(&[SpiFlash::EXIT_4B])])?;
let sr = StatusRegister::read(&*uart)?;
assert!(!sr.addr_4b, "expected to be in 3b mode");
Ok(())
}
fn test_write_enable_disable(opts: &Opts, transport: &TransportWrapper) -> Result<()> {
let uart = transport.uart("console")?;
let spi = transport.spi(&opts.spi)?;
log::info!("Sending WRITE_ENABLE");
spi.run_transaction(&mut [Transfer::Write(&[SpiFlash::WRITE_ENABLE])])?;
let status = SpiFlash::read_status(&*spi)?;
let sr = StatusRegister::read(&*uart)?;
assert!(
status as u32 & FLASH_STATUS_WEL != 0,
"expected WEL set via read_status"
);
assert!(
sr.status & FLASH_STATUS_WEL != 0,
"expected WEL set on the device"
);
log::info!("Sending WRITE_DISABLE");
spi.run_transaction(&mut [Transfer::Write(&[SpiFlash::WRITE_DISABLE])])?;
let status = SpiFlash::read_status(&*spi)?;
let sr = StatusRegister::read(&*uart)?;
assert!(
status as u32 & FLASH_STATUS_WEL == 0,
"expected WEL clear via read_status"
);
assert!(
sr.status & FLASH_STATUS_WEL == 0,
"expected WEL clear on the device"
);
Ok(())
}
fn test_read_status_extended(opts: &Opts, transport: &TransportWrapper) -> Result<()> {
let uart = transport.uart("console")?;
let spi = transport.spi(&opts.spi)?;
let sr = StatusRegister {
status: 0x5A55AA,
addr_4b: false,
};
sr.write(&*uart)?;
// Note: because we're programming the flash_status register in firmware,
// we require one CS low-to-high transition to latch the values from the
// CSR into the spi device. We'd normally expect this type of register
// setup to be done at init time and that the first SPI transaction will
// be a READ_ID or READ_SFDP, thus latching the flash_status contents.
//
// In this test program, we simply issue a NOP transaction to the device.
spi.run_transaction(&mut [Transfer::Write(&[SpiFlash::NOP])])?;
let value = SpiFlash::read_status_ex(&*spi, None)?;
assert_eq!(value, sr.status);
Ok(())
}
fn read_sfdp(spi: &dyn Target, offset: u32) -> Result<Vec<u8>> {
let mut buf = vec![0u8; 256];
spi.run_transaction(&mut [
// READ_SFDP always takes a 3-byte address followed by a dummy
// byte regardless of address mode.
Transfer::Write(&[
SpiFlash::READ_SFDP,
(offset >> 16) as u8,
(offset >> 8) as u8,
(offset >> 0) as u8,
0, // Dummy byte.
]),
Transfer::Read(&mut buf),
])?;
Ok(buf)
}
fn test_read_sfdp(opts: &Opts, transport: &TransportWrapper) -> Result<()> {
let uart = transport.uart("console")?;
let spi = transport.spi(&opts.spi)?;
let sfdp = SfdpData {
data: (0..256).map(|x| x as u8).collect(),
};
sfdp.write(&*uart)?;
// Read and compare the whole SFDP buffer.
let buf = read_sfdp(&*spi, 0)?;
assert_eq!(buf, sfdp.data.as_slice());
// Test a read that would go beyond the length of the SFDP data.
// The observed behavior should be that the buffer recieved from
// the device should wrap around.
let buf = read_sfdp(&*spi, 0x30)?;
let data = sfdp.data.as_slice();
assert_eq!(buf[0x00..0xd0], data[0x30..0x100]);
assert_eq!(buf[0xd0..0x100], data[0x00..0x30]);
Ok(())
}
fn test_chip_erase(opts: &Opts, transport: &TransportWrapper) -> Result<()> {
let uart = transport.uart("console")?;
let spi = transport.spi(&opts.spi)?;
let flash = SpiFlash::default();
let info = UploadInfo::execute(&*uart, || {
flash.chip_erase(&*spi)?;
Ok(())
})?;
assert_eq!(info.opcode, SpiFlash::CHIP_ERASE);
assert_eq!(info.has_address, false);
assert_eq!(info.data_len, 0);
assert_eq!(
info.flash_status & FLASH_STATUS_STD_BITS,
FLASH_STATUS_WEL | FLASH_STATUS_WIP
);
Ok(())
}
fn test_sector_erase(opts: &Opts, transport: &TransportWrapper, address: u32) -> Result<()> {
let uart = transport.uart("console")?;
let spi = transport.spi(&opts.spi)?;
let mut flash = SpiFlash::default();
// Double the flash size so we can test 3b and 4b addresses.
flash.size = 32 * 1024 * 1024;
// Make sure we're in a mode appropriate for the address.
let mode = if address < 0x1000000 {
AddressMode::Mode3b
} else {
AddressMode::Mode4b
};
flash.set_address_mode(&*spi, mode)?;
let info = UploadInfo::execute(&*uart, || {
flash.erase(&*spi, address, flash.erase_size)?;
Ok(())
})?;
assert_eq!(info.opcode, SpiFlash::SECTOR_ERASE);
assert_eq!(info.has_address, true);
assert_eq!(info.addr_4b, mode == AddressMode::Mode4b);
assert_eq!(info.address, address);
assert_eq!(info.data_len, 0);
assert_eq!(
info.flash_status & FLASH_STATUS_STD_BITS,
FLASH_STATUS_WEL | FLASH_STATUS_WIP
);
Ok(())
}
fn test_write_status(opts: &Opts, transport: &TransportWrapper, opcode: u8) -> Result<()> {
let uart = transport.uart("console")?;
let spi = transport.spi(&opts.spi)?;
let info = UploadInfo::execute(&*uart, || {
spi.run_transaction(&mut [Transfer::Write(&[opcode])])?;
Ok(())
})?;
assert_eq!(info.opcode, opcode);
assert_eq!(info.has_address, false);
assert_eq!(info.data_len, 0);
assert_eq!(info.flash_status & FLASH_STATUS_STD_BITS, FLASH_STATUS_WIP);
Ok(())
}
fn main() -> Result<()> {
let opts = Opts::from_args();
opts.init.init_logging();
let transport = opts.init.init_target()?;
let uart = transport.uart("console")?;
uart.set_flow_control(true)?;
let _ = UartConsole::wait_for(&*uart, r"Running [^\r\n]*", opts.timeout)?;
uart.clear_rx_buffer()?;
execute_test!(test_jedec_id, &opts, &transport);
execute_test!(test_enter_exit_4b_mode, &opts, &transport);
execute_test!(test_write_enable_disable, &opts, &transport);
execute_test!(test_read_status_extended, &opts, &transport);
execute_test!(test_read_sfdp, &opts, &transport);
execute_test!(test_chip_erase, &opts, &transport);
execute_test!(test_sector_erase, &opts, &transport, 0x0000_4000);
execute_test!(test_sector_erase, &opts, &transport, 0x0100_4000);
execute_test!(test_write_status, &opts, &transport, SpiFlash::WRITE_STATUS);
execute_test!(
test_write_status,
&opts,
&transport,
SpiFlash::WRITE_STATUS2
);
execute_test!(
test_write_status,
&opts,
&transport,
SpiFlash::WRITE_STATUS3
);
Ok(())
}