Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[opentitantool] Add support for Google TPM "ready signal" #21289

Merged
merged 2 commits into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 121 additions & 24 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,14 +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>) -> 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 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 @@ -232,21 +263,39 @@ impl SpiDriver {
let req = self.compose_header(register, len, is_read);
self.spi
.run_transaction(&mut [spi::Transfer::Both(&req, &mut buffer)])?;
// The TPM has a chance to indicate that it is ready to produce the response in the very
// next byte. As the fourth and final byte of the header is being sent, if the TPM sends
// back 0x01 on the other data line, data will come next.
if buffer[3] & 1 == 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe leave some comments and/or create consts that explain what is happening with buffer here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

let mut retries = 10;
// The TPM was not immediately ready, keep polling, until we receive a byte of 0x01.
let start_time = Instant::now();
while {
self.spi
.run_transaction(&mut [spi::Transfer::Read(&mut buffer[0..1])])?;
buffer[0] & 1 == 0
} {
retries -= 1;
if retries == 0 {
if Instant::now().duration_since(start_time) > TIMEOUT {
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 +306,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 +323,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 +346,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 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 @@ -323,6 +392,31 @@ impl I2cDriver {
_ => None,
}
}

fn try_read_register(&self, register: Register, data: &mut [u8]) -> Result<()> {
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)],
)
}
}
}

impl Driver for I2cDriver {
Expand All @@ -332,14 +426,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 +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);
}
}
}
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
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
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
Loading