| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| use anyhow::{bail, Result}; |
| use log; |
| |
| use std::cell::{Cell, RefCell}; |
| use std::io::{Read, Write}; |
| use std::os::unix::net::UnixStream; |
| use std::path::PathBuf; |
| use std::rc::Rc; |
| use std::time::{Duration, Instant}; |
| |
| use crate::io::emu::EmuState; |
| use crate::io::i2c::{Bus, I2cError, Transfer}; |
| use crate::transport::ti50emulator::emu::EMULATOR_INVALID_ID; |
| use crate::transport::ti50emulator::Inner; |
| use crate::transport::ti50emulator::Ti50Emulator; |
| |
| const MAX_READ_TIMEOUT: Duration = Duration::from_millis(35); |
| const MAX_WRITE_TIMEOUT: Duration = Duration::from_millis(35); |
| |
| const TI50_I2C_BUS_WRITE_REQ: u8 = b'W'; |
| const TI50_I2C_BUS_READ_REQ: u8 = b'R'; |
| const TI50_I2C_BUS_WRITE_RES: u8 = b'w'; |
| const TI50_I2C_BUS_READ_RES: u8 = b'r'; |
| |
| /// Structure representing the emulated I2C BUS control packet encoder/decoder |
| /// The current implementation does not require the use of a low-level |
| /// representation with ACK/NACK bits. However, a minimal data separation |
| /// between successive transactions is required. |
| struct Ti50BusControl {} |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Emulated I2C BUS data flow: |
| // |
| // [NAME] - named byte. |
| // [####] - long sequence of data bytes |
| // Tx - Direction of transfer - From Host (OpenTitanTool) to Target (chip). |
| // Rx - Direction of transfer - From Target (chip) to Target (chip). |
| // |
| // (1) Write transfer |
| // /--2B--\/----------LEN_BE-----------\ deadline->| |
| // | || | | |
| // Tx <<< [WRITE_REQ][ADDR][LEN_BE][###########################]+------------|- |
| // | | |
| // | | |
| // | | |
| // Rx >>> ------------------------------------------------------+[WRITE_RES]-|- |
| // |
| // Note: There is possibility that the data will get stuck in UnixStreamSocket |
| // buffer so we have to wait for WRITE_RES before starting next transaction. |
| // |
| // (2) Read transfer |
| // |
| // /--2B--\ deadline->| |
| // | | | |
| // Tx <<< [READ_REQ][ADDR][LEN_BE]+----------------------------------|--------- |
| // | | |
| // | /-----LEN_BE-----\ | |
| // | | | | |
| // Rx >>> ------------------------+[READ_RES][################]------|--------- |
| // |
| // Note: In the case of a read operation, LEN_BE represents the number of bytes |
| // the DUT would normally respond with an ACK signal |
| // |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| impl Ti50BusControl { |
| /// Send write request |
| pub fn send_write_request( |
| fd: &mut UnixStream, |
| deadline: Instant, |
| addr: u8, |
| len: u16, |
| ) -> Result<()> { |
| let mut header = [TI50_I2C_BUS_WRITE_REQ, addr, 0, 0]; |
| header[2..].copy_from_slice(&len.to_be_bytes()); |
| Ti50I2cBus::tx(fd, &header, deadline) |
| } |
| |
| /// Send read request |
| pub fn send_read_request( |
| fd: &mut UnixStream, |
| deadline: Instant, |
| addr: u8, |
| len: u16, |
| ) -> Result<()> { |
| let mut header = [TI50_I2C_BUS_READ_REQ, addr, 0, 0]; |
| header[2..].copy_from_slice(&len.to_be_bytes()); |
| Ti50I2cBus::tx(fd, &header, deadline) |
| } |
| |
| /// Receive write response |
| pub fn recv_write_response(fd: &mut UnixStream, deadline: Instant) -> Result<()> { |
| let buf = &mut [1u8; 1]; |
| let _rx_len = Ti50I2cBus::rx(fd, &mut buf[..], deadline)?; |
| match buf { |
| [TI50_I2C_BUS_WRITE_RES] => Ok(()), |
| [raw] => Err( |
| I2cError::Generic(format!("Unexpected BUS control message: {:02X}", raw)).into(), |
| ), |
| } |
| } |
| |
| /// Receive read response |
| pub fn recv_read_response(fd: &mut UnixStream, deadline: Instant) -> Result<()> { |
| let buf = &mut [1u8; 1]; |
| let _rx_len = Ti50I2cBus::rx(fd, &mut buf[..], deadline)?; |
| match buf { |
| [TI50_I2C_BUS_READ_RES] => Ok(()), |
| [raw] => Err( |
| I2cError::Generic(format!("Unexpected BUS control message: {:02X}", raw)).into(), |
| ), |
| } |
| } |
| } |
| |
| /// Structure representing Emulated I2C BUS |
| pub struct Ti50I2cBus { |
| inner: Rc<RefCell<Inner>>, |
| // This socket is valid as long as SubProcess is running. |
| socket: RefCell<Option<UnixStream>>, |
| // full path to socket file |
| path: PathBuf, |
| // last SubProcess ID |
| last_id: Cell<u64>, |
| } |
| |
| impl Ti50I2cBus { |
| /// Create new instance of [`Ti50I2cBus`] based on provided parameters. |
| pub fn open(ti50: &Ti50Emulator, path: &str) -> Result<Self> { |
| let soc_path = ti50.inner.borrow().process.get_runtime_dir().join(path); |
| Ok(Self { |
| inner: Rc::clone(&ti50.inner), |
| socket: RefCell::default(), |
| path: soc_path, |
| last_id: Cell::new(EMULATOR_INVALID_ID), |
| }) |
| } |
| |
| /// Function re-connect socket to `SubProcess` when detect |
| /// that process was restarted. |
| pub fn reconnect(&self) -> Result<()> { |
| let mut socket = self.socket.borrow_mut(); |
| let id = self.inner.borrow_mut().process.get_id(); |
| if self.last_id.get() != id { |
| let fd = UnixStream::connect(&self.path)?; |
| *socket = Some(fd); |
| self.last_id.set(id); |
| } |
| Ok(()) |
| } |
| |
| /// Function check if I2C transaction should be performed on DUT. |
| pub fn check_state(&self) -> Result<()> { |
| let process = &mut self.inner.borrow_mut().process; |
| process.update_status()?; |
| match process.get_state() { |
| EmuState::On => Ok(()), |
| state => Err(I2cError::Generic(format!( |
| "Operation not supported in Emulator state: {}", |
| state |
| )) |
| .into()), |
| } |
| } |
| |
| /// Function try to receive data from unix socket. |
| pub fn rx(fd: &mut UnixStream, data: &mut [u8], deadline: Instant) -> Result<usize> { |
| let mut rx_count: usize = 0; |
| while rx_count <= data.len() { |
| let ts = Instant::now(); |
| if ts > deadline { |
| bail!(I2cError::Timeout); |
| } |
| fd.set_read_timeout(Some(deadline - ts))?; |
| rx_count += fd.read(&mut data[rx_count..])?; |
| } |
| Ok(rx_count) |
| } |
| |
| /// Function send contents of slice to unix socket. |
| pub fn tx(fd: &mut UnixStream, data: &[u8], deadline: Instant) -> Result<()> { |
| let mut tx_count: usize = 0; |
| while tx_count <= data.len() { |
| let ts = Instant::now(); |
| if ts > deadline { |
| bail!(I2cError::Timeout); |
| } |
| fd.set_write_timeout(Some(deadline - ts))?; |
| tx_count += fd.write(&data[tx_count..])?; |
| } |
| Ok(()) |
| } |
| |
| /// Function perform write transaction on emulated bus. |
| pub fn write(&self, addr: u8, data: &[u8]) -> Result<()> { |
| if let Some(ref mut fd) = *self.socket.borrow_mut() { |
| let deadline = Instant::now() + MAX_WRITE_TIMEOUT; |
| log::debug!( |
| "I2C Transmit transaction write request addr: {:02X} len: {}", |
| addr, |
| data.len() |
| ); |
| Ti50BusControl::send_write_request(fd, deadline, addr, data.len() as u16)?; |
| Ti50I2cBus::tx(fd, data, deadline)?; |
| return Ti50BusControl::recv_write_response(fd, deadline); |
| } |
| bail!(I2cError::Generic("Invalid socket".to_string())); |
| } |
| |
| /// Function perform read transaction on emulated BUS. |
| pub fn read(&self, addr: u8, data: &mut [u8]) -> Result<()> { |
| if let Some(ref mut fd) = *self.socket.borrow_mut() { |
| let deadline = Instant::now() + MAX_READ_TIMEOUT; |
| log::debug!( |
| "I2C Transmit transaction read request addr: {:02X} len: {}", |
| addr, |
| data.len() |
| ); |
| Ti50BusControl::send_read_request(fd, deadline, addr, data.len() as u16)?; |
| Ti50BusControl::recv_read_response(fd, deadline)?; |
| Ti50I2cBus::rx(fd, &mut data[..], deadline)?; |
| return Ok(()); |
| } |
| bail!(I2cError::Generic("Invalid socket".to_string())); |
| } |
| } |
| |
| impl Bus for Ti50I2cBus { |
| fn run_transaction(&self, addr: u8, transaction: &mut [Transfer]) -> Result<()> { |
| self.check_state()?; |
| self.reconnect()?; |
| for transfer in transaction { |
| match transfer { |
| Transfer::Write(wr) => { |
| self.write(addr, wr)?; |
| } |
| Transfer::Read(rd) => { |
| self.read(addr, rd)?; |
| } |
| } |
| } |
| Ok(()) |
| } |
| } |