Skip to content

Commit

Permalink
[opentitantool] Make TPM driver take advantage of new primitives
Browse files Browse the repository at this point in the history
Adapt TPM driver layer to use advanced transport features, if available.

This change speeds up TPM communication between 2x and 4x when
HyperDebug is used to communicate with GSC chips.

(This CL also removes unused code which tried to deal with SPI TPM
transactions on debuggers which do not support bidirectional SPI
transfers.)

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

Change-Id: I16c56d367478c1394825b1be56a3622452af207a
  • Loading branch information
jesultra committed Nov 19, 2024
1 parent 9fcfe3e commit 2d71bed
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 131 deletions.
158 changes: 53 additions & 105 deletions sw/host/opentitanlib/src/tpm/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,43 +190,15 @@ 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>)>,
use_gsc_ready: bool,
}

impl SpiDriver {
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 })
pub fn new(spi: Rc<dyn spi::Target>, use_gsc_ready: bool) -> Result<Self> {
Ok(Self { spi, use_gsc_ready })
}

/// Numerical TPM register address as used in SPI protocol.
Expand Down Expand Up @@ -311,76 +283,59 @@ const TIMEOUT: Duration = Duration::from_millis(500);

impl Driver for SpiDriver {
fn read_register(&self, register: Register, data: &mut [u8]) -> Result<()> {
if !self.spi.supports_bidirectional_transfer()? {
// SPI transport does not support bidirectional transfer. Assume that the TPM will
// send 0x01 on the byte immediately following the fourth and final request byte.
let req = self.compose_header(register, data.len(), true);
let mut buffer = vec![0u8; data.len() + 1];
if !self.spi.supports_tpm_poll()? {
// Fallback on polling TPM status from this Rust code.
return self.do_read_register(register, data);
}
let req = self.compose_header(register, data.len(), true /* is_read */);
if self.use_gsc_ready {
self.spi.run_transaction(&mut [
spi::Transfer::Write(&req),
spi::Transfer::Read(&mut buffer),
])?;
ensure!(buffer[0] & 1 != 0, "TPM did not respond as expected",);
data.clone_from_slice(&buffer[1..]);
return Ok(());
}
let result = self.do_read_register(register, data);
if result.is_ok() {
wait_for_gsc_ready(&self.gsc_ready_pin)?;
spi::Transfer::GscReady,
spi::Transfer::TpmPoll,
spi::Transfer::Read(data),
])
} else {
self.spi.run_transaction(&mut [
spi::Transfer::Write(&req),
spi::Transfer::TpmPoll,
spi::Transfer::Read(data),
])
}
result
}

fn write_register(&self, register: Register, data: &[u8]) -> Result<()> {
if !self.spi.supports_bidirectional_transfer()? {
/*
* SPI transport does not support bidirectional transfer. Assume that the TPM will
* send 0x01 on the byte immediately following the fourth and final request byte.
*/
let req = self.compose_header(register, data.len(), true);
let mut buffer = [0u8; 1];
if !self.spi.supports_tpm_poll()? {
// Fallback on polling TPM status from this Rust code.
return self.do_write_register(register, data);
}
let req = self.compose_header(register, data.len(), false /* is_read */);
if self.use_gsc_ready {
self.spi.run_transaction(&mut [
spi::Transfer::Write(&req),
spi::Transfer::Read(&mut buffer),
spi::Transfer::TpmPoll,
spi::Transfer::Write(data),
])?;
ensure!(buffer[0] & 1 != 0, "TPM did not respond as expected",);
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);
spi::Transfer::GscReady,
])
} else {
self.spi.run_transaction(&mut [
spi::Transfer::Write(&req),
spi::Transfer::TpmPoll,
spi::Transfer::Write(data),
])
}
}
}

/// 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>)>,
use_gsc_ready: bool,
}

impl I2cDriver {
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 })
pub fn new(i2c: Rc<dyn i2c::Bus>, use_gsc_ready: bool) -> Result<Self> {
Ok(Self { i2c, use_gsc_ready })
}

/// Numerical TPM register address as used in Google I2C protocol.
Expand All @@ -395,7 +350,7 @@ impl I2cDriver {
}

fn try_read_register(&self, register: Register, data: &mut [u8]) -> Result<()> {
if self.gsc_ready_pin.is_none() {
if !self.use_gsc_ready {
// Do two I2C transfers in one call, for lowest latency.
self.i2c.run_transaction(
None, /* default addr */
Expand All @@ -405,16 +360,13 @@ impl I2cDriver {
],
)
} 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)],
&mut [
i2c::Transfer::Write(&[Self::addr(register).unwrap()]),
i2c::Transfer::GscReady,
i2c::Transfer::Read(data),
],
)
}
}
Expand Down Expand Up @@ -456,20 +408,16 @@ impl Driver for I2cDriver {
fn write_register(&self, register: Register, data: &[u8]) -> Result<()> {
let mut buffer = vec![Self::addr(register).unwrap()];
buffer.extend_from_slice(data);
self.i2c.run_transaction(
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);
if !self.use_gsc_ready {
self.i2c.run_transaction(
None, /* default addr */
&mut [i2c::Transfer::Write(&buffer)],
)
} else {
self.i2c.run_transaction(
None, /* default addr */
&mut [i2c::Transfer::Write(&buffer), i2c::Transfer::GscReady],
)
}
}
}
14 changes: 6 additions & 8 deletions sw/host/opentitantool/src/command/i2c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,14 +274,12 @@ 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<dyn tpm::Driver> = Box::new(tpm::I2cDriver::new(
context.params.create(transport, "TPM")?,
ready_pin,
)?);
let i2c = context.params.create(transport, "TPM")?;
if let Some(pin) = &self.gsc_ready {
i2c.set_pins(None, None, Some(&transport.gpio_pin(pin)?))?;
}
let tpm_driver: Box<dyn tpm::Driver> =
Box::new(tpm::I2cDriver::new(i2c, self.gsc_ready.is_some())?);
self.command.run(&tpm_driver, transport)
}
}
Expand Down
14 changes: 6 additions & 8 deletions sw/host/opentitantool/src/command/spi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,14 +294,12 @@ 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,
)?);
let spi = context.params.create(transport, "TPM")?;
if let Some(pin) = &self.gsc_ready {
spi.set_pins(None, None, None, None, Some(&transport.gpio_pin(pin)?))?;
}
let tpm_driver: Box<dyn tpm::Driver> =
Box::new(tpm::SpiDriver::new(spi, self.gsc_ready.is_some())?);
self.command.run(&tpm_driver, transport)
}
}
Expand Down
18 changes: 8 additions & 10 deletions sw/host/tpm2_test_server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,19 +84,17 @@ pub fn main() -> anyhow::Result<()> {
let bus: Box<dyn Driver> = match options.bus {
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)?)
if let Some(pin) = &gsc_ready {
spi.set_pins(None, None, None, None, Some(&transport.gpio_pin(pin)?))?;
}
Box::new(SpiDriver::new(spi, gsc_ready.is_some())?)
}
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)?)
if let Some(pin) = &gsc_ready {
i2c.set_pins(None, None, Some(&transport.gpio_pin(pin)?))?;
}
Box::new(I2cDriver::new(i2c, gsc_ready.is_some())?)
}
};
bus.init()?;
Expand Down

0 comments on commit 2d71bed

Please sign in to comment.