diff --git a/sw/host/opentitanlib/src/transport/hyperdebug/i2c.rs b/sw/host/opentitanlib/src/transport/hyperdebug/i2c.rs index 8fb155604d7645..4c47a613fa0553 100644 --- a/sw/host/opentitanlib/src/transport/hyperdebug/i2c.rs +++ b/sw/host/opentitanlib/src/transport/hyperdebug/i2c.rs @@ -9,6 +9,7 @@ use std::rc::Rc; use std::time::Duration; use zerocopy::{AsBytes, FromBytes, FromZeroes}; +use crate::io::gpio::GpioPin; use crate::io::i2c::{self, Bus, DeviceStatus, DeviceTransfer, I2cError, ReadStatus, Transfer}; use crate::transport::hyperdebug::{BulkInterface, Inner}; use crate::transport::{TransportError, TransportInterfaceType}; @@ -59,7 +60,7 @@ struct CmdTransferLong { write_count: u8, read_count: u8, read_count1: u8, - reserved: u8, + flags: u8, data: [u8; USB_MAX_SIZE - 6], } @@ -175,7 +176,7 @@ impl HyperdebugI2cBus { } /// Transmit data for a single I2C operation, using one or more USB packets. - fn transmit_then_receive(&self, addr: u8, wbuf: &[u8], rbuf: &mut [u8]) -> Result<()> { + fn transmit_then_receive(&self, addr: u8, wbuf: &[u8], rbuf: &mut [u8], gsc_ready: bool) -> Result<()> { ensure!( rbuf.len() < self.max_read_size, I2cError::InvalidDataLength(rbuf.len()) @@ -185,7 +186,7 @@ impl HyperdebugI2cBus { I2cError::InvalidDataLength(wbuf.len()) ); let encapsulation_header_size = if self.cmsis_encapsulation { 1 } else { 0 }; - let mut index = if rbuf.len() < 128 { + let mut index = if false /*rbuf.len() < 128*/ { // Short format header let mut req = CmdTransferShort { encapsulation_header: Self::CMSIS_DAP_CUSTOM_COMMAND_I2C, @@ -197,6 +198,7 @@ impl HyperdebugI2cBus { }; let databytes = cmp::min(USB_MAX_SIZE - 4 - encapsulation_header_size, wbuf.len()); req.data[..databytes].clone_from_slice(&wbuf[..databytes]); + log::error!("I2C wrote {:?}", &req.as_bytes()[1 - encapsulation_header_size..1 + 4 + databytes]); self.usb_write_bulk(&req.as_bytes()[1 - encapsulation_header_size..1 + 4 + databytes])?; databytes } else { @@ -206,13 +208,14 @@ impl HyperdebugI2cBus { port: self.bus_idx | (((wbuf.len() & 0x0F00) >> 4) as u8), addr, write_count: (wbuf.len() & 0x00FF) as u8, - read_count: (rbuf.len() & 0x007F) as u8, + read_count: (rbuf.len() & 0x007F | 0x0080) as u8, read_count1: (rbuf.len() >> 7) as u8, - reserved: 0, + flags: if gsc_ready { 0x80 } else { 0x00 }, data: [0; USB_MAX_SIZE - 6], }; let databytes = cmp::min(USB_MAX_SIZE - 6 - encapsulation_header_size, wbuf.len()); req.data[..databytes].clone_from_slice(&wbuf[..databytes]); + log::error!("I2C wrote {:?}", &req.as_bytes()[1 - encapsulation_header_size..1 + 6 + databytes]); self.usb_write_bulk(&req.as_bytes()[1 - encapsulation_header_size..1 + 6 + databytes])?; databytes }; @@ -220,6 +223,7 @@ impl HyperdebugI2cBus { // Transmit any more data without further header. while index < wbuf.len() { let databytes = cmp::min(USB_MAX_SIZE, wbuf.len() - index); + log::error!("I2C wrote {:?}", &wbuf[index..index + databytes]); self.usb_write_bulk(&wbuf[index..index + databytes])?; index += databytes; } @@ -230,6 +234,7 @@ impl HyperdebugI2cBus { let read_count = self.usb_read_bulk( &mut resp.as_bytes_mut()[1 - encapsulation_header_size + bytecount..][..64], )?; + log::error!("I2C read {:?}", &resp.as_bytes_mut()[1 - encapsulation_header_size + bytecount..][..read_count]); ensure!( read_count > 0, TransportError::CommunicationError("Truncated I2C response".to_string()) @@ -336,6 +341,25 @@ impl Bus for HyperdebugI2cBus { .cmd_no_output(&format!("i2c set speed {} {}", &self.bus_idx, max_speed)) } + fn set_pins( + &self, + serial_clock: Option<&Rc>, + serial_data: Option<&Rc>, + gsc_ready: Option<&Rc>, + ) -> Result<()> { + if serial_clock.is_some() || serial_data.is_some() { + bail!(I2cError::InvalidPin); + } + if let Some(pin) = gsc_ready { + self.inner.cmd_no_output(&format!( + "i2c set ready {} {}", + &self.bus_idx, + pin.get_internal_pin_name().ok_or(I2cError::InvalidPin)? + ))?; + } + Ok(()) + } + fn set_default_address(&self, addr: u8) -> Result<()> { self.default_addr.set(Some(addr)); Ok(()) @@ -347,6 +371,22 @@ impl Bus for HyperdebugI2cBus { .ok_or(I2cError::MissingAddress)?; while !transaction.is_empty() { match transaction { + [Transfer::Write(wbuf), Transfer::GscReady, Transfer::Read(rbuf), ..] => { + // Hyperdebug can do I2C write followed by I2C read as a single USB + // request/reply. Take advantage of that by detecting pairs of + // Transfer::Write followed by Transfer::Read. + ensure!( + wbuf.len() <= self.max_write_size, + I2cError::InvalidDataLength(wbuf.len()) + ); + ensure!( + rbuf.len() <= self.max_read_size, + I2cError::InvalidDataLength(rbuf.len()) + ); + self.transmit_then_receive(addr, wbuf, rbuf, true)?; + // Skip three steps ahead, as three items were processed. + transaction = &mut transaction[3..]; + } [Transfer::Write(wbuf), Transfer::Read(rbuf), ..] => { // Hyperdebug can do I2C write followed by I2C read as a single USB // request/reply. Take advantage of that by detecting pairs of @@ -359,7 +399,16 @@ impl Bus for HyperdebugI2cBus { rbuf.len() <= self.max_read_size, I2cError::InvalidDataLength(rbuf.len()) ); - self.transmit_then_receive(addr, wbuf, rbuf)?; + self.transmit_then_receive(addr, wbuf, rbuf, false)?; + // Skip two steps ahead, as two items were processed. + transaction = &mut transaction[2..]; + } + [Transfer::Write(wbuf), Transfer::GscReady, ..] => { + ensure!( + wbuf.len() <= self.max_write_size, + I2cError::InvalidDataLength(wbuf.len()) + ); + self.transmit_then_receive(addr, wbuf, &mut [], true)?; // Skip two steps ahead, as two items were processed. transaction = &mut transaction[2..]; } @@ -368,7 +417,7 @@ impl Bus for HyperdebugI2cBus { wbuf.len() <= self.max_write_size, I2cError::InvalidDataLength(wbuf.len()) ); - self.transmit_then_receive(addr, wbuf, &mut [])?; + self.transmit_then_receive(addr, wbuf, &mut [], false)?; transaction = &mut transaction[1..]; } [Transfer::Read(rbuf), ..] => { @@ -376,7 +425,7 @@ impl Bus for HyperdebugI2cBus { rbuf.len() <= self.max_read_size, I2cError::InvalidDataLength(rbuf.len()) ); - self.transmit_then_receive(addr, &[], rbuf)?; + self.transmit_then_receive(addr, &[], rbuf, false)?; transaction = &mut transaction[1..]; } [] => (), diff --git a/sw/host/opentitanlib/src/transport/hyperdebug/mod.rs b/sw/host/opentitanlib/src/transport/hyperdebug/mod.rs index 0a455f6ea95da6..49e9b43035aff4 100644 --- a/sw/host/opentitanlib/src/transport/hyperdebug/mod.rs +++ b/sw/host/opentitanlib/src/transport/hyperdebug/mod.rs @@ -138,6 +138,7 @@ impl Hyperdebug { const GOOGLE_CAP_GPIO_MONITORING: u16 = 0x0004; const GOOGLE_CAP_GPIO_BITBANGING: u16 = 0x0008; const GOOGLE_CAP_UART_QUEUE_CLEAR: u16 = 0x0010; + const GOOGLE_CAP_TPM_POLL: u16 = 0x0020; /// Establish connection with a particular HyperDebug. pub fn open( @@ -442,6 +443,7 @@ pub struct CachedIo { pub struct Conn { console_port: RefCell, + first_use: Cell, } // The way that the HyperDebug allows callers to request optimization for a sequence of operations @@ -467,6 +469,7 @@ impl Inner { flock_serial(&port, port_name)?; let conn = Rc::new(Conn { console_port: RefCell::new(port), + first_use: Cell::new(true), }); // Return a (strong) reference to the newly opened connection, while keeping a weak // reference to the same in this `Inner` object. The result is that if the caller keeps @@ -560,10 +563,16 @@ impl Conn { fn execute_command(&self, cmd: &str, mut callback: impl FnMut(&str)) -> Result<()> { let port: &mut TTYPort = &mut self.console_port.borrow_mut(); - // Send Ctrl-C, followed by the command, then newline. This will discard any previous - // partial input, before executing our command. - port.write(format!("\x03{}\n", cmd).as_bytes()) - .context("writing to HyperDebug console")?; + if self.first_use.get() { + // Send Ctrl-C, followed by the command, then newline. This will discard any previous + // partial input, before executing our command. + port.write(format!("\x03{}\n", cmd).as_bytes()) + .context("writing to HyperDebug console")?; + self.first_use.set(false); + } else { + port.write(format!("{}\n", cmd).as_bytes()) + .context("writing to HyperDebug console")?; + } // Now process response from HyperDebug. First we expect to see the echo of the command // we just "typed". Then zero, one or more lines of useful output, which we want to pass @@ -656,6 +665,7 @@ impl Transport for Hyperdebug { &self.spi_interface, enable_cmd, idx, + self.get_cmsis_google_capabilities()? & Self::GOOGLE_CAP_TPM_POLL != 0, )?); self.cached_io_interfaces .spis diff --git a/sw/host/opentitanlib/src/transport/hyperdebug/spi.rs b/sw/host/opentitanlib/src/transport/hyperdebug/spi.rs index 03b1ea989df7f2..f0a3f019c880d6 100644 --- a/sw/host/opentitanlib/src/transport/hyperdebug/spi.rs +++ b/sw/host/opentitanlib/src/transport/hyperdebug/spi.rs @@ -24,6 +24,7 @@ pub struct HyperdebugSpiTarget { target_enable_cmd: u8, target_idx: u8, feature_bitmap: u16, + supports_tpm_poll: bool, max_sizes: MaxSizes, cs_asserted_count: Cell, } @@ -251,6 +252,7 @@ impl HyperdebugSpiTarget { spi_interface: &BulkInterface, enable_cmd: u8, idx: u8, + supports_tpm_poll: bool, ) -> Result { let mut usb_handle = inner.usb_device.borrow_mut(); @@ -293,6 +295,7 @@ impl HyperdebugSpiTarget { target_enable_cmd: enable_cmd, target_idx: idx, feature_bitmap: resp.feature_bitmap, + supports_tpm_poll, max_sizes: MaxSizes { read: resp.max_read_chunk as usize, write: resp.max_write_chunk as usize, @@ -337,6 +340,50 @@ impl HyperdebugSpiTarget { Ok(()) } + /// XXX + fn tpm_transmit(&self, wbuf: &[u8], rbuf_len: usize, await_gsc_ready: bool) -> Result<()> { + const TPM_HEADER_SIZE: usize = 4; + log::error!("tpm_transmit({}, {}, await_gsc_ready = {})", wbuf.len(), rbuf_len, await_gsc_ready); + let mut req = CmdEepromTransferStart::new(); + if rbuf_len == 0 { + req.flags |= EEPROM_FLAGS_WRITE; + req.count = (wbuf.len() - TPM_HEADER_SIZE) as u16; + } else { + req.count = rbuf_len as u16; + ensure!( + wbuf.len() == TPM_HEADER_SIZE, + SpiError::InvalidDataLength(wbuf.len()) + ); + } + + //if wait_for_busy_clear { + // req.flags |= EEPROM_FLAGS_POLL_BUSY; + //} + req.flags |= (TPM_HEADER_SIZE as u32) << EEPROM_FLAGS_ADDR_LEN_POS; + if await_gsc_ready { + req.flags |= 1 << 26; + } + req.flags |= 1 << 27; + + let data_start_offset = 0; + // Optional write data bytes + let databytes = std::cmp::min(USB_MAX_SIZE - 8 - data_start_offset, wbuf.len()); + req.data[data_start_offset..data_start_offset + databytes] + .clone_from_slice(&wbuf[0..databytes]); + self.usb_write_bulk(&req.as_bytes()[0..8 + data_start_offset + databytes])?; + let mut index = databytes; + + while index < wbuf.len() { + let mut req = CmdTransferContinue::new(); + req.data_index = index as u16; + let databytes = std::cmp::min(USB_MAX_SIZE - 4, wbuf.len() - index); + req.data[0..databytes].clone_from_slice(&wbuf[index..index + databytes]); + self.usb_write_bulk(&req.as_bytes()[0..4 + databytes])?; + index += databytes; + } + Ok(()) + } + /// Receive data for a single SPI operation, using one or more USB packets. fn receive(&self, rbuf: &mut [u8]) -> Result<()> { let mut resp = RspTransferStart::new(); @@ -685,7 +732,7 @@ impl Target for HyperdebugSpiTarget { } fn supports_tpm_poll(&self) -> Result { - Ok(false) + Ok(self.supports_tpm_poll) } fn set_pins( @@ -696,7 +743,7 @@ impl Target for HyperdebugSpiTarget { chip_select: Option<&Rc>, gsc_ready: Option<&Rc>, ) -> Result<()> { - if serial_clock.is_some() || host_out_device_in.is_some() || host_in_device_out.is_some() || gsc_ready.is_some() { + if serial_clock.is_some() || host_out_device_in.is_some() || host_in_device_out.is_some() { bail!(SpiError::InvalidPin); } if let Some(pin) = chip_select { @@ -706,6 +753,13 @@ impl Target for HyperdebugSpiTarget { pin.get_internal_pin_name().ok_or(SpiError::InvalidPin)? ))?; } + if let Some(pin) = gsc_ready { + self.inner.cmd_no_output(&format!( + "spi set ready {} {}", + &self.target_idx, + pin.get_internal_pin_name().ok_or(SpiError::InvalidPin)? + ))?; + } Ok(()) } @@ -743,6 +797,36 @@ impl Target for HyperdebugSpiTarget { self.receive(rbuf)?; return Ok(()); } + [Transfer::Write(wbuf), Transfer::GscReady, Transfer::TpmPoll, Transfer::Read(rbuf)] => { + // Hyperdebug can do SPI TPM transaction as a single USB + // request/reply. + ensure!( + wbuf.len() <= self.max_sizes.write, + SpiError::InvalidDataLength(wbuf.len()) + ); + ensure!( + rbuf.len() <= self.max_sizes.read, + SpiError::InvalidDataLength(rbuf.len()) + ); + self.tpm_transmit(wbuf, rbuf.len(), true)?; + self.receive(rbuf)?; + return Ok(()); + } + [Transfer::Write(wbuf), Transfer::TpmPoll, Transfer::Read(rbuf)] => { + // Hyperdebug can do SPI TPM transaction as a single USB + // request/reply. + ensure!( + wbuf.len() <= self.max_sizes.write, + SpiError::InvalidDataLength(wbuf.len()) + ); + ensure!( + rbuf.len() <= self.max_sizes.read, + SpiError::InvalidDataLength(rbuf.len()) + ); + self.tpm_transmit(wbuf, rbuf.len(), false)?; + self.receive(rbuf)?; + return Ok(()); + } [Transfer::Write(wbuf)] => { ensure!( wbuf.len() <= self.max_sizes.write, @@ -762,6 +846,34 @@ impl Target for HyperdebugSpiTarget { return Ok(()); } } + [Transfer::Write(wbuf1), Transfer::TpmPoll, Transfer::Write(wbuf2), Transfer::GscReady] => { + // Hyperdebug can do SPI TPM transaction as a single USB + // request/reply. + ensure!( + wbuf1.len() + wbuf2.len() <= self.max_sizes.write, + SpiError::InvalidDataLength(wbuf1.len() + wbuf2.len()) + ); + let mut combined_buf = vec![0u8; wbuf1.len() + wbuf2.len()]; + combined_buf[..wbuf1.len()].clone_from_slice(wbuf1); + combined_buf[wbuf1.len()..].clone_from_slice(wbuf2); + self.tpm_transmit(&combined_buf, 0, true)?; + self.receive(&mut [])?; + return Ok(()); + } + [Transfer::Write(wbuf1), Transfer::TpmPoll, Transfer::Write(wbuf2)] => { + // Hyperdebug can do SPI TPM transaction as a single USB + // request/reply. + ensure!( + wbuf1.len() + wbuf2.len() <= self.max_sizes.write, + SpiError::InvalidDataLength(wbuf1.len() + wbuf2.len()) + ); + let mut combined_buf = vec![0u8; wbuf1.len() + wbuf2.len()]; + combined_buf[..wbuf1.len()].clone_from_slice(wbuf1); + combined_buf[wbuf1.len()..].clone_from_slice(wbuf2); + self.tpm_transmit(&combined_buf, 0, false)?; + self.receive(&mut [])?; + return Ok(()); + } [Transfer::Read(rbuf)] => { ensure!( rbuf.len() <= self.max_sizes.read,