Refactor bitstream loading

Refactor bitstream loading into common code and transport-specific code.

Signed-off-by: Chris Frantz <cfrantz@google.com>
diff --git a/sw/host/opentitanlib/BUILD b/sw/host/opentitanlib/BUILD
index c413d39..8dedb05 100644
--- a/sw/host/opentitanlib/BUILD
+++ b/sw/host/opentitanlib/BUILD
@@ -96,6 +96,7 @@
         "src/tpm/mod.rs",
         "src/tpm/status.rs",
         "src/transport/common/mod.rs",
+        "src/transport/common/fpga.rs",
         "src/transport/common/uart.rs",
         "src/transport/cw310/gpio.rs",
         "src/transport/cw310/mod.rs",
diff --git a/sw/host/opentitanlib/src/test_utils/load_bitstream.rs b/sw/host/opentitanlib/src/test_utils/load_bitstream.rs
index d69c8b8..5100c31 100644
--- a/sw/host/opentitanlib/src/test_utils/load_bitstream.rs
+++ b/sw/host/opentitanlib/src/test_utils/load_bitstream.rs
@@ -9,7 +9,7 @@
 use structopt::StructOpt;
 
 use crate::app::{self, TransportWrapper};
-use crate::transport::cw310;
+use crate::transport::common::fpga::FpgaProgram;
 use crate::util::rom_detect::RomKind;
 
 /// Load a bitstream into the FPGA.
@@ -51,7 +51,7 @@
         let pfunc = Box::new(move |_, chunk| {
             progress.inc(chunk as u64);
         });
-        let operation = cw310::FpgaProgram {
+        let operation = FpgaProgram {
             bitstream: payload,
             rom_kind: self.rom_kind,
             rom_reset_pulse: self.rom_reset_pulse,
diff --git a/sw/host/opentitanlib/src/transport/common/fpga.rs b/sw/host/opentitanlib/src/transport/common/fpga.rs
new file mode 100644
index 0000000..c8fcbfa
--- /dev/null
+++ b/sw/host/opentitanlib/src/transport/common/fpga.rs
@@ -0,0 +1,57 @@
+// 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 crate::io::gpio::GpioPin;
+use crate::io::uart::Uart;
+use crate::util::rom_detect::{RomDetect, RomKind};
+
+/// Command for Transport::dispatch().
+pub struct FpgaProgram<'a> {
+    /// The bitstream content to load into the FPGA.
+    pub bitstream: Vec<u8>,
+    /// What type of ROM to expect.
+    pub rom_kind: Option<RomKind>,
+    /// How long of a reset pulse to send to the device.
+    pub rom_reset_pulse: Duration,
+    /// How long to wait for the ROM to print its type and version.
+    pub rom_timeout: Duration,
+    /// A progress function to provide user feedback.
+    /// Will be called with the address and length of each chunk sent to the target device.
+    pub progress: Option<Box<dyn Fn(u32, u32) + 'a>>,
+}
+
+impl FpgaProgram<'_> {
+    pub fn check_correct_version(&self, uart: &dyn Uart, reset_pin: &dyn GpioPin) -> Result<bool> {
+        if let Some(rom_kind) = &self.rom_kind {
+            let mut rd = RomDetect::new(*rom_kind, &self.bitstream, Some(self.rom_timeout))?;
+
+            // Send a reset pulse so the ROM will print the FPGA version.
+            // Reset is active low, sleep, then drive high.
+            reset_pin.write(false)?;
+            std::thread::sleep(self.rom_reset_pulse);
+            // Also clear the UART RX buffer for improved robustness.
+            uart.clear_rx_buffer()?;
+            reset_pin.write(true)?;
+
+            // Now read the uart until the ROM prints it's version.
+            if rd.detect(&*uart)? {
+                log::info!("Already running the correct bitstream.  Skip loading bitstream.");
+                // If we're already running the right ROM+bitstream,
+                // then we can skip bootstrap.
+                return Ok(true);
+            }
+        }
+        Ok(false)
+    }
+
+    pub fn skip(&self) -> bool {
+        self.bitstream.starts_with(b"__skip__")
+    }
+}
+
+/// Command for Transport::dispatch().
+pub struct ClearBitstream;
diff --git a/sw/host/opentitanlib/src/transport/common/mod.rs b/sw/host/opentitanlib/src/transport/common/mod.rs
index af23e89..36c135d 100644
--- a/sw/host/opentitanlib/src/transport/common/mod.rs
+++ b/sw/host/opentitanlib/src/transport/common/mod.rs
@@ -2,4 +2,5 @@
 // Licensed under the Apache License, Version 2.0, see LICENSE for details.
 // SPDX-License-Identifier: Apache-2.0
 
+pub mod fpga;
 pub mod uart;
diff --git a/sw/host/opentitanlib/src/transport/cw310/mod.rs b/sw/host/opentitanlib/src/transport/cw310/mod.rs
index 3430f19..1f87b6e 100644
--- a/sw/host/opentitanlib/src/transport/cw310/mod.rs
+++ b/sw/host/opentitanlib/src/transport/cw310/mod.rs
@@ -10,17 +10,16 @@
 use std::collections::hash_map::Entry;
 use std::collections::HashMap;
 use std::rc::Rc;
-use std::time::Duration;
 
 use crate::io::gpio::GpioPin;
 use crate::io::spi::Target;
 use crate::io::uart::{Uart, UartError};
+use crate::transport::common::fpga::{ClearBitstream, FpgaProgram};
 use crate::transport::common::uart::SerialPortUart;
 use crate::transport::{
     Capabilities, Capability, Transport, TransportError, TransportInterfaceType,
 };
 use crate::util::parse_int::ParseInt;
-use crate::util::rom_detect::{RomDetect, RomKind};
 
 pub mod gpio;
 pub mod spi;
@@ -185,34 +184,15 @@
             // Open the console UART.  We do this first so we get the receiver
             // started and the uart buffering data for us.
             let uart = self.uart("0")?;
-            if fpga_program.bitstream.starts_with(b"__skip__") {
+            let reset_pin = self.gpio_pin(Self::PIN_SRST)?;
+            if fpga_program.skip() {
                 log::info!("Skip loading the __skip__ bitstream.");
                 return Ok(None);
             }
-            if let Some(rom_kind) = &fpga_program.rom_kind {
-                let mut rd = RomDetect::new(
-                    *rom_kind,
-                    &fpga_program.bitstream,
-                    Some(fpga_program.rom_timeout),
-                )?;
-
-                // Send a reset pulse so the ROM will print the FPGA version.
-                let reset_pin = self.gpio_pin(Self::PIN_SRST)?;
-                // Reset is active low, sleep, then drive high.
-                reset_pin.write(false)?;
-                std::thread::sleep(fpga_program.rom_reset_pulse);
-                // Also clear the UART RX buffer for improved robustness.
-                uart.clear_rx_buffer()?;
-                reset_pin.write(true)?;
-
-                // Now read the uart until the ROM prints it's version.
-                if rd.detect(&*uart)? {
-                    log::info!("Already running the correct bitstream.  Skip loading bitstream.");
-                    // If we're already running the right ROM+bitstream,
-                    // then we can skip bootstrap.
-                    return Ok(None);
-                }
+            if fpga_program.check_correct_version(&*uart, &*reset_pin)? {
+                return Ok(None);
             }
+
             // Program the FPGA bitstream.
             log::info!("Programming the FPGA bitstream.");
             let usb = self.device.borrow();
@@ -248,25 +228,7 @@
 }
 
 /// Command for Transport::dispatch().
-pub struct FpgaProgram<'a> {
-    /// The bitstream content to load into the FPGA.
-    pub bitstream: Vec<u8>,
-    /// What type of ROM to expect.
-    pub rom_kind: Option<RomKind>,
-    /// How long of a reset pulse to send to the device.
-    pub rom_reset_pulse: Duration,
-    /// How long to wait for the ROM to print its type and version.
-    pub rom_timeout: Duration,
-    /// A progress function to provide user feedback.
-    /// Will be called with the address and length of each chunk sent to the target device.
-    pub progress: Option<Box<dyn Fn(u32, u32) + 'a>>,
-}
-
-/// Command for Transport::dispatch().
 pub struct SetPll {}
 
 /// Command for Transport::dispatch(). Resets the CW310's SAM3X chip.
 pub struct ResetSam3x {}
-
-/// Command for Transport::dispatch().
-pub struct ClearBitstream {}
diff --git a/sw/host/opentitantool/src/command/clear_bitstream.rs b/sw/host/opentitantool/src/command/clear_bitstream.rs
index 7ed5f53..36b99a3 100644
--- a/sw/host/opentitantool/src/command/clear_bitstream.rs
+++ b/sw/host/opentitantool/src/command/clear_bitstream.rs
@@ -9,7 +9,7 @@
 
 use opentitanlib::app::command::CommandDispatch;
 use opentitanlib::app::TransportWrapper;
-use opentitanlib::transport::cw310;
+use opentitanlib::transport::common::fpga;
 
 /// Clear the bitstream of the FPGA
 #[derive(Debug, StructOpt)]
@@ -21,6 +21,6 @@
         _context: &dyn Any,
         transport: &TransportWrapper,
     ) -> Result<Option<Box<dyn Annotate>>> {
-        transport.dispatch(&cw310::ClearBitstream {})
+        transport.dispatch(&fpga::ClearBitstream)
     }
 }
diff --git a/sw/host/opentitantool/src/command/load_bitstream.rs b/sw/host/opentitantool/src/command/load_bitstream.rs
index d3d991e..5985597 100644
--- a/sw/host/opentitantool/src/command/load_bitstream.rs
+++ b/sw/host/opentitantool/src/command/load_bitstream.rs
@@ -12,7 +12,7 @@
 
 use opentitanlib::app::command::CommandDispatch;
 use opentitanlib::app::{self, TransportWrapper};
-use opentitanlib::transport::cw310;
+use opentitanlib::transport::common::fpga::FpgaProgram;
 use opentitanlib::util::rom_detect::RomKind;
 
 /// Load a bitstream into the FPGA.
@@ -46,7 +46,7 @@
         let pfunc = Box::new(move |_, chunk| {
             progress.inc(chunk as u64);
         });
-        let operation = cw310::FpgaProgram {
+        let operation = FpgaProgram {
             bitstream,
             rom_kind: self.rom_kind,
             rom_reset_pulse: self.rom_reset_pulse,