From 09d49bd00d6a64525897f0ed7107bc8d13d4fb04 Mon Sep 17 00:00:00 2001 From: "Jes B. Klinke" Date: Tue, 19 Nov 2024 14:13:38 -0800 Subject: [PATCH] [opentitantool] HyperDebug support for advanced TPM primitives Add support in the HyperDebug transport driver for `TpmPoll` and `GscReady`. It will reject the opertations if not supported by HyperDebug firmware. Though that is only going to be the case if the user has failed to issue `transport update-firmware`, or is using this backend driver with some other partially-compatible debugger devices. (Also known as "flavors" of HyperDebug.) Signed-off-by: Jes B. Klinke Change-Id: I154229794bcb88cc7845329c03621eec5c4da4a5 --- .../src/transport/hyperdebug/i2c.rs | 69 ++++++++-- .../src/transport/hyperdebug/mod.rs | 2 + .../src/transport/hyperdebug/spi.rs | 125 +++++++++++++++++- 3 files changed, 181 insertions(+), 15 deletions(-) diff --git a/sw/host/opentitanlib/src/transport/hyperdebug/i2c.rs b/sw/host/opentitanlib/src/transport/hyperdebug/i2c.rs index 8fb155604d764..869669eea9e3e 100644 --- a/sw/host/opentitanlib/src/transport/hyperdebug/i2c.rs +++ b/sw/host/opentitanlib/src/transport/hyperdebug/i2c.rs @@ -9,6 +9,7 @@ use std::rc::Rc; use std::time::Duration; use zerocopy::{AsBytes, FromBytes, FromZeroes}; +use crate::io::gpio::GpioPin; use crate::io::i2c::{self, Bus, DeviceStatus, DeviceTransfer, I2cError, ReadStatus, Transfer}; use crate::transport::hyperdebug::{BulkInterface, Inner}; use crate::transport::{TransportError, TransportInterfaceType}; @@ -59,7 +60,7 @@ struct CmdTransferLong { write_count: u8, read_count: u8, read_count1: u8, - reserved: u8, + flags: u8, data: [u8; USB_MAX_SIZE - 6], } @@ -175,7 +176,13 @@ impl HyperdebugI2cBus { } /// Transmit data for a single I2C operation, using one or more USB packets. - fn transmit_then_receive(&self, addr: u8, wbuf: &[u8], rbuf: &mut [u8]) -> Result<()> { + fn transmit_then_receive( + &self, + addr: u8, + wbuf: &[u8], + rbuf: &mut [u8], + gsc_ready: bool, + ) -> Result<()> { ensure!( rbuf.len() < self.max_read_size, I2cError::InvalidDataLength(rbuf.len()) @@ -185,7 +192,7 @@ impl HyperdebugI2cBus { I2cError::InvalidDataLength(wbuf.len()) ); let encapsulation_header_size = if self.cmsis_encapsulation { 1 } else { 0 }; - let mut index = if rbuf.len() < 128 { + let mut index = if rbuf.len() < 128 && !gsc_ready { // Short format header let mut req = CmdTransferShort { encapsulation_header: Self::CMSIS_DAP_CUSTOM_COMMAND_I2C, @@ -200,15 +207,15 @@ impl HyperdebugI2cBus { self.usb_write_bulk(&req.as_bytes()[1 - encapsulation_header_size..1 + 4 + databytes])?; databytes } else { - // Long format header + // Long format header (wider read_count field and additional flags) let mut req = CmdTransferLong { encapsulation_header: Self::CMSIS_DAP_CUSTOM_COMMAND_I2C, port: self.bus_idx | (((wbuf.len() & 0x0F00) >> 4) as u8), addr, write_count: (wbuf.len() & 0x00FF) as u8, - read_count: (rbuf.len() & 0x007F) as u8, + read_count: (rbuf.len() & 0x007F | 0x0080) as u8, read_count1: (rbuf.len() >> 7) as u8, - reserved: 0, + flags: if gsc_ready { 0x80 } else { 0x00 }, data: [0; USB_MAX_SIZE - 6], }; let databytes = cmp::min(USB_MAX_SIZE - 6 - encapsulation_header_size, wbuf.len()); @@ -336,6 +343,25 @@ impl Bus for HyperdebugI2cBus { .cmd_no_output(&format!("i2c set speed {} {}", &self.bus_idx, max_speed)) } + fn set_pins( + &self, + serial_clock: Option<&Rc>, + serial_data: Option<&Rc>, + gsc_ready: Option<&Rc>, + ) -> Result<()> { + if serial_clock.is_some() || serial_data.is_some() { + bail!(I2cError::InvalidPin); + } + if let Some(pin) = gsc_ready { + self.inner.cmd_no_output(&format!( + "i2c set ready {} {}", + &self.bus_idx, + pin.get_internal_pin_name().ok_or(I2cError::InvalidPin)? + ))?; + } + Ok(()) + } + fn set_default_address(&self, addr: u8) -> Result<()> { self.default_addr.set(Some(addr)); Ok(()) @@ -347,6 +373,22 @@ impl Bus for HyperdebugI2cBus { .ok_or(I2cError::MissingAddress)?; while !transaction.is_empty() { match transaction { + [Transfer::Write(wbuf), Transfer::GscReady, Transfer::Read(rbuf), ..] => { + // Hyperdebug can do I2C write followed by I2C read as a single USB + // request/reply. Take advantage of that by detecting pairs of + // Transfer::Write followed by Transfer::Read. + ensure!( + wbuf.len() <= self.max_write_size, + I2cError::InvalidDataLength(wbuf.len()) + ); + ensure!( + rbuf.len() <= self.max_read_size, + I2cError::InvalidDataLength(rbuf.len()) + ); + self.transmit_then_receive(addr, wbuf, rbuf, true)?; + // Skip three steps ahead, as three items were processed. + transaction = &mut transaction[3..]; + } [Transfer::Write(wbuf), Transfer::Read(rbuf), ..] => { // Hyperdebug can do I2C write followed by I2C read as a single USB // request/reply. Take advantage of that by detecting pairs of @@ -359,7 +401,16 @@ impl Bus for HyperdebugI2cBus { rbuf.len() <= self.max_read_size, I2cError::InvalidDataLength(rbuf.len()) ); - self.transmit_then_receive(addr, wbuf, rbuf)?; + self.transmit_then_receive(addr, wbuf, rbuf, false)?; + // Skip two steps ahead, as two items were processed. + transaction = &mut transaction[2..]; + } + [Transfer::Write(wbuf), Transfer::GscReady, ..] => { + ensure!( + wbuf.len() <= self.max_write_size, + I2cError::InvalidDataLength(wbuf.len()) + ); + self.transmit_then_receive(addr, wbuf, &mut [], true)?; // Skip two steps ahead, as two items were processed. transaction = &mut transaction[2..]; } @@ -368,7 +419,7 @@ impl Bus for HyperdebugI2cBus { wbuf.len() <= self.max_write_size, I2cError::InvalidDataLength(wbuf.len()) ); - self.transmit_then_receive(addr, wbuf, &mut [])?; + self.transmit_then_receive(addr, wbuf, &mut [], false)?; transaction = &mut transaction[1..]; } [Transfer::Read(rbuf), ..] => { @@ -376,7 +427,7 @@ impl Bus for HyperdebugI2cBus { rbuf.len() <= self.max_read_size, I2cError::InvalidDataLength(rbuf.len()) ); - self.transmit_then_receive(addr, &[], rbuf)?; + self.transmit_then_receive(addr, &[], rbuf, false)?; transaction = &mut transaction[1..]; } [] => (), diff --git a/sw/host/opentitanlib/src/transport/hyperdebug/mod.rs b/sw/host/opentitanlib/src/transport/hyperdebug/mod.rs index efafa964485ba..3f2d398f92254 100644 --- a/sw/host/opentitanlib/src/transport/hyperdebug/mod.rs +++ b/sw/host/opentitanlib/src/transport/hyperdebug/mod.rs @@ -138,6 +138,7 @@ impl Hyperdebug { const GOOGLE_CAP_GPIO_MONITORING: u16 = 0x0004; const GOOGLE_CAP_GPIO_BITBANGING: u16 = 0x0008; const GOOGLE_CAP_UART_QUEUE_CLEAR: u16 = 0x0010; + const GOOGLE_CAP_TPM_POLL: u16 = 0x0020; /// Establish connection with a particular HyperDebug. pub fn open( @@ -669,6 +670,7 @@ impl Transport for Hyperdebug { &self.spi_interface, enable_cmd, idx, + self.get_cmsis_google_capabilities()? & Self::GOOGLE_CAP_TPM_POLL != 0, )?); self.cached_io_interfaces .spis diff --git a/sw/host/opentitanlib/src/transport/hyperdebug/spi.rs b/sw/host/opentitanlib/src/transport/hyperdebug/spi.rs index 3c09fcb388466..af394f60c0180 100644 --- a/sw/host/opentitanlib/src/transport/hyperdebug/spi.rs +++ b/sw/host/opentitanlib/src/transport/hyperdebug/spi.rs @@ -24,6 +24,7 @@ pub struct HyperdebugSpiTarget { target_enable_cmd: u8, target_idx: u8, feature_bitmap: u16, + supports_tpm_poll: bool, max_sizes: MaxSizes, cs_asserted_count: Cell, } @@ -73,6 +74,8 @@ const EEPROM_FLAGS_WIDTH_4WIRE: u32 = 0x00000100; const EEPROM_FLAGS_WIDTH_8WIRE: u32 = 0x00000180; const EEPROM_FLAGS_DTR: u32 = 0x00000200; const EEPROM_FLAGS_DUMMY_CYCLES_POS: u8 = 10; +const EEPROM_FLAGS_GSC_READY: u32 = 0x04000000; +const EEPROM_FLAGS_TPM: u32 = 0x08000000; const EEPROM_FLAGS_WRITE_ENABLE: u32 = 0x10000000; const EEPROM_FLAGS_POLL_BUSY: u32 = 0x20000000; const EEPROM_FLAGS_DOUBLE_BUFFER: u32 = 0x40000000; @@ -251,6 +254,7 @@ impl HyperdebugSpiTarget { spi_interface: &BulkInterface, enable_cmd: u8, idx: u8, + supports_tpm_poll: bool, ) -> Result { let mut usb_handle = inner.usb_device.borrow_mut(); @@ -293,6 +297,7 @@ impl HyperdebugSpiTarget { target_enable_cmd: enable_cmd, target_idx: idx, feature_bitmap: resp.feature_bitmap, + supports_tpm_poll, max_sizes: MaxSizes { read: resp.max_read_chunk as usize, write: resp.max_write_chunk as usize, @@ -337,6 +342,52 @@ impl HyperdebugSpiTarget { Ok(()) } + /// Preform TPM transactions, that is, send four bytes of header/address, then repeatedly poll + /// for ready statys from the device, before sending/receiving the data bytes. Optionally + /// wait for falling edge on "GSC ready" pin, at appropriate time during tracsation. + fn tpm_transmit(&self, wbuf: &[u8], rbuf_len: usize, await_gsc_ready: bool) -> Result<()> { + const TPM_HEADER_SIZE: usize = 4; + let mut req = CmdEepromTransferStart::new(); + if rbuf_len == 0 { + req.flags |= EEPROM_FLAGS_WRITE; + req.count = (wbuf.len() - TPM_HEADER_SIZE) as u16; + ensure!( + wbuf.len() > TPM_HEADER_SIZE, + SpiError::InvalidDataLength(wbuf.len()) + ); + } else { + req.count = rbuf_len as u16; + ensure!( + wbuf.len() == TPM_HEADER_SIZE, + SpiError::InvalidDataLength(wbuf.len()) + ); + } + + req.flags |= (TPM_HEADER_SIZE as u32) << EEPROM_FLAGS_ADDR_LEN_POS; + req.flags |= EEPROM_FLAGS_TPM; + if await_gsc_ready { + req.flags |= EEPROM_FLAGS_GSC_READY; + } + + let data_start_offset = 0; + // Optional write data bytes + let databytes = std::cmp::min(USB_MAX_SIZE - 8 - data_start_offset, wbuf.len()); + req.data[data_start_offset..data_start_offset + databytes] + .clone_from_slice(&wbuf[0..databytes]); + self.usb_write_bulk(&req.as_bytes()[0..8 + data_start_offset + databytes])?; + let mut index = databytes; + + while index < wbuf.len() { + let mut req = CmdTransferContinue::new(); + req.data_index = index as u16; + let databytes = std::cmp::min(USB_MAX_SIZE - 4, wbuf.len() - index); + req.data[0..databytes].clone_from_slice(&wbuf[index..index + databytes]); + self.usb_write_bulk(&req.as_bytes()[0..4 + databytes])?; + index += databytes; + } + Ok(()) + } + /// Receive data for a single SPI operation, using one or more USB packets. fn receive(&self, rbuf: &mut [u8]) -> Result<()> { let mut resp = RspTransferStart::new(); @@ -685,7 +736,7 @@ impl Target for HyperdebugSpiTarget { } fn supports_tpm_poll(&self) -> Result { - Ok(false) + Ok(self.supports_tpm_poll) } fn set_pins( @@ -696,11 +747,7 @@ impl Target for HyperdebugSpiTarget { chip_select: Option<&Rc>, gsc_ready: Option<&Rc>, ) -> Result<()> { - if serial_clock.is_some() - || host_out_device_in.is_some() - || host_in_device_out.is_some() - || gsc_ready.is_some() - { + if serial_clock.is_some() || host_out_device_in.is_some() || host_in_device_out.is_some() { bail!(SpiError::InvalidPin); } if let Some(pin) = chip_select { @@ -710,6 +757,13 @@ impl Target for HyperdebugSpiTarget { pin.get_internal_pin_name().ok_or(SpiError::InvalidPin)? ))?; } + if let Some(pin) = gsc_ready { + self.inner.cmd_no_output(&format!( + "spi set ready {} {}", + &self.target_idx, + pin.get_internal_pin_name().ok_or(SpiError::InvalidPin)? + ))?; + } Ok(()) } @@ -755,6 +809,36 @@ impl Target for HyperdebugSpiTarget { self.receive(rbuf)?; return Ok(()); } + [Transfer::Write(wbuf), Transfer::GscReady, Transfer::TpmPoll, Transfer::Read(rbuf)] => { + // Hyperdebug can do SPI TPM transaction as a single USB + // request/reply. + ensure!( + wbuf.len() <= self.max_sizes.write, + SpiError::InvalidDataLength(wbuf.len()) + ); + ensure!( + rbuf.len() <= self.max_sizes.read, + SpiError::InvalidDataLength(rbuf.len()) + ); + self.tpm_transmit(wbuf, rbuf.len(), true)?; + self.receive(rbuf)?; + return Ok(()); + } + [Transfer::Write(wbuf), Transfer::TpmPoll, Transfer::Read(rbuf)] => { + // Hyperdebug can do SPI TPM transaction as a single USB + // request/reply. + ensure!( + wbuf.len() <= self.max_sizes.write, + SpiError::InvalidDataLength(wbuf.len()) + ); + ensure!( + rbuf.len() <= self.max_sizes.read, + SpiError::InvalidDataLength(rbuf.len()) + ); + self.tpm_transmit(wbuf, rbuf.len(), false)?; + self.receive(rbuf)?; + return Ok(()); + } [Transfer::Write(wbuf)] => { ensure!( wbuf.len() <= self.max_sizes.write, @@ -774,6 +858,35 @@ impl Target for HyperdebugSpiTarget { return Ok(()); } } + [Transfer::Write(wbuf1), Transfer::TpmPoll, Transfer::Write(wbuf2), Transfer::GscReady] => + { + // Hyperdebug can do SPI TPM transaction as a single USB + // request/reply. + ensure!( + wbuf1.len() + wbuf2.len() <= self.max_sizes.write, + SpiError::InvalidDataLength(wbuf1.len() + wbuf2.len()) + ); + let mut combined_buf = vec![0u8; wbuf1.len() + wbuf2.len()]; + combined_buf[..wbuf1.len()].clone_from_slice(wbuf1); + combined_buf[wbuf1.len()..].clone_from_slice(wbuf2); + self.tpm_transmit(&combined_buf, 0, true)?; + self.receive(&mut [])?; + return Ok(()); + } + [Transfer::Write(wbuf1), Transfer::TpmPoll, Transfer::Write(wbuf2)] => { + // Hyperdebug can do SPI TPM transaction as a single USB + // request/reply. + ensure!( + wbuf1.len() + wbuf2.len() <= self.max_sizes.write, + SpiError::InvalidDataLength(wbuf1.len() + wbuf2.len()) + ); + let mut combined_buf = vec![0u8; wbuf1.len() + wbuf2.len()]; + combined_buf[..wbuf1.len()].clone_from_slice(wbuf1); + combined_buf[wbuf1.len()..].clone_from_slice(wbuf2); + self.tpm_transmit(&combined_buf, 0, false)?; + self.receive(&mut [])?; + return Ok(()); + } [Transfer::Read(rbuf)] => { ensure!( rbuf.len() <= self.max_sizes.read,