[opentitantool] Add the EEPROM bootstrap protocol

Signed-off-by: Chris Frantz <cfrantz@google.com>
diff --git a/sw/host/opentitanlib/BUILD b/sw/host/opentitanlib/BUILD
index 086d5e8..16bc302 100644
--- a/sw/host/opentitanlib/BUILD
+++ b/sw/host/opentitanlib/BUILD
@@ -19,6 +19,7 @@
         "src/backend/ti50emulator.rs",
         "src/backend/ultradebug.rs",
         "src/backend/verilator.rs",
+        "src/bootstrap/eeprom.rs",
         "src/bootstrap/legacy.rs",
         "src/bootstrap/mod.rs",
         "src/bootstrap/primitive.rs",
diff --git a/sw/host/opentitanlib/src/bootstrap/eeprom.rs b/sw/host/opentitanlib/src/bootstrap/eeprom.rs
new file mode 100644
index 0000000..a2eaa42
--- /dev/null
+++ b/sw/host/opentitanlib/src/bootstrap/eeprom.rs
@@ -0,0 +1,53 @@
+// 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 crate::app::TransportWrapper;
+use crate::bootstrap::{Bootstrap, UpdateProtocol};
+use crate::spiflash::SpiFlash;
+use crate::transport::Capability;
+
+/// Implements the SPI EEPROM bootstrap protocol.
+pub struct Eeprom;
+
+impl Eeprom {
+    /// Creates a new `Eeprom` protocol updater.
+    pub fn new() -> Self {
+        Eeprom
+    }
+}
+
+impl UpdateProtocol for Eeprom {
+    fn verify_capabilities(
+        &self,
+        _container: &Bootstrap,
+        transport: &TransportWrapper,
+    ) -> Result<()> {
+        transport
+            .capabilities()?
+            .request(Capability::GPIO | Capability::SPI)
+            .ok()?;
+        Ok(())
+    }
+
+    fn uses_common_bootstrap_reset(&self) -> bool {
+        true
+    }
+
+    /// Performs the update protocol using the `transport` with the firmware `payload`.
+    fn update(
+        &self,
+        container: &Bootstrap,
+        transport: &TransportWrapper,
+        payload: &[u8],
+    ) -> Result<()> {
+        let spi = container.spi_params.create(transport)?;
+        let flash = SpiFlash::from_spi(&*spi)?;
+        flash.chip_erase(&*spi)?;
+        flash.program(&*spi, 0, payload)?;
+        SpiFlash::chip_reset(&*spi)?;
+        Ok(())
+    }
+}
diff --git a/sw/host/opentitanlib/src/bootstrap/mod.rs b/sw/host/opentitanlib/src/bootstrap/mod.rs
index 99db9df..a119e5f 100644
--- a/sw/host/opentitanlib/src/bootstrap/mod.rs
+++ b/sw/host/opentitanlib/src/bootstrap/mod.rs
@@ -18,6 +18,7 @@
 use crate::io::uart::UartParams;
 use crate::transport::Capability;
 
+mod eeprom;
 mod legacy;
 mod primitive;
 mod rescue;
@@ -133,9 +134,7 @@
             BootstrapProtocol::Primitive => Box::new(primitive::Primitive::new(&options)),
             BootstrapProtocol::Legacy => Box::new(legacy::Legacy::new(&options)),
             BootstrapProtocol::Rescue => Box::new(rescue::Rescue::new(&options)),
-            BootstrapProtocol::Eeprom => {
-                unimplemented!();
-            }
+            BootstrapProtocol::Eeprom => Box::new(eeprom::Eeprom::new()),
             BootstrapProtocol::Emulator => {
                 // Not intended to be implemented by this struct.
                 unimplemented!();
diff --git a/sw/host/opentitanlib/src/spiflash/flash.rs b/sw/host/opentitanlib/src/spiflash/flash.rs
index 3f509f3..e0ac370 100644
--- a/sw/host/opentitanlib/src/spiflash/flash.rs
+++ b/sw/host/opentitanlib/src/spiflash/flash.rs
@@ -231,6 +231,14 @@
         Ok(())
     }
 
+    /// Erase the entire EEPROM via the CHIP_ERASE opcode.
+    pub fn chip_erase(&self, spi: &dyn Target) -> Result<()> {
+        Self::set_write_enable(spi)?;
+        spi.run_transaction(&mut [Transfer::Write(&[Self::CHIP_ERASE])])?;
+        Self::wait_for_busy_clear(spi)?;
+        Ok(())
+    }
+
     /// Erase a segment of the SPI flash starting at `address` for `length` bytes.
     /// The address and length must be sector aligned.
     pub fn erase(&self, spi: &dyn Target, address: u32, length: u32) -> Result<()> {
@@ -311,4 +319,11 @@
         }
         Ok(())
     }
+
+    /// 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])])?;
+        Ok(())
+    }
 }