From 85d7378a3934beaf1b95375bbf38a7be499e890b Mon Sep 17 00:00:00 2001 From: "Jes B. Klinke" Date: Sat, 10 Feb 2024 22:30:13 -0800 Subject: [PATCH] [opentitantool] Add support for Google TPM "ready signal" Google security chips deviate from the TPM standard. Because the chips are not able to meet the requirements for response/processing time, they employ an additional "ready" signal, generated by the GSC to indicate when it has finished processing the most recent request, and is ready to receive a new request. This CL adds an optional flag `--gsc_ready` to `opentitantool {spi,i2c} tpm` and to `tpm2_test_server`. Omitting this flag will result in `opentitantool` use the standard TPM protocol, specifying the flag will make `opentitantool` wait for the Google-specific ready signal at each transaction. Signed-off-by: Jes B. Klinke Change-Id: I1fa442095cb85c3073500f66fdb38c5547b038d9 --- sw/host/opentitanlib/src/tpm/driver.rs | 94 +++++++++++++++++-- sw/host/opentitantool/src/command/i2c.rs | 9 ++ sw/host/opentitantool/src/command/spi.rs | 9 ++ .../chip/spi_device_tpm_test/src/main.rs | 2 +- sw/host/tpm2_test_server/src/main.rs | 22 ++++- 5 files changed, 124 insertions(+), 12 deletions(-) diff --git a/sw/host/opentitanlib/src/tpm/driver.rs b/sw/host/opentitanlib/src/tpm/driver.rs index 7767d1ba3f9f05..c627e9cc1cc0f1 100644 --- a/sw/host/opentitanlib/src/tpm/driver.rs +++ b/sw/host/opentitanlib/src/tpm/driver.rs @@ -5,12 +5,14 @@ use anyhow::{bail, ensure, Result}; use clap::ValueEnum; use serde::{Deserialize, Serialize}; +use std::borrow::Borrow; use std::cmp::Ordering; use std::rc::Rc; use std::thread; use std::time::{Duration, Instant, SystemTime}; use thiserror::Error; +use crate::io::gpio; use crate::io::i2c; use crate::io::spi; use crate::tpm::access::TpmAccess; @@ -188,16 +190,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 = SystemTime::now(); + while !monitoring + .monitoring_read(&[gsc_ready_pin.borrow()], true)? + .events + .into_iter() + .any(|e| e.edge == gpio::Edge::Falling) + { + if SystemTime::now().duration_since(start_time)?.cmp(&TIMEOUT) == Ordering::Greater { + 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 moniroting 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. @@ -293,6 +322,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 } @@ -313,20 +345,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 moniroting 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. @@ -341,13 +392,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)], + ) + } } } @@ -391,6 +457,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 675b9022470d53..05bf5644818557 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 644b4163ec8eb8..55437d637b6190 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 7625a20aa788c2..00818017f0c35d 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 0dc6045fecdb29..bfc4de72a34249 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, )?) } };