From ad2cc95df9429a4be2cc4d79ccf92d466b8b7ba2 Mon Sep 17 00:00:00 2001 From: "Jes B. Klinke" Date: Thu, 8 Feb 2024 09:02:49 -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. Also, code a fixed timeout of 500ms, rather than hard-coded ten retries, which could vary depending on debugger latency. Signed-off-by: Jes B. Klinke Change-Id: I1fa442095cb85c3073500f66fdb38c5547b038d9 --- sw/host/opentitanlib/src/tpm/driver.rs | 145 ++++++++++++++++++---- sw/host/opentitantool/src/command/i2c.rs | 16 ++- sw/host/opentitantool/src/command/spi.rs | 15 ++- sw/host/tpm2_test_server/src/interface.rs | 19 +-- sw/host/tpm2_test_server/src/main.rs | 44 +++++-- 5 files changed, 189 insertions(+), 50 deletions(-) diff --git a/sw/host/opentitanlib/src/tpm/driver.rs b/sw/host/opentitanlib/src/tpm/driver.rs index a1601c840a88d8..fcb00936bcd7cd 100644 --- a/sw/host/opentitanlib/src/tpm/driver.rs +++ b/sw/host/opentitanlib/src/tpm/driver.rs @@ -5,11 +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}; +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; @@ -187,14 +190,43 @@ pub trait Driver { } } +fn wait_for_gsc_ready( + gsc_ready_pin: &Option<(Rc, Rc)>, +) -> 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) + } + } + return 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) -> Self { - Self { spi } + pub fn new( + spi: Rc, + gsc_ready_pin: Option<(Rc, Rc)>, + ) -> Result { + 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. @@ -233,20 +265,35 @@ impl SpiDriver { self.spi .run_transaction(&mut [spi::Transfer::Both(&req, &mut buffer)])?; if buffer[3] & 1 == 0 { - let mut retries = 10; + let start_time = SystemTime::now(); while { self.spi .run_transaction(&mut [spi::Transfer::Read(&mut buffer[0..1])])?; buffer[0] & 1 == 0 } { - retries -= 1; - if retries == 0 { + if SystemTime::now().duration_since(start_time)?.cmp(&TIMEOUT) == Ordering::Greater + { bail!(TpmError::Timeout) } } } Ok(()) } + + fn do_read_register(&self, register: Register, data: &mut [u8]) -> Result<()> { + let _cs_asserted = Rc::clone(&self.spi).assert_cs()?; // Deasserts when going out of scope. + self.write_header(register, data.len(), true)?; + self.spi.run_transaction(&mut [spi::Transfer::Read(data)])?; + Ok(()) + } + + fn do_write_register(&self, register: Register, data: &[u8]) -> Result<()> { + let _cs_asserted = Rc::clone(&self.spi).assert_cs()?; // Deasserts when going out of scope. + self.write_header(register, data.len(), false)?; + self.spi + .run_transaction(&mut [spi::Transfer::Write(data)])?; + Ok(()) + } } const SPI_TPM_READ: u32 = 0xC0000000; @@ -257,6 +304,7 @@ const SPI_TPM_ADDRESS_OFFSET: u32 = 0x00D40000; const MAX_TRANSACTION_SIZE: usize = 32; const RESPONSE_HEADER_SIZE: usize = 6; const MAX_RESPONSE_SIZE: usize = 4096; +const TIMEOUT: Duration = Duration::from_millis(500); impl Driver for SpiDriver { fn read_register(&self, register: Register, data: &mut [u8]) -> Result<()> { @@ -273,10 +321,11 @@ impl Driver for SpiDriver { data.clone_from_slice(&buffer[1..]); return Ok(()); } - let _cs_asserted = Rc::clone(&self.spi).assert_cs()?; // Deasserts when going out of scope. - self.write_header(register, data.len(), true)?; - self.spi.run_transaction(&mut [spi::Transfer::Read(data)])?; - Ok(()) + let result = self.do_read_register(register, data); + if result.is_ok() { + wait_for_gsc_ready(&self.gsc_ready_pin)?; + } + result } fn write_register(&self, register: Register, data: &[u8]) -> Result<()> { @@ -295,22 +344,40 @@ impl Driver for SpiDriver { ensure!(buffer[0] & 1 != 0, "TPM did not respond as expected",); return Ok(()); } - let _cs_asserted = Rc::clone(&self.spi).assert_cs()?; // Deasserts when going out of scope. - self.write_header(register, data.len(), false)?; - self.spi - .run_transaction(&mut [spi::Transfer::Write(data)])?; - 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) -> Self { - Self { i2c } + pub fn new( + i2c: Rc, + gsc_ready_pin: Option<(Rc, Rc)>, + ) -> Result { + 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. @@ -323,6 +390,31 @@ impl I2cDriver { _ => None, } } + + fn try_read_register(&self, register: Register, data: &mut [u8]) -> Result<()> { + if let Some(_) = &self.gsc_ready_pin { + // 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)], + ) + } + } } impl Driver for I2cDriver { @@ -332,14 +424,7 @@ impl Driver for I2cDriver { // Retry in case the I2C bus wasn't ready. let res = loop { count += 1; - let res = self.i2c.run_transaction( - None, /* default addr */ - &mut [ - i2c::Transfer::Write(&[Self::addr(register).unwrap()]), - i2c::Transfer::Read(data), - ], - ); - match res { + match self.try_read_register(register, data) { Err(e) => { log::trace!( "Register 0x{:X} access error: {}", @@ -372,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 87e28cc842b379..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,9 +274,15 @@ impl CommandDispatch for I2cTpm { transport: &TransportWrapper, ) -> Result>> { let context = context.downcast_ref::().unwrap(); - let tpm_driver = tpm::I2cDriver::new(context.params.create(transport, "TPM")?); - let bus: Box = Box::new(tpm_driver); - self.command.run(&bus, transport) + 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 c904bee2be212f..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,10 +323,15 @@ impl CommandDispatch for SpiTpm { transport: &TransportWrapper, ) -> Result>> { let context = context.downcast_ref::().unwrap(); - let bus: Box = Box::new(tpm::SpiDriver::new( + 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")?, - )); - self.command.run(&bus, transport) + ready_pin, + )?); + self.command.run(&tpm_driver, transport) } } diff --git a/sw/host/tpm2_test_server/src/interface.rs b/sw/host/tpm2_test_server/src/interface.rs index 0348ef060fefd2..09f238a6d0cb74 100644 --- a/sw/host/tpm2_test_server/src/interface.rs +++ b/sw/host/tpm2_test_server/src/interface.rs @@ -122,14 +122,17 @@ fn handle_send(stream: &mut TcpStream, tpm: &dyn Driver) -> Result<()> { } log::debug!("TPM cmd {:02x?}", cmd); - if let Ok(res) = tpm.execute_command(&cmd) { - stream.write_all(&(res.len() as u32).to_be_bytes())?; - stream.write_all(&res)?; - stream.write_all(&[0u8; 4])?; - } else { - log::error!("Command fail."); - stream.write_all(&[0u8; 4])?; - stream.write_all(&[0u8; 4])?; + match tpm.execute_command(&cmd) { + Ok(res) => { + stream.write_all(&(res.len() as u32).to_be_bytes())?; + stream.write_all(&res)?; + stream.write_all(&[0u8; 4])?; + } + Err(e) => { + log::error!("Command fail: {}", e); + stream.write_all(&[0u8; 4])?; + stream.write_all(&[0u8; 4])?; + } } Ok(()) diff --git a/sw/host/tpm2_test_server/src/main.rs b/sw/host/tpm2_test_server/src/main.rs index 0e431ca71333f4..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, }, } @@ -49,7 +57,7 @@ struct Opts { const CMD_TOKEN: Token = Token(0); const PLATFORM_TOKEN: Token = Token(1); -pub fn main() -> std::io::Result<()> { +pub fn main() -> anyhow::Result<()> { let options = Opts::parse(); env_logger::Builder::from_default_env() .filter(None, options.logging) @@ -72,18 +80,32 @@ pub fn main() -> std::io::Result<()> { poll.registry() .register(&mut cmd_stream, CMD_TOKEN, Interest::READABLE)?; - let transport = backend::create(&options.backend_opts).unwrap(); + let transport = backend::create(&options.backend_opts)?; let bus: Box = match options.bus { - TpmBus::Spi { params } => { - let spi = params.create(&transport, "TPM").unwrap(); - Box::new(SpiDriver::new(spi)) + 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 } => { - let i2c = params.create(&transport, "TPM").unwrap(); - Box::new(I2cDriver::new(i2c)) + 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, + )?) } }; - bus.init().unwrap(); + bus.init()?; loop { poll.poll(&mut events, None)?; @@ -91,12 +113,12 @@ pub fn main() -> std::io::Result<()> { for event in events.iter() { match event.token() { CMD_TOKEN => { - if serve_command(&mut cmd_stream, &*bus).unwrap() { + if serve_command(&mut cmd_stream, &*bus)? { return Ok(()); } } PLATFORM_TOKEN => { - if serve_command(&mut platform_stream, &*bus).unwrap() { + if serve_command(&mut platform_stream, &*bus)? { return Ok(()); } }