diff --git a/framework_lib/src/ccgx/device.rs b/framework_lib/src/ccgx/device.rs index aa1af5d0..ef7ac889 100644 --- a/framework_lib/src/ccgx/device.rs +++ b/framework_lib/src/ccgx/device.rs @@ -3,23 +3,17 @@ //! The current implementation talks to them by tunneling I2C through EC host commands. use alloc::format; -use alloc::string::ToString; -use alloc::vec; use alloc::vec::Vec; #[cfg(feature = "uefi")] use core::prelude::rust_2021::derive; use crate::ccgx::{AppVersion, BaseVersion, ControllerVersion}; -use crate::chromium_ec::command::EcCommands; -use crate::chromium_ec::{CrosEc, CrosEcDriver, EcError, EcResult}; -use crate::util::{self, assert_win_len, Config, Platform}; -use std::mem::size_of; +use crate::chromium_ec::i2c_passthrough::*; +use crate::chromium_ec::{CrosEc, EcError, EcResult}; +use crate::util::{assert_win_len, Config, Platform}; use super::*; -/// Maximum transfer size for one I2C transaction supported by the chip -const MAX_I2C_CHUNK: usize = 128; - enum ControlRegisters { DeviceMode = 0, SiliconId = 2, // Two bytes long, First LSB, then MSB @@ -106,58 +100,6 @@ pub struct PdController { ec: CrosEc, } -fn passthrough_offset(dev_index: u16) -> u16 { - dev_index * 0x4000 -} - -#[repr(C, packed)] -struct EcParamsI2cPassthruMsg { - /// Slave address and flags - addr_and_flags: u16, - transfer_len: u16, -} - -#[repr(C, packed)] -struct EcParamsI2cPassthru { - port: u8, - /// How many messages - messages: u8, - msg: [EcParamsI2cPassthruMsg; 0], -} - -#[repr(C, packed)] -struct _EcI2cPassthruResponse { - i2c_status: u8, - /// How many messages - messages: u8, - data: [u8; 0], -} - -struct EcI2cPassthruResponse { - i2c_status: u8, // TODO: Can probably use enum - data: Vec, -} - -impl EcI2cPassthruResponse { - fn is_successful(&self) -> EcResult<()> { - if self.i2c_status & 1 > 0 { - return Err(EcError::DeviceError( - "I2C Transfer not acknowledged".to_string(), - )); - } - if self.i2c_status & (1 << 1) > 0 { - return Err(EcError::DeviceError("I2C Transfer timeout".to_string())); - } - // I'm not aware of any other errors, but there might be. - // But I don't think multiple errors can be indicated at the same time - assert_eq!(self.i2c_status, 0); - Ok(()) - } -} - -/// Indicate that it's a read, not a write -const I2C_READ_FLAG: u16 = 1 << 15; - #[derive(Debug, PartialEq)] pub enum FwMode { BootLoader = 0, @@ -194,13 +136,6 @@ impl PdController { pub fn new(port: PdPort, ec: CrosEc) -> Self { PdController { port, ec } } - /// Wrapped with support for dev id - /// TODO: Should move into chromium_ec module - /// TODO: Must not call CrosEc::new() otherwise the driver isn't configurable! - fn send_ec_command(&self, code: u16, dev_index: u16, data: &[u8]) -> EcResult> { - let command_id = code + passthrough_offset(dev_index); - self.ec.send_command(command_id, 0, data) - } fn i2c_read(&self, addr: u16, len: u16) -> EcResult { trace!( @@ -208,49 +143,13 @@ impl PdController { self.port.i2c_port().unwrap(), self.port.i2c_address() ); - trace!("i2c_read(addr: {}, len: {})", addr, len); - if usize::from(len) > MAX_I2C_CHUNK { - return EcResult::Err(EcError::DeviceError(format!( - "i2c_read too long. Must be <128, is: {}", - len - ))); - } - let addr_bytes = u16::to_le_bytes(addr); - let messages = vec![ - EcParamsI2cPassthruMsg { - addr_and_flags: self.port.i2c_address(), - transfer_len: addr_bytes.len() as u16, - }, - EcParamsI2cPassthruMsg { - addr_and_flags: self.port.i2c_address() + I2C_READ_FLAG, - transfer_len: len, // How much to read - }, - ]; - let msgs_len = size_of::() * messages.len(); - let msgs_buffer: &[u8] = unsafe { util::any_vec_as_u8_slice(&messages) }; - - let params = EcParamsI2cPassthru { - port: self.port.i2c_port()?, - messages: messages.len() as u8, - msg: [], // Messages are copied right after this struct - }; - let params_len = size_of::(); - let params_buffer: &[u8] = unsafe { util::any_as_u8_slice(¶ms) }; - - let mut buffer: Vec = vec![0; params_len + msgs_len + addr_bytes.len()]; - buffer[0..params_len].copy_from_slice(params_buffer); - buffer[params_len..params_len + msgs_len].copy_from_slice(msgs_buffer); - buffer[params_len + msgs_len..].copy_from_slice(&addr_bytes); - - let data = self.send_ec_command(EcCommands::I2cPassthrough as u16, 0, &buffer)?; - let res: _EcI2cPassthruResponse = unsafe { std::ptr::read(data.as_ptr() as *const _) }; - let res_data = &data[size_of::<_EcI2cPassthruResponse>()..]; - // TODO: Seems to be either one, non-deterministically - debug_assert!(res.messages as usize == messages.len() || res.messages == 0); - Ok(EcI2cPassthruResponse { - i2c_status: res.i2c_status, - data: res_data.to_vec(), - }) + i2c_read( + &self.ec, + self.port.i2c_port().unwrap(), + self.port.i2c_address(), + addr, + len, + ) } fn ccgx_read(&self, reg: ControlRegisters, len: u16) -> EcResult> { diff --git a/framework_lib/src/chromium_ec/i2c_passthrough.rs b/framework_lib/src/chromium_ec/i2c_passthrough.rs new file mode 100644 index 00000000..dfebaafc --- /dev/null +++ b/framework_lib/src/chromium_ec/i2c_passthrough.rs @@ -0,0 +1,164 @@ +use crate::chromium_ec::command::EcCommands; +use crate::chromium_ec::{CrosEc, CrosEcDriver, EcError, EcResult}; +use crate::util; +use alloc::format; +use alloc::string::ToString; +use alloc::vec; +use alloc::vec::Vec; +use std::mem::size_of; + +/// Maximum transfer size for one I2C transaction supported by the chip +pub const MAX_I2C_CHUNK: usize = 128; + +#[repr(C, packed)] +pub struct EcParamsI2cPassthruMsg { + /// Slave address and flags + addr_and_flags: u16, + transfer_len: u16, +} + +#[repr(C, packed)] +pub struct EcParamsI2cPassthru { + port: u8, + /// How many messages + messages: u8, + msg: [EcParamsI2cPassthruMsg; 0], +} + +#[repr(C, packed)] +struct _EcI2cPassthruResponse { + i2c_status: u8, + /// How many messages + messages: u8, + data: [u8; 0], +} + +#[derive(Debug)] +pub struct EcI2cPassthruResponse { + pub i2c_status: u8, // TODO: Can probably use enum + pub data: Vec, +} + +impl EcI2cPassthruResponse { + pub fn is_successful(&self) -> EcResult<()> { + if self.i2c_status & 1 > 0 { + return Err(EcError::DeviceError( + "I2C Transfer not acknowledged".to_string(), + )); + } + if self.i2c_status & (1 << 1) > 0 { + return Err(EcError::DeviceError("I2C Transfer timeout".to_string())); + } + // I'm not aware of any other errors, but there might be. + // But I don't think multiple errors can be indicated at the same time + assert_eq!(self.i2c_status, 0); + Ok(()) + } +} + +/// Indicate that it's a read, not a write +const I2C_READ_FLAG: u16 = 1 << 15; + +pub fn i2c_read( + ec: &CrosEc, + i2c_port: u8, + i2c_addr: u16, + addr: u16, + len: u16, +) -> EcResult { + trace!( + "i2c_read(i2c_port: 0x{:X}, i2c_addr: 0x{:X}, addr: 0x{:X}, len: 0x{:X})", + i2c_port, + i2c_addr, + addr, + len + ); + if usize::from(len) > MAX_I2C_CHUNK { + return EcResult::Err(EcError::DeviceError(format!( + "i2c_read too long. Must be <128, is: {}", + len + ))); + } + let addr_bytes = u16::to_le_bytes(addr); + let messages = vec![ + EcParamsI2cPassthruMsg { + addr_and_flags: i2c_addr, + transfer_len: addr_bytes.len() as u16, + }, + EcParamsI2cPassthruMsg { + addr_and_flags: i2c_addr + I2C_READ_FLAG, + transfer_len: len, // How much to read + }, + ]; + let msgs_len = size_of::() * messages.len(); + let msgs_buffer: &[u8] = unsafe { util::any_vec_as_u8_slice(&messages) }; + + let params = EcParamsI2cPassthru { + port: i2c_port, + messages: messages.len() as u8, + msg: [], // Messages are copied right after this struct + }; + let params_len = size_of::(); + let params_buffer: &[u8] = unsafe { util::any_as_u8_slice(¶ms) }; + + let mut buffer: Vec = vec![0; params_len + msgs_len + addr_bytes.len()]; + buffer[0..params_len].copy_from_slice(params_buffer); + buffer[params_len..params_len + msgs_len].copy_from_slice(msgs_buffer); + buffer[params_len + msgs_len..].copy_from_slice(&addr_bytes); + + let data = ec.send_command(EcCommands::I2cPassthrough as u16, 0, &buffer)?; + let res: _EcI2cPassthruResponse = unsafe { std::ptr::read(data.as_ptr() as *const _) }; + let res_data = &data[size_of::<_EcI2cPassthruResponse>()..]; + // TODO: Seems to be either one, non-deterministically + debug_assert!(res.messages as usize == messages.len() || res.messages == 0); + Ok(EcI2cPassthruResponse { + i2c_status: res.i2c_status, + data: res_data.to_vec(), + }) +} + +pub fn i2c_write( + ec: &CrosEc, + i2c_port: u8, + i2c_addr: u16, + addr: u16, + data: &[u8], +) -> EcResult { + trace!( + " i2c_write(addr: {}, len: {}, data: {:?})", + addr, + data.len(), + data + ); + let addr_bytes = [addr as u8, (addr >> 8) as u8]; + let messages = vec![EcParamsI2cPassthruMsg { + addr_and_flags: i2c_addr, + transfer_len: (addr_bytes.len() + data.len()) as u16, + }]; + let msgs_len = size_of::() * messages.len(); + let msgs_buffer: &[u8] = unsafe { util::any_vec_as_u8_slice(&messages) }; + + let params = EcParamsI2cPassthru { + port: i2c_port, + messages: messages.len() as u8, + msg: [], // Messages are copied right after this struct + }; + let params_len = size_of::(); + let params_buffer: &[u8] = unsafe { util::any_as_u8_slice(¶ms) }; + + let mut buffer: Vec = vec![0; params_len + msgs_len + addr_bytes.len() + data.len()]; + buffer[0..params_len].copy_from_slice(params_buffer); + buffer[params_len..params_len + msgs_len].copy_from_slice(msgs_buffer); + buffer[params_len + msgs_len..params_len + msgs_len + addr_bytes.len()] + .copy_from_slice(&addr_bytes); + buffer[params_len + msgs_len + addr_bytes.len()..].copy_from_slice(data); + + let data = ec.send_command(EcCommands::I2cPassthrough as u16, 0, &buffer)?; + let res: _EcI2cPassthruResponse = unsafe { std::ptr::read(data.as_ptr() as *const _) }; + assert_eq!(data.len(), size_of::<_EcI2cPassthruResponse>()); // No extra data other than the header + debug_assert_eq!(res.messages as usize, messages.len()); + Ok(EcI2cPassthruResponse { + i2c_status: res.i2c_status, + data: vec![], // Writing doesn't return any data + }) +} diff --git a/framework_lib/src/chromium_ec/mod.rs b/framework_lib/src/chromium_ec/mod.rs index e1a53c20..7ee21b72 100644 --- a/framework_lib/src/chromium_ec/mod.rs +++ b/framework_lib/src/chromium_ec/mod.rs @@ -22,6 +22,7 @@ pub mod command; pub mod commands; #[cfg(feature = "cros_ec_driver")] mod cros_ec; +pub mod i2c_passthrough; pub mod input_deck; mod portio; mod portio_mec;