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