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.

Signed-off-by: Jes B. Klinke <[email protected]>

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

type GpioPinAndMonitoring = (Rc<dyn gpio::GpioPin>, Rc<dyn gpio::GpioMonitoring>);

fn wait_for_gsc_ready(gsc_ready_pin: &Option<GpioPinAndMonitoring>) -> 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<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>,
gsc_ready_pin: Option<(Rc<dyn gpio::GpioPin>, Rc<dyn gpio::GpioMonitoring>)>,
) -> Result<Self> {
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.
Expand Down Expand Up @@ -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
}

Expand All @@ -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<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>,
gsc_ready_pin: Option<(Rc<dyn gpio::GpioPin>, Rc<dyn gpio::GpioMonitoring>)>,
) -> Result<Self> {
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.
Expand All @@ -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)],
)
}
}
}

Expand Down Expand Up @@ -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);
}
}
}
9 changes: 9 additions & 0 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,8 +274,13 @@ impl CommandDispatch for I2cTpm {
transport: &TransportWrapper,
) -> Result<Option<Box<dyn Annotate>>> {
let context = context.downcast_ref::<I2cCommand>().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)
}
Expand Down
9 changes: 9 additions & 0 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,8 +323,13 @@ impl CommandDispatch for SpiTpm {
transport: &TransportWrapper,
) -> Result<Option<Box<dyn Annotate>>> {
let context = context.downcast_ref::<SpiCommand>().unwrap();
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")?,
ready_pin,
)?);
self.command.run(&tpm_driver, transport)
}
Expand Down
2 changes: 1 addition & 1 deletion sw/host/tests/chip/spi_device_tpm_test/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
22 changes: 20 additions & 2 deletions sw/host/tpm2_test_server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
},
I2C {
#[command(flatten)]
params: I2cParams,

/// Pin used for signalling by Google security chips
#[arg(long)]
gsc_ready: Option<String>,
},
}

Expand Down Expand Up @@ -74,16 +82,26 @@ pub fn main() -> anyhow::Result<()> {

let transport = backend::create(&options.backend_opts)?;
let bus: Box<dyn Driver> = 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,
)?)
}
};
Expand Down

0 comments on commit c540024

Please sign in to comment.