diff --git a/sw/host/opentitanlib/src/tpm/driver.rs b/sw/host/opentitanlib/src/tpm/driver.rs index 13745b13c3512..b1d6d70dd4119 100644 --- a/sw/host/opentitanlib/src/tpm/driver.rs +++ b/sw/host/opentitanlib/src/tpm/driver.rs @@ -5,11 +5,13 @@ use anyhow::{bail, ensure, Result}; use clap::ValueEnum; use serde::{Deserialize, Serialize}; +use std::borrow::Borrow; use std::rc::Rc; use std::thread; use std::time::{Duration, Instant}; use thiserror::Error; +use crate::io::gpio; use crate::io::i2c; use crate::io::spi; use crate::tpm::access::TpmAccess; @@ -187,16 +189,43 @@ pub trait Driver { } } +type GpioPinAndMonitoring = (Rc, Rc); + +fn wait_for_gsc_ready(gsc_ready_pin: &Option) -> Result<()> { + let Some((gsc_ready_pin, monitoring)) = gsc_ready_pin else { + return Ok(()); + }; + let start_time = Instant::now(); + while !monitoring + .monitoring_read(&[gsc_ready_pin.borrow()], true)? + .events + .into_iter() + .any(|e| e.edge == gpio::Edge::Falling) + { + if Instant::now().duration_since(start_time) > TIMEOUT { + bail!(TpmError::Timeout) + } + } + Ok(()) +} + /// Implementation of the low level interface via standard SPI protocol. pub struct SpiDriver { spi: Rc, + gsc_ready_pin: Option<(Rc, Rc)>, } impl SpiDriver { pub fn new( spi: Rc, + gsc_ready_pin: Option<(Rc, Rc)>, ) -> Result { - Ok(Self { spi }) + if let Some((gsc_ready_pin, monitoring)) = &gsc_ready_pin { + // Set up monitoring of edges on the GSC ready pin. This will be more efficient than + // starting/stopping the monitoring on each TPM operation. + monitoring.monitoring_start(&[gsc_ready_pin.borrow()])?; + } + Ok(Self { spi, gsc_ready_pin }) } /// Numerical TPM register address as used in SPI protocol. @@ -295,6 +324,9 @@ impl Driver for SpiDriver { return Ok(()); } let result = self.do_read_register(register, data); + if result.is_ok() { + wait_for_gsc_ready(&self.gsc_ready_pin)?; + } result } @@ -315,20 +347,39 @@ impl Driver for SpiDriver { return Ok(()); } let result = self.do_write_register(register, data); + if result.is_ok() { + wait_for_gsc_ready(&self.gsc_ready_pin)?; + } result } } +impl Drop for SpiDriver { + fn drop(&mut self) { + if let Some((gsc_ready_pin, monitoring)) = &self.gsc_ready_pin { + // Stop monitoring of the gsc_ready pin, by reading one final time. + let _ = monitoring.monitoring_read(&[gsc_ready_pin.borrow()], false); + } + } +} + /// Implementation of the low level interface via Google I2C protocol. pub struct I2cDriver { i2c: Rc, + gsc_ready_pin: Option<(Rc, Rc)>, } impl I2cDriver { pub fn new( i2c: Rc, + gsc_ready_pin: Option<(Rc, Rc)>, ) -> Result { - Ok(Self { i2c }) + if let Some((gsc_ready_pin, monitoring)) = &gsc_ready_pin { + // Set up monitoring of edges on the GSC ready pin. This will be more efficient than + // starting/stopping the monitoring on each TPM operation. + monitoring.monitoring_start(&[gsc_ready_pin.borrow()])?; + } + Ok(Self { i2c, gsc_ready_pin }) } /// Numerical TPM register address as used in Google I2C protocol. @@ -343,13 +394,28 @@ impl I2cDriver { } fn try_read_register(&self, register: Register, data: &mut [u8]) -> Result<()> { - self.i2c.run_transaction( - None, /* default addr */ - &mut [ - i2c::Transfer::Write(&[Self::addr(register).unwrap()]), - i2c::Transfer::Read(data), - ], - ) + if self.gsc_ready_pin.is_some() { + // Do two I2C transfers in one call, for lowest latency. + self.i2c.run_transaction( + None, /* default addr */ + &mut [ + i2c::Transfer::Write(&[Self::addr(register).unwrap()]), + i2c::Transfer::Read(data), + ], + ) + } else { + // Since we need to check for the GSC ready signal in between, we have to do one I2C + // transfer at a time, and tolerate the latency of multiple roundtrip. + self.i2c.run_transaction( + None, /* default addr */ + &mut [i2c::Transfer::Write(&[Self::addr(register).unwrap()])], + )?; + wait_for_gsc_ready(&self.gsc_ready_pin)?; + self.i2c.run_transaction( + None, /* default addr */ + &mut [i2c::Transfer::Read(data)], + ) + } } } @@ -393,6 +459,16 @@ impl Driver for I2cDriver { None, /* default addr */ &mut [i2c::Transfer::Write(&buffer)], )?; + wait_for_gsc_ready(&self.gsc_ready_pin)?; Ok(()) } } + +impl Drop for I2cDriver { + fn drop(&mut self) { + if let Some((gsc_ready_pin, monitoring)) = &self.gsc_ready_pin { + // Stop monitoring of the gsc_ready pin, by reading one final time. + let _ = monitoring.monitoring_read(&[gsc_ready_pin.borrow()], false); + } + } +} diff --git a/sw/host/opentitantool/src/command/i2c.rs b/sw/host/opentitantool/src/command/i2c.rs index 675b9022470d5..05bf564481855 100644 --- a/sw/host/opentitantool/src/command/i2c.rs +++ b/sw/host/opentitantool/src/command/i2c.rs @@ -261,6 +261,10 @@ impl CommandDispatch for I2cPrepareRead { pub struct I2cTpm { #[command(subcommand)] command: super::tpm::TpmSubCommand, + + /// Pin used for signalling by Google security chips + #[arg(long)] + gsc_ready: Option, } impl CommandDispatch for I2cTpm { @@ -270,8 +274,13 @@ impl CommandDispatch for I2cTpm { transport: &TransportWrapper, ) -> Result>> { let context = context.downcast_ref::().unwrap(); + let ready_pin = match &self.gsc_ready { + Some(pin) => Some((transport.gpio_pin(pin)?, transport.gpio_monitoring()?)), + None => None, + }; let tpm_driver = Box::new(tpm::I2cDriver::new( context.params.create(transport, "TPM")?, + ready_pin, )?); self.command.run(&tpm_driver, transport) } diff --git a/sw/host/opentitantool/src/command/spi.rs b/sw/host/opentitantool/src/command/spi.rs index 644b4163ec8eb..55437d637b619 100644 --- a/sw/host/opentitantool/src/command/spi.rs +++ b/sw/host/opentitantool/src/command/spi.rs @@ -310,6 +310,10 @@ impl CommandDispatch for SpiProgram { pub struct SpiTpm { #[command(subcommand)] command: super::tpm::TpmSubCommand, + + /// Pin used for signalling by Google security chips + #[arg(long)] + gsc_ready: Option, } impl CommandDispatch for SpiTpm { @@ -319,8 +323,13 @@ impl CommandDispatch for SpiTpm { transport: &TransportWrapper, ) -> Result>> { let context = context.downcast_ref::().unwrap(); + let ready_pin = match &self.gsc_ready { + Some(pin) => Some((transport.gpio_pin(pin)?, transport.gpio_monitoring()?)), + None => None, + }; let tpm_driver: Box = Box::new(tpm::SpiDriver::new( context.params.create(transport, "TPM")?, + ready_pin, )?); self.command.run(&tpm_driver, transport) } diff --git a/sw/host/tests/chip/spi_device_tpm_test/src/main.rs b/sw/host/tests/chip/spi_device_tpm_test/src/main.rs index 7625a20aa788c..00818017f0c35 100644 --- a/sw/host/tests/chip/spi_device_tpm_test/src/main.rs +++ b/sw/host/tests/chip/spi_device_tpm_test/src/main.rs @@ -39,7 +39,7 @@ fn tpm_read_test(opts: &Opts, transport: &TransportWrapper) -> Result<()> { /* Wait sync message. */ let _ = UartConsole::wait_for(&*uart, r"SYNC: Begin TPM Test\r\n", opts.timeout)?; - let tpm = tpm::SpiDriver::new(spi); + let tpm = tpm::SpiDriver::new(spi, None)?; const SIZE: usize = 10; for _ in 0..10 { diff --git a/sw/host/tpm2_test_server/src/main.rs b/sw/host/tpm2_test_server/src/main.rs index 0dc6045fecdb2..bfc4de72a3424 100644 --- a/sw/host/tpm2_test_server/src/main.rs +++ b/sw/host/tpm2_test_server/src/main.rs @@ -19,10 +19,18 @@ pub enum TpmBus { Spi { #[command(flatten)] params: SpiParams, + + /// Pin used for signalling by Google security chips + #[arg(long)] + gsc_ready: Option, }, I2C { #[command(flatten)] params: I2cParams, + + /// Pin used for signalling by Google security chips + #[arg(long)] + gsc_ready: Option, }, } @@ -74,16 +82,26 @@ pub fn main() -> anyhow::Result<()> { let transport = backend::create(&options.backend_opts)?; let bus: Box = match options.bus { - TpmBus::Spi { params } => { + TpmBus::Spi { params, gsc_ready } => { let spi = params.create(&transport, "TPM")?; + let ready_pin = match &gsc_ready { + Some(pin) => Some((transport.gpio_pin(pin)?, transport.gpio_monitoring()?)), + None => None, + }; Box::new(SpiDriver::new( spi, + ready_pin, )?) } - TpmBus::I2C { params } => { + TpmBus::I2C { params, gsc_ready } => { let i2c = params.create(&transport, "TPM")?; + let ready_pin = match &gsc_ready { + Some(pin) => Some((transport.gpio_pin(pin)?, transport.gpio_monitoring()?)), + None => None, + }; Box::new(I2cDriver::new( i2c, + ready_pin, )?) } };