[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(()) } }