Skip to content

Commit

Permalink
[opentitantool] Add support for Google TPM "ready signal"
Browse files Browse the repository at this point in the history
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 <[email protected]>

Change-Id: I1fa442095cb85c3073500f66fdb38c5547b038d9
  • Loading branch information
jesultra committed Feb 11, 2024
1 parent b98f8e2 commit ad2cc95
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 50 deletions.
145 changes: 120 additions & 25 deletions sw/host/opentitanlib/src/tpm/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -187,14 +190,43 @@ pub trait Driver {
}
}

fn wait_for_gsc_ready(
gsc_ready_pin: &Option<(Rc<dyn gpio::GpioPin>, Rc<dyn gpio::GpioMonitoring>)>,
) -> 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<dyn spi::Target>,
gsc_ready_pin: Option<(Rc<dyn gpio::GpioPin>, Rc<dyn gpio::GpioMonitoring>)>,
}

impl SpiDriver {
pub fn new(spi: Rc<dyn spi::Target>) -> Self {
Self { spi }
pub fn new(
spi: Rc<dyn spi::Target>,
gsc_ready_pin: Option<(Rc<dyn gpio::GpioPin>, Rc<dyn gpio::GpioMonitoring>)>,
) -> Result<Self> {
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.
Expand Down Expand Up @@ -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;
Expand All @@ -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<()> {
Expand All @@ -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<()> {
Expand All @@ -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<dyn i2c::Bus>,
gsc_ready_pin: Option<(Rc<dyn gpio::GpioPin>, Rc<dyn gpio::GpioMonitoring>)>,
}

impl I2cDriver {
pub fn new(i2c: Rc<dyn i2c::Bus>) -> Self {
Self { i2c }
pub fn new(
i2c: Rc<dyn i2c::Bus>,
gsc_ready_pin: Option<(Rc<dyn gpio::GpioPin>, Rc<dyn gpio::GpioMonitoring>)>,
) -> Result<Self> {
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.
Expand All @@ -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 {
Expand All @@ -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: {}",
Expand Down Expand Up @@ -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);
}
}
}
16 changes: 13 additions & 3 deletions sw/host/opentitantool/src/command/i2c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
}

impl CommandDispatch for I2cTpm {
Expand All @@ -270,9 +274,15 @@ impl CommandDispatch for I2cTpm {
transport: &TransportWrapper,
) -> Result<Option<Box<dyn Annotate>>> {
let context = context.downcast_ref::<I2cCommand>().unwrap();
let tpm_driver = tpm::I2cDriver::new(context.params.create(transport, "TPM")?);
let bus: Box<dyn tpm::Driver> = 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)
}
}

Expand Down
15 changes: 12 additions & 3 deletions sw/host/opentitantool/src/command/spi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
}

impl CommandDispatch for SpiTpm {
Expand All @@ -319,10 +323,15 @@ impl CommandDispatch for SpiTpm {
transport: &TransportWrapper,
) -> Result<Option<Box<dyn Annotate>>> {
let context = context.downcast_ref::<SpiCommand>().unwrap();
let bus: Box<dyn tpm::Driver> = 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<dyn tpm::Driver> = Box::new(tpm::SpiDriver::new(
context.params.create(transport, "TPM")?,
));
self.command.run(&bus, transport)
ready_pin,
)?);
self.command.run(&tpm_driver, transport)
}
}

Expand Down
19 changes: 11 additions & 8 deletions sw/host/tpm2_test_server/src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
Expand Down
Loading

0 comments on commit ad2cc95

Please sign in to comment.