[opentitantool] Eeprom primitives in Spi trait
This PR introduces a new method on the spi::Target trait, for doing SPI
transactions following the EEPROM format, that is, consisting of a few
bytes of command/address, followed by a chuck of data to be either read
or written, optionally followed by repeated polling a status bit, to see
if the operation is complete.
A default implementation of the new method is provided for transport
backends which support generic SPI read/write transactions, in effect
moving part of the code from the existing SpiFlash struct into the
spi::Target trait.
Going forward, transports that do not implement generic SPI
transactions, but supports only EEPROM/Flash-style protocol, can provide
their own implementation of the new trait method, and leave the existing
generic Spi transaction method either unimplemented, or severely
restricted.
Signed-off-by: Jes B. Klinke <jbk@chromium.org>
Change-Id: Ibc33c37e7200eccdea2b2b5a370b4cd924c50bdd
diff --git a/sw/host/opentitanlib/BUILD b/sw/host/opentitanlib/BUILD
index 4620172..d7ab74a 100644
--- a/sw/host/opentitanlib/BUILD
+++ b/sw/host/opentitanlib/BUILD
@@ -68,6 +68,7 @@
"src/image/manifest.rs",
"src/image/manifest_def.rs",
"src/image/mod.rs",
+ "src/io/eeprom.rs",
"src/io/emu.rs",
"src/io/gpio.rs",
"src/io/i2c.rs",
diff --git a/sw/host/opentitanlib/src/io/eeprom.rs b/sw/host/opentitanlib/src/io/eeprom.rs
new file mode 100644
index 0000000..39c9263
--- /dev/null
+++ b/sw/host/opentitanlib/src/io/eeprom.rs
@@ -0,0 +1,243 @@
+// 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::{Deserialize, Serialize};
+
+use super::spi::SpiError;
+
+#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
+pub enum DataWidth {
+ Single, // Standard SPI
+ SingleDtr, // Data on both rising and falling edges
+ Dual, // Use both COPI and CIPO for data
+ DualDtr, // Both COPI and CIPO, both clock edges
+ Quad,
+ QuadDtr,
+ Octo,
+ OctoDtr,
+}
+
+#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
+pub struct Cmd {
+ data: [u8; 8],
+ opcode_len: u8,
+ opcode_width: DataWidth,
+ addr_len: u8,
+ addr_width: DataWidth,
+ dummy_cycles: u8,
+ data_width: DataWidth,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum AddressMode {
+ Mode3b = 3,
+ Mode4b = 4,
+}
+
+impl Default for AddressMode {
+ fn default() -> Self {
+ AddressMode::Mode3b
+ }
+}
+
+#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
+pub struct Mode {
+ pub opcode_width: DataWidth,
+ pub addr_width: DataWidth,
+ pub dummy_cycles: u8,
+ pub data_width: DataWidth,
+}
+
+impl Mode {
+ /// One-byte opcode, without address
+ pub fn cmd(&self, opcode: u8) -> Cmd {
+ let mut result = Cmd {
+ data: [0u8; 8],
+ opcode_len: 1,
+ opcode_width: self.opcode_width,
+ addr_len: 0,
+ addr_width: self.addr_width,
+ dummy_cycles: self.dummy_cycles,
+ data_width: self.data_width,
+ };
+ result.data[0] = opcode;
+ result
+ }
+ /// One-byte opcode, with address
+ pub fn cmd_addr(&self, opcode: u8, addr: u32, addr_mode: AddressMode) -> Cmd {
+ let mut result = Cmd {
+ data: [0u8; 8],
+ opcode_len: 1,
+ opcode_width: self.opcode_width,
+ addr_len: addr_mode as u8,
+ addr_width: self.addr_width,
+ dummy_cycles: self.dummy_cycles,
+ data_width: self.data_width,
+ };
+ result.data[0] = opcode;
+ result.data[1..1 + result.addr_len as usize]
+ .clone_from_slice(&addr.to_be_bytes()[4 - result.addr_len as usize..]);
+ result
+ }
+ /// Two-byte opcode, without address
+ pub fn cmd2(&self, opcode1: u8, opcode2: u8) -> Cmd {
+ let mut result = Cmd {
+ data: [0u8; 8],
+ opcode_len: 2,
+ opcode_width: self.opcode_width,
+ addr_len: 0,
+ addr_width: self.addr_width,
+ dummy_cycles: self.dummy_cycles,
+ data_width: self.data_width,
+ };
+ result.data[0] = opcode1;
+ result.data[1] = opcode2;
+ result
+ }
+ /// Two-byte opcode, with address
+ pub fn cmd2_addr(&self, opcode1: u8, opcode2: u8, addr: u32, addr_mode: AddressMode) -> Cmd {
+ let mut result = Cmd {
+ data: [0u8; 8],
+ opcode_len: 2,
+ opcode_width: self.opcode_width,
+ addr_len: addr_mode as u8,
+ addr_width: self.addr_width,
+ dummy_cycles: self.dummy_cycles,
+ data_width: self.data_width,
+ };
+ result.data[0] = opcode1;
+ result.data[1] = opcode2;
+ result.data[2..2 + result.addr_len as usize]
+ .clone_from_slice(&addr.to_be_bytes()[4 - result.addr_len as usize..]);
+ result
+ }
+
+ pub fn dummy_cycles(&self, dummy_cycles: u8) -> Mode {
+ Mode {
+ opcode_width: self.opcode_width,
+ addr_width: self.addr_width,
+ dummy_cycles,
+ data_width: self.data_width,
+ }
+ }
+}
+
+/// Single-wire
+pub const MODE_111: Mode = Mode {
+ opcode_width: DataWidth::Single,
+ addr_width: DataWidth::Single,
+ dummy_cycles: 0,
+ data_width: DataWidth::Single,
+};
+
+/// Single-wire address, dual-wire data
+pub const MODE_112: Mode = Mode {
+ opcode_width: DataWidth::Single,
+ addr_width: DataWidth::Single,
+ dummy_cycles: 0,
+ data_width: DataWidth::Dual,
+};
+
+pub const MODE_122: Mode = Mode {
+ opcode_width: DataWidth::Single,
+ addr_width: DataWidth::Dual,
+ dummy_cycles: 0,
+ data_width: DataWidth::Dual,
+};
+
+pub const MODE_222: Mode = Mode {
+ opcode_width: DataWidth::Dual,
+ addr_width: DataWidth::Dual,
+ dummy_cycles: 0,
+ data_width: DataWidth::Dual,
+};
+
+/// Single-wire address, quad-wire data
+pub const MODE_114: Mode = Mode {
+ opcode_width: DataWidth::Single,
+ addr_width: DataWidth::Single,
+ dummy_cycles: 0,
+ data_width: DataWidth::Quad,
+};
+
+pub const MODE_144: Mode = Mode {
+ opcode_width: DataWidth::Single,
+ addr_width: DataWidth::Quad,
+ dummy_cycles: 0,
+ data_width: DataWidth::Quad,
+};
+
+pub const MODE_444: Mode = Mode {
+ opcode_width: DataWidth::Quad,
+ addr_width: DataWidth::Quad,
+ dummy_cycles: 0,
+ data_width: DataWidth::Quad,
+};
+
+impl Cmd {
+ /// Method use to get binary representation of the command for use on "plain" SPI. Will be
+ /// used in cases where the transport backend does not have specialied EEPROM/Flash
+ /// communication primitives.
+ pub fn to_bytes(&self) -> Result<&[u8]> {
+ if self.opcode_width == DataWidth::Single
+ && self.addr_width == DataWidth::Single
+ && self.dummy_cycles % 8 == 0
+ && self.data_width == DataWidth::Single
+ {
+ Ok(&self.data[0..(self.opcode_len + self.addr_len + self.dummy_cycles / 8) as usize])
+ } else {
+ Err(SpiError::InvalidOption(
+ "This target does not support the requested mode".to_string(),
+ )
+ .into())
+ }
+ }
+
+ pub fn get_opcode_len(&self) -> u8 {
+ self.opcode_len
+ }
+
+ pub fn get_opcode_width(&self) -> DataWidth {
+ self.opcode_width
+ }
+
+ pub fn get_opcode(&self) -> &[u8] {
+ &self.data[..self.opcode_len as usize]
+ }
+
+ pub fn get_address_len(&self) -> u8 {
+ self.addr_len
+ }
+
+ pub fn get_address_width(&self) -> DataWidth {
+ self.addr_width
+ }
+
+ pub fn get_address(&self) -> u32 {
+ let mut addr_bytes = [0u8; 4];
+ addr_bytes[4 - self.addr_len as usize..].clone_from_slice(
+ &self.data[self.opcode_len as usize..(self.opcode_len + self.addr_len) as usize],
+ );
+ u32::from_be_bytes(addr_bytes)
+ }
+
+ pub fn get_dummy_cycles(&self) -> u8 {
+ self.dummy_cycles
+ }
+
+ pub fn get_data_width(&self) -> DataWidth {
+ self.data_width
+ }
+}
+
+pub enum Transaction<'rd, 'wr> {
+ Command(Cmd),
+ Read(Cmd, &'rd mut [u8]),
+ Write(Cmd, &'wr [u8]),
+ WaitForBusyClear,
+}
+
+pub const READ_STATUS: u8 = 0x05;
+pub const STATUS_WIP: u8 = 0x01;
diff --git a/sw/host/opentitanlib/src/io/mod.rs b/sw/host/opentitanlib/src/io/mod.rs
index b987a97..b52129d 100644
--- a/sw/host/opentitanlib/src/io/mod.rs
+++ b/sw/host/opentitanlib/src/io/mod.rs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
+pub mod eeprom;
pub mod emu;
pub mod gpio;
pub mod i2c;
diff --git a/sw/host/opentitanlib/src/io/spi.rs b/sw/host/opentitanlib/src/io/spi.rs
index 44ed19e..e669ac4 100644
--- a/sw/host/opentitanlib/src/io/spi.rs
+++ b/sw/host/opentitanlib/src/io/spi.rs
@@ -9,6 +9,7 @@
use structopt::StructOpt;
use thiserror::Error;
+use super::eeprom;
use crate::app::TransportWrapper;
use crate::impl_serializable_error;
use crate::util::voltage::Voltage;
@@ -159,6 +160,42 @@
/// CS for the duration of the entire transactions.
fn run_transaction(&self, transaction: &mut [Transfer]) -> Result<()>;
+ /// Runs a number of EEPROM/FLASH protocol SPI transactions. Will assert and deassert CS for
+ /// each transaction.
+ fn run_eeprom_transactions(&self, transactions: &mut [eeprom::Transaction]) -> Result<()> {
+ // Default implementation translates into generic SPI read/write, which works as long as
+ // the transport supports generic SPI transfers of sufficint length, and that the mode is
+ // single-data-wire.
+ for transfer in transactions {
+ match transfer {
+ eeprom::Transaction::Command(cmd) => {
+ self.run_transaction(&mut [Transfer::Write(cmd.to_bytes()?)])?
+ }
+ eeprom::Transaction::Read(cmd, rbuf) => self.run_transaction(&mut [
+ Transfer::Write(cmd.to_bytes()?),
+ Transfer::Read(rbuf),
+ ])?,
+ eeprom::Transaction::Write(cmd, wbuf) => self.run_transaction(&mut [
+ Transfer::Write(cmd.to_bytes()?),
+ Transfer::Write(wbuf),
+ ])?,
+ eeprom::Transaction::WaitForBusyClear => {
+ while {
+ let mut buf = [0u8; 1];
+ self.run_transaction(&mut [
+ Transfer::Write(&[eeprom::READ_STATUS]),
+ Transfer::Read(&mut buf),
+ ])?;
+ buf[0]
+ } & eeprom::STATUS_WIP
+ != 0
+ {}
+ }
+ }
+ }
+ Ok(())
+ }
+
/// Assert the CS signal. Uses reference counting, will be deasserted when each and every
/// returned `AssertChipSelect` object have gone out of scope.
fn assert_cs(self: Rc<Self>) -> Result<AssertChipSelect>;
diff --git a/sw/host/opentitanlib/src/spiflash/flash.rs b/sw/host/opentitanlib/src/spiflash/flash.rs
index 1f1ff08..e8bad7e 100644
--- a/sw/host/opentitanlib/src/spiflash/flash.rs
+++ b/sw/host/opentitanlib/src/spiflash/flash.rs
@@ -2,7 +2,8 @@
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
-use crate::io::spi::{Target, Transfer};
+use crate::io::eeprom::{AddressMode, Transaction, MODE_111};
+use crate::io::spi::Target;
use crate::spiflash::sfdp::{BlockEraseSize, Sfdp, SupportedAddressModes};
use anyhow::{ensure, Result};
use std::convert::TryFrom;
@@ -20,12 +21,6 @@
BadSequenceLength(usize),
}
-#[derive(Debug, PartialEq, Eq)]
-pub enum AddressMode {
- Mode3b,
- Mode4b,
-}
-
impl From<SupportedAddressModes> for AddressMode {
fn from(mode: SupportedAddressModes) -> Self {
match mode {
@@ -35,12 +30,6 @@
}
}
-impl Default for AddressMode {
- fn default() -> Self {
- AddressMode::Mode3b
- }
-}
-
pub struct SpiFlash {
pub size: u32,
pub erase_size: u32,
@@ -92,39 +81,23 @@
/// The `WEL` bit is the write enable latch.
pub const STATUS_WEL: u8 = 0x02;
- /// Prepare an opcode and address buffer according to the address_mode.
- fn opcode_with_address(&self, opcode: u8, address: u32) -> Result<Vec<u8>> {
- ensure!(
- address < self.size,
- Error::AddressOutOfBounds(address, self.size)
- );
- let mut buf = vec![opcode];
- if self.address_mode == AddressMode::Mode4b {
- buf.push((address >> 24) as u8);
- }
- buf.push((address >> 16) as u8);
- buf.push((address >> 8) as u8);
- buf.push(address as u8);
- Ok(buf)
- }
-
/// Read `length` bytes of the JEDEC ID from the `spi` target.
pub fn read_jedec_id(spi: &dyn Target, length: usize) -> Result<Vec<u8>> {
let mut buf = vec![0u8; length];
- spi.run_transaction(&mut [
- Transfer::Write(&[SpiFlash::READ_ID]),
- Transfer::Read(&mut buf),
- ])?;
+ spi.run_eeprom_transactions(&mut [Transaction::Read(
+ MODE_111.cmd(SpiFlash::READ_ID),
+ &mut buf,
+ )])?;
Ok(buf)
}
/// Read status register from the `spi` target.
pub fn read_status(spi: &dyn Target) -> Result<u8> {
let mut buf = [0u8; 1];
- spi.run_transaction(&mut [
- Transfer::Write(&[SpiFlash::READ_STATUS]),
- Transfer::Read(&mut buf),
- ])?;
+ spi.run_eeprom_transactions(&mut [Transaction::Read(
+ MODE_111.cmd(SpiFlash::READ_STATUS),
+ &mut buf,
+ )])?;
Ok(buf[0])
}
@@ -137,33 +110,33 @@
);
let mut buf = [0u8; 4];
for (op, byte) in seq.iter().zip(buf.iter_mut()) {
- spi.run_transaction(&mut [
- Transfer::Write(std::slice::from_ref(op)),
- Transfer::Read(std::slice::from_mut(byte)),
- ])?;
+ spi.run_eeprom_transactions(&mut [Transaction::Read(
+ MODE_111.cmd(*op),
+ std::slice::from_mut(byte),
+ )])?;
}
Ok(u32::from_le_bytes(buf))
}
/// Poll the status register waiting for the busy bit to clear.
pub fn wait_for_busy_clear(spi: &dyn Target) -> Result<()> {
- while SpiFlash::read_status(spi)? & SpiFlash::STATUS_WIP != 0 {
- // Do nothing.
- }
+ spi.run_eeprom_transactions(&mut [Transaction::WaitForBusyClear])?;
Ok(())
}
/// Send the WRITE_ENABLE opcode to the `spi` target.
pub fn set_write_enable(spi: &dyn Target) -> Result<()> {
- let wren = [SpiFlash::WRITE_ENABLE];
- spi.run_transaction(&mut [Transfer::Write(&wren)])?;
+ spi.run_eeprom_transactions(&mut [Transaction::Command(
+ MODE_111.cmd(SpiFlash::WRITE_ENABLE),
+ )])?;
Ok(())
}
/// Send the WRITE_DISABLE opcode to the `spi` target.
pub fn set_write_disable(spi: &dyn Target) -> Result<()> {
- let wrdi = [SpiFlash::WRITE_DISABLE];
- spi.run_transaction(&mut [Transfer::Write(&wrdi)])?;
+ spi.run_eeprom_transactions(&mut [Transaction::Command(
+ MODE_111.cmd(SpiFlash::WRITE_DISABLE),
+ )])?;
Ok(())
}
@@ -172,13 +145,14 @@
let mut buf = vec![0u8; 256];
let mut tries = 0;
loop {
- spi.run_transaction(&mut [
- // READ_SFDP always takes a 3-byte address followed by a dummy
- // byte regardless of address mode. To simplify, we always
- // read from address zero.
- Transfer::Write(&[SpiFlash::READ_SFDP, 0, 0, 0, 0]),
- Transfer::Read(&mut buf),
- ])?;
+ // READ_SFDP always takes a 3-byte address followed by a dummy byte regardless of
+ // address mode.
+ spi.run_eeprom_transactions(&mut [Transaction::Read(
+ MODE_111
+ .dummy_cycles(8)
+ .cmd_addr(SpiFlash::READ_SFDP, 0, AddressMode::Mode3b),
+ &mut buf,
+ )])?;
// We only want to give SFDP parsing one extra chance for length
// extension. If parsing fails a second time, just return the error.
@@ -215,11 +189,11 @@
/// Set the SPI flash addressing mode to either 3b or 4b mode.
pub fn set_address_mode(&mut self, spi: &dyn Target, mode: AddressMode) -> Result<()> {
- let opcode = [match mode {
+ let opcode = match mode {
AddressMode::Mode3b => SpiFlash::EXIT_4B,
AddressMode::Mode4b => SpiFlash::ENTER_4B,
- }];
- spi.run_transaction(&mut [Transfer::Write(&opcode)])?;
+ };
+ spi.run_eeprom_transactions(&mut [Transaction::Command(MODE_111.cmd(opcode))])?;
self.address_mode = mode;
Ok(())
}
@@ -252,8 +226,10 @@
) -> Result<&Self> {
// Break the read up according to the maximum chunksize the backend can handle.
for chunk in buffer.chunks_mut(spi.max_chunk_size()?) {
- let op_addr = self.opcode_with_address(SpiFlash::READ, address)?;
- spi.run_transaction(&mut [Transfer::Write(&op_addr), Transfer::Read(chunk)])?;
+ spi.run_eeprom_transactions(&mut [Transaction::Read(
+ MODE_111.cmd_addr(SpiFlash::READ, address, self.address_mode),
+ chunk,
+ )])?;
address += chunk.len() as u32;
progress(address, chunk.len() as u32);
}
@@ -262,9 +238,11 @@
/// Erase the entire EEPROM via the CHIP_ERASE opcode.
pub fn chip_erase(&self, spi: &dyn Target) -> Result<&Self> {
- Self::set_write_enable(spi)?;
- spi.run_transaction(&mut [Transfer::Write(&[Self::CHIP_ERASE])])?;
- Self::wait_for_busy_clear(spi)?;
+ spi.run_eeprom_transactions(&mut [
+ Transaction::Command(MODE_111.cmd(SpiFlash::WRITE_ENABLE)),
+ Transaction::Command(MODE_111.cmd(SpiFlash::CHIP_ERASE)),
+ Transaction::WaitForBusyClear,
+ ])?;
Ok(self)
}
@@ -292,12 +270,15 @@
}
let end = address + length;
for addr in (address..end).step_by(self.erase_size as usize) {
- // Issue the write enable first as a separate transaction.
- SpiFlash::set_write_enable(spi)?;
- // Then issue the erase transaction.
- let op_addr = self.opcode_with_address(SpiFlash::SECTOR_ERASE, addr)?;
- spi.run_transaction(&mut [Transfer::Write(&op_addr)])?;
- SpiFlash::wait_for_busy_clear(spi)?;
+ spi.run_eeprom_transactions(&mut [
+ Transaction::Command(MODE_111.cmd(SpiFlash::WRITE_ENABLE)),
+ Transaction::Command(MODE_111.cmd_addr(
+ SpiFlash::SECTOR_ERASE,
+ addr,
+ self.address_mode,
+ )),
+ Transaction::WaitForBusyClear,
+ ])?;
progress(addr, self.erase_size);
}
Ok(self)
@@ -335,12 +316,14 @@
let chunk = &buffer[chunk_start..chunk_end];
// Skip this chunk if all bytes are 0xff.
if !chunk.iter().all(|&x| x == 0xff) {
- let op_addr = self.opcode_with_address(SpiFlash::PAGE_PROGRAM, address)?;
- // Issue the write enable first as a separate transaction.
- SpiFlash::set_write_enable(spi)?;
- // Then issue the program operation.
- spi.run_transaction(&mut [Transfer::Write(&op_addr), Transfer::Write(chunk)])?;
- SpiFlash::wait_for_busy_clear(spi)?;
+ spi.run_eeprom_transactions(&mut [
+ Transaction::Command(MODE_111.cmd(SpiFlash::WRITE_ENABLE)),
+ Transaction::Write(
+ MODE_111.cmd_addr(SpiFlash::PAGE_PROGRAM, address, self.address_mode),
+ chunk,
+ ),
+ Transaction::WaitForBusyClear,
+ ])?;
}
address += chunk_size as u32;
chunk_start += chunk_size;
@@ -352,8 +335,10 @@
/// Send the software reset sequence to the `spi` target.
pub fn chip_reset(spi: &dyn Target) -> Result<()> {
- spi.run_transaction(&mut [Transfer::Write(&[Self::RESET_ENABLE])])?;
- spi.run_transaction(&mut [Transfer::Write(&[Self::RESET])])?;
+ spi.run_eeprom_transactions(&mut [
+ Transaction::Command(MODE_111.cmd(SpiFlash::RESET_ENABLE)),
+ Transaction::Command(MODE_111.cmd(SpiFlash::RESET)),
+ ])?;
Ok(())
}
}