Add bitstream loading to hyperdebug

The hyperdebug board is not capable of loading a bitstream into the
CW310 board.  To accomplish bitstream loading, we add  a `CW310Flavor`
to the hyperdebug backend.  When using the CW310Flavor, we assume
that both the Hyperdebug and CW310 USB interfaces are attached to the
host.  The hyperdebug `load-bitstream` operation is delegated to the
CW310 connection.  All other IO (uarts, spi, etc) use the hyperdebug
connection.

Signed-off-by: Chris Frantz <cfrantz@google.com>
diff --git a/sw/host/opentitanlib/src/backend/mod.rs b/sw/host/opentitanlib/src/backend/mod.rs
index 3b2c2c5..2bf1811 100644
--- a/sw/host/opentitanlib/src/backend/mod.rs
+++ b/sw/host/opentitanlib/src/backend/mod.rs
@@ -10,7 +10,7 @@
 use crate::app::config::process_config_file;
 use crate::app::{TransportWrapper, TransportWrapperBuilder};
 use crate::transport::hyperdebug::c2d2::C2d2Flavor;
-use crate::transport::hyperdebug::StandardFlavor;
+use crate::transport::hyperdebug::CW310Flavor;
 use crate::transport::{EmptyTransport, Transport};
 use crate::util::parse_int::ParseInt;
 
@@ -76,7 +76,7 @@
             Some(Path::new("/__builtin__/opentitan_ultradebug.json")),
         ),
         "hyperdebug" => (
-            hyperdebug::create::<StandardFlavor>(args)?,
+            hyperdebug::create::<CW310Flavor>(args)?,
             Some(Path::new("/__builtin__/hyperdebug_cw310.json")),
         ),
         "hyperdebug_dfu" => (hyperdebug::create_dfu(args)?, None),
diff --git a/sw/host/opentitanlib/src/transport/cw310/mod.rs b/sw/host/opentitanlib/src/transport/cw310/mod.rs
index 1f87b6e..7b3981b 100644
--- a/sw/host/opentitanlib/src/transport/cw310/mod.rs
+++ b/sw/host/opentitanlib/src/transport/cw310/mod.rs
@@ -33,7 +33,7 @@
 }
 
 pub struct CW310 {
-    device: Rc<RefCell<usb::Backend>>,
+    pub(crate) device: Rc<RefCell<usb::Backend>>,
     uart_override: Vec<String>,
     inner: RefCell<Inner>,
 }
diff --git a/sw/host/opentitanlib/src/transport/hyperdebug/mod.rs b/sw/host/opentitanlib/src/transport/hyperdebug/mod.rs
index a0adf2f..524ecc2 100644
--- a/sw/host/opentitanlib/src/transport/hyperdebug/mod.rs
+++ b/sw/host/opentitanlib/src/transport/hyperdebug/mod.rs
@@ -25,7 +25,9 @@
 use crate::io::i2c::Bus;
 use crate::io::spi::Target;
 use crate::io::uart::Uart;
+use crate::transport::common::fpga::{ClearBitstream, FpgaProgram};
 use crate::transport::common::uart::{flock_serial, SerialPortExclusiveLock, SerialPortUart};
+use crate::transport::cw310::CW310;
 use crate::transport::{
     Capabilities, Capability, Transport, TransportError, TransportInterfaceType, UpdateFirmware,
 };
@@ -56,6 +58,12 @@
     fn gpio_pin(inner: &Rc<Inner>, pinname: &str) -> Result<Rc<dyn GpioPin>>;
     fn get_default_usb_vid() -> u16;
     fn get_default_usb_pid() -> u16;
+    fn load_bitstream(_transport: &impl Transport, _fpga_program: &FpgaProgram) -> Result<()> {
+        Err(TransportError::UnsupportedOperation.into())
+    }
+    fn clear_bitstream(_clear: &ClearBitstream) -> Result<()> {
+        Err(TransportError::UnsupportedOperation.into())
+    }
 }
 
 pub const VID_GOOGLE: u16 = 0x18d1;
@@ -578,13 +586,18 @@
                 &update_firmware_action.firmware,
                 &update_firmware_action.progress,
             )
+        } else if let Some(fpga_program) = action.downcast_ref::<FpgaProgram>() {
+            T::load_bitstream(self, fpga_program).map(|_| None)
+        } else if let Some(clear) = action.downcast_ref::<ClearBitstream>() {
+            T::clear_bitstream(clear).map(|_| None)
         } else {
             Err(TransportError::UnsupportedOperation.into())
         }
     }
 }
 
-pub struct StandardFlavor {}
+/// A `StandardFlavor` is a plain Hyperdebug board.
+pub struct StandardFlavor;
 
 impl Flavor for StandardFlavor {
     fn gpio_pin(inner: &Rc<Inner>, pinname: &str) -> Result<Rc<dyn GpioPin>> {
@@ -598,6 +611,61 @@
     }
 }
 
+/// A `CW310Flavor` is a Hyperdebug attached to a CW310 board.  Furthermore,
+/// both the Hyperdebug and CW310 USB interfaces are attached to the host.
+/// Hyperdebug is used for all IO with the CW310 board except for bitstream
+/// programming.
+pub struct CW310Flavor;
+
+impl Flavor for CW310Flavor {
+    fn gpio_pin(inner: &Rc<Inner>, pinname: &str) -> Result<Rc<dyn GpioPin>> {
+        StandardFlavor::gpio_pin(inner, pinname)
+    }
+    fn get_default_usb_vid() -> u16 {
+        StandardFlavor::get_default_usb_vid()
+    }
+    fn get_default_usb_pid() -> u16 {
+        StandardFlavor::get_default_usb_pid()
+    }
+    fn load_bitstream(transport: &impl Transport, fpga_program: &FpgaProgram) -> Result<()> {
+        if fpga_program.skip() {
+            log::info!("Skip loading the __skip__ bitstream.");
+            return Ok(());
+        }
+
+        // First, try to establish a connection to the native CW310 interface
+        // which we will use for bitstream loading.
+        let cw310 = CW310::new(None, None, None, &[])?;
+
+        // The transport does not provide name resolution for the IO interface
+        // names, so: console=UART2 and RESET=CN10_29 on the Hyp+CW310.
+        // Open the console UART.  We do this first so we get the receiver
+        // started and the uart buffering data for us.
+        let uart = transport.uart("UART2")?;
+        let reset_pin = transport.gpio_pin("CN10_29")?;
+        if fpga_program.check_correct_version(&*uart, &*reset_pin)? {
+            return Ok(());
+        }
+
+        // Program the FPGA bitstream.
+        log::info!("Programming the FPGA bitstream.");
+        let usb = cw310.device.borrow();
+        usb.spi1_enable(false)?;
+        usb.fpga_program(
+            &fpga_program.bitstream,
+            fpga_program.progress.as_ref().map(Box::as_ref),
+        )?;
+        Ok(())
+    }
+    fn clear_bitstream(_clear: &ClearBitstream) -> Result<()> {
+        let cw310 = CW310::new(None, None, None, &[])?;
+        let usb = cw310.device.borrow();
+        usb.spi1_enable(false)?;
+        usb.clear_bitstream()?;
+        Ok(())
+    }
+}
+
 lazy_static! {
     pub static ref SPI_REGEX: Regex = Regex::new("^ +([0-9]+) ([^ ]+) ([0-9]+)").unwrap();
 }