Skip to content

Commit

Permalink
[opentitantool] HyperDebug support for advanced TPM primitives
Browse files Browse the repository at this point in the history
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 <[email protected]>
Change-Id: I154229794bcb88cc7845329c03621eec5c4da4a5
  • Loading branch information
jesultra committed Dec 6, 2024
1 parent 3279b09 commit 09d49bd
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 15 deletions.
69 changes: 60 additions & 9 deletions sw/host/opentitanlib/src/transport/hyperdebug/i2c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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],
}

Expand Down Expand Up @@ -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())
Expand All @@ -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,
Expand All @@ -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());
Expand Down Expand Up @@ -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<dyn GpioPin>>,
serial_data: Option<&Rc<dyn GpioPin>>,
gsc_ready: Option<&Rc<dyn GpioPin>>,
) -> 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(())
Expand All @@ -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
Expand All @@ -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..];
}
Expand All @@ -368,15 +419,15 @@ 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), ..] => {
ensure!(
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..];
}
[] => (),
Expand Down
2 changes: 2 additions & 0 deletions sw/host/opentitanlib/src/transport/hyperdebug/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ impl<T: Flavor> Hyperdebug<T> {
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(
Expand Down Expand Up @@ -669,6 +670,7 @@ impl<T: Flavor> Transport for Hyperdebug<T> {
&self.spi_interface,
enable_cmd,
idx,
self.get_cmsis_google_capabilities()? & Self::GOOGLE_CAP_TPM_POLL != 0,
)?);
self.cached_io_interfaces
.spis
Expand Down
125 changes: 119 additions & 6 deletions sw/host/opentitanlib/src/transport/hyperdebug/spi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u32>,
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -251,6 +254,7 @@ impl HyperdebugSpiTarget {
spi_interface: &BulkInterface,
enable_cmd: u8,
idx: u8,
supports_tpm_poll: bool,
) -> Result<Self> {
let mut usb_handle = inner.usb_device.borrow_mut();

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -685,7 +736,7 @@ impl Target for HyperdebugSpiTarget {
}

fn supports_tpm_poll(&self) -> Result<bool> {
Ok(false)
Ok(self.supports_tpm_poll)
}

fn set_pins(
Expand All @@ -696,11 +747,7 @@ impl Target for HyperdebugSpiTarget {
chip_select: Option<&Rc<dyn GpioPin>>,
gsc_ready: Option<&Rc<dyn GpioPin>>,
) -> 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 {
Expand All @@ -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(())
}

Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down

0 comments on commit 09d49bd

Please sign in to comment.