use anyhow::{bail, ensure, Result};
use std::cell::Cell;
use std::cmp;
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};
pub struct HyperdebugI2cBus {
inner: Rc<Inner>,
interface: BulkInterface,
cmsis_encapsulation: bool,
supports_i2c_device: bool,
bus_idx: u8,
mode: Cell<Mode>,
max_write_size: usize,
max_read_size: usize,
default_addr: Cell<Option<u8>>,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Mode {
Host,
Device,
}
const USB_MAX_SIZE: usize = 64;
#[derive(AsBytes, FromBytes, FromZeroes, Debug)]
#[allow(dead_code)] #[repr(C, packed)]
struct CmdTransferShort {
encapsulation_header: u8,
port: u8,
addr: u8,
write_count: u8,
read_count: u8,
data: [u8; USB_MAX_SIZE - 4],
}
#[derive(AsBytes, FromBytes, FromZeroes, Debug)]
#[allow(dead_code)] #[repr(C, packed)]
struct CmdTransferLong {
encapsulation_header: u8,
port: u8,
addr: u8,
write_count: u8,
read_count: u8,
read_count1: u8,
flags: u8,
data: [u8; USB_MAX_SIZE - 6],
}
#[derive(AsBytes, FromBytes, FromZeroes, Debug)]
#[allow(dead_code)] #[repr(C, packed)]
struct RspTransfer {
encapsulation_header: u8,
status_code: u16,
reserved: u16,
data: [u8; USB_MAX_SIZE],
}
impl RspTransfer {
fn new() -> Self {
Self {
encapsulation_header: 0,
status_code: 0,
reserved: 0,
data: [0; USB_MAX_SIZE],
}
}
}
#[derive(AsBytes, FromBytes, FromZeroes, Debug)]
#[allow(dead_code)] #[repr(C, packed)]
struct CmdGetDeviceStatus {
encapsulation_header: u8,
port: u8,
device_cmd: u8,
timeout_ms: u16,
}
const I2C_DEVICE_CMD_GET_DEVICE_STATUS: u8 = 0x00;
const I2C_DEVICE_CMD_PREPARE_READ_DATA: u8 = 0x01;
const I2C_DEVICE_FLAG_STICKY: u8 = 0x80;
#[derive(AsBytes, FromBytes, FromZeroes, Debug)]
#[repr(C, packed)]
struct RspGetDeviceStatus {
encapsulation_header: u8,
struct_size: u16,
read_status: u8,
blocked_read_addr: u8,
transcript_size: u16,
data: [u8; USB_MAX_SIZE],
}
impl RspGetDeviceStatus {
fn new() -> Self {
Self {
encapsulation_header: 0,
struct_size: 0,
read_status: 0,
blocked_read_addr: 0,
transcript_size: 0,
data: [0u8; USB_MAX_SIZE],
}
}
}
#[derive(AsBytes, FromBytes, FromZeroes, Debug)]
#[allow(dead_code)] #[repr(C, packed)]
struct CmdPrepareReadData {
encapsulation_header: u8,
port: u8,
device_cmd: u8,
data_size: u16,
data: [u8; USB_MAX_SIZE - 4],
}
impl HyperdebugI2cBus {
const CMSIS_DAP_CUSTOM_COMMAND_I2C: u8 = 0x81;
const CMSIS_DAP_CUSTOM_COMMAND_I2C_DEVICE: u8 = 0x82;
pub fn open(
inner: &Rc<Inner>,
i2c_interface: &BulkInterface,
cmsis_encapsulation: bool,
supports_i2c_device: bool,
idx: u8,
mode: Mode,
) -> Result<Self> {
ensure!(
idx < 16,
TransportError::InvalidInstance(TransportInterfaceType::I2c, idx.to_string())
);
let mut usb_handle = inner.usb_device.borrow_mut();
usb_handle.claim_interface(i2c_interface.interface)?;
Ok(Self {
inner: Rc::clone(inner),
interface: *i2c_interface,
cmsis_encapsulation,
supports_i2c_device,
bus_idx: idx,
mode: Cell::new(mode),
max_read_size: 0x8000,
max_write_size: 0x1000,
default_addr: Cell::new(None),
})
}
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())
);
ensure!(
wbuf.len() < self.max_write_size,
I2cError::InvalidDataLength(wbuf.len())
);
let encapsulation_header_size = if self.cmsis_encapsulation { 1 } else { 0 };
let mut index = if rbuf.len() < 128 && !gsc_ready {
let mut req = CmdTransferShort {
encapsulation_header: Self::CMSIS_DAP_CUSTOM_COMMAND_I2C,
port: self.bus_idx | (((wbuf.len() & 0x0F00) >> 4) as u8),
addr,
write_count: (wbuf.len() & 0x00FF) as u8,
read_count: rbuf.len() as u8,
data: [0; USB_MAX_SIZE - 4],
};
let databytes = cmp::min(USB_MAX_SIZE - 4 - encapsulation_header_size, wbuf.len());
req.data[..databytes].clone_from_slice(&wbuf[..databytes]);
self.usb_write_bulk(&req.as_bytes()[1 - encapsulation_header_size..1 + 4 + databytes])?;
databytes
} else {
let mut req = CmdTransferLong {
encapsulation_header: Self::CMSIS_DAP_CUSTOM_COMMAND_I2C,
port: self.bus_idx | (((wbuf.len() & 0x0F00) >> 4) as u8),
addr,
write_count: (wbuf.len() & 0x00FF) as u8,
read_count: (rbuf.len() & 0x007F | 0x0080) as u8,
read_count1: (rbuf.len() >> 7) as u8,
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]);
self.usb_write_bulk(&req.as_bytes()[1 - encapsulation_header_size..1 + 6 + databytes])?;
databytes
};
while index < wbuf.len() {
let databytes = cmp::min(USB_MAX_SIZE, wbuf.len() - index);
self.usb_write_bulk(&wbuf[index..index + databytes])?;
index += databytes;
}
let mut resp = RspTransfer::new();
let mut bytecount = 0;
while bytecount < 4 + encapsulation_header_size {
let read_count = self.usb_read_bulk(
&mut resp.as_bytes_mut()[1 - encapsulation_header_size + bytecount..][..64],
)?;
ensure!(
read_count > 0,
TransportError::CommunicationError("Truncated I2C response".to_string())
);
bytecount += read_count;
}
if encapsulation_header_size == 1 {
ensure!(
resp.encapsulation_header == Self::CMSIS_DAP_CUSTOM_COMMAND_I2C,
TransportError::CommunicationError(
"Unrecognized CMSIS-DAP response to I2C request".to_string()
)
);
}
match resp.status_code {
0 => (),
1 => bail!(I2cError::Timeout),
2 => bail!(I2cError::Busy),
n => bail!(TransportError::CommunicationError(format!(
"I2C error: {}",
n
))),
}
let databytes = bytecount - 4 - encapsulation_header_size;
rbuf[..databytes].clone_from_slice(&resp.data[..databytes]);
let mut index = databytes;
while index < rbuf.len() {
let databytes = self.usb_read_bulk(&mut resp.data[index..])?;
ensure!(
databytes > 0,
TransportError::CommunicationError(
"Unrecognized reponse to I2C request".to_string()
)
);
index += databytes;
}
Ok(())
}
fn usb_write_bulk(&self, buf: &[u8]) -> Result<()> {
self.inner
.usb_device
.borrow()
.write_bulk(self.interface.out_endpoint, buf)?;
Ok(())
}
fn usb_read_bulk(&self, buf: &mut [u8]) -> Result<usize> {
self.inner
.usb_device
.borrow()
.read_bulk(self.interface.in_endpoint, buf)
}
fn usb_read_bulk_timeout(&self, buf: &mut [u8], timeout: Duration) -> Result<usize> {
self.inner
.usb_device
.borrow()
.read_bulk_timeout(self.interface.in_endpoint, buf, timeout)
}
}
impl Bus for HyperdebugI2cBus {
fn set_mode(&self, mode: i2c::Mode) -> Result<()> {
match mode {
i2c::Mode::Host => {
self.inner
.cmd_no_output(&format!("i2c set mode {} host", &self.bus_idx))?;
self.mode.set(Mode::Host);
}
i2c::Mode::Device(addr) => {
ensure!(
self.supports_i2c_device,
TransportError::UnsupportedOperation,
);
self.inner
.cmd_no_output(&format!("i2c set mode {} device {}", &self.bus_idx, addr))?;
self.mode.set(Mode::Device);
}
}
Ok(())
}
fn get_max_speed(&self) -> Result<u32> {
let mut buf = String::new();
let captures = self.inner.cmd_one_line_output_match(
&format!("i2c info {}", &self.bus_idx),
&super::SPI_REGEX,
&mut buf,
)?;
Ok(captures.get(3).unwrap().as_str().parse().unwrap())
}
fn set_max_speed(&self, max_speed: u32) -> Result<()> {
self.inner
.cmd_no_output(&format!("i2c set speed {} {}", &self.bus_idx, max_speed))
}
fn set_pins(
&self,
serial_clock: Option<&Rc<dyn GpioPin>>,
serial_data: Option<&Rc<dyn GpioPin>>,
gsc_ready: Option<&Rc<dyn GpioPin>>,
) -> 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(())
}
fn run_transaction(&self, addr: Option<u8>, mut transaction: &mut [Transfer]) -> Result<()> {
let addr = addr
.or(self.default_addr.get())
.ok_or(I2cError::MissingAddress)?;
while !transaction.is_empty() {
match transaction {
[Transfer::Write(wbuf), Transfer::GscReady, Transfer::Read(rbuf), ..] => {
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)?;
transaction = &mut transaction[3..];
}
[Transfer::Write(wbuf), Transfer::Read(rbuf), ..] => {
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, false)?;
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)?;
transaction = &mut transaction[2..];
}
[Transfer::Write(wbuf), ..] => {
ensure!(
wbuf.len() <= self.max_write_size,
I2cError::InvalidDataLength(wbuf.len())
);
self.transmit_then_receive(addr, wbuf, &mut [], false)?;
transaction = &mut transaction[1..];
}
[Transfer::Read(rbuf), ..] => {
ensure!(
rbuf.len() <= self.max_read_size,
I2cError::InvalidDataLength(rbuf.len())
);
self.transmit_then_receive(addr, &[], rbuf, false)?;
transaction = &mut transaction[1..];
}
[] => (),
_ => bail!(TransportError::UnsupportedOperation),
}
}
Ok(())
}
fn get_device_status(&self, timeout: Duration) -> Result<DeviceStatus> {
ensure!(
self.cmsis_encapsulation && self.supports_i2c_device,
TransportError::UnsupportedOperation
);
ensure!(self.mode.get() == Mode::Device, I2cError::NotInDeviceMode);
let timeout_ms = timeout.as_millis();
let req = CmdGetDeviceStatus {
encapsulation_header: Self::CMSIS_DAP_CUSTOM_COMMAND_I2C_DEVICE,
port: self.bus_idx,
device_cmd: I2C_DEVICE_CMD_GET_DEVICE_STATUS,
timeout_ms: if timeout_ms > 65535 {
65535
} else {
timeout_ms as u16
},
};
self.usb_write_bulk(req.as_bytes())?;
let mut resp = RspGetDeviceStatus::new();
let mut bytecount = 0;
while bytecount < 7 {
let read_count = self.usb_read_bulk_timeout(
&mut resp.as_bytes_mut()[bytecount..][..64],
Duration::from_millis(req.timeout_ms as u64 + 500),
)?;
ensure!(
read_count > 0,
TransportError::CommunicationError("Truncated I2C response".to_string())
);
bytecount += read_count;
}
ensure!(
resp.encapsulation_header == Self::CMSIS_DAP_CUSTOM_COMMAND_I2C_DEVICE,
TransportError::CommunicationError(
"Unrecognized CMSIS-DAP response to I2C request".to_string()
)
);
let skip_bytes = resp.struct_size - 6;
let mut databytes: Vec<u8> = Vec::new();
databytes.extend_from_slice(&resp.data[..bytecount - 7]);
while databytes.len() < (skip_bytes + resp.transcript_size) as usize {
let original_length = databytes.len();
databytes.resize(original_length + 64, 0u8);
let c = self.usb_read_bulk(&mut databytes[original_length..])?;
databytes.resize(original_length + c, 0u8);
}
let mut transfers = Vec::new();
let mut idx = skip_bytes as usize;
while idx < databytes.len() {
let addr = databytes[idx] >> 1;
let is_read = (databytes[idx] & 0x01) != 0;
let timeout = (databytes[idx + 1] & 0x01) != 0;
let transfer_len = databytes[idx + 2] as usize + ((databytes[idx + 3] as usize) << 8);
idx += 4;
if is_read {
transfers.push(DeviceTransfer::Read {
addr,
timeout,
len: transfer_len,
});
} else {
transfers.push(DeviceTransfer::Write {
addr,
data: databytes[idx..idx + transfer_len].to_vec(),
});
idx += (transfer_len + 3) & !3;
}
}
let read_status = match resp.read_status {
0 => ReadStatus::Idle,
1 => ReadStatus::DataPrepared,
2 => ReadStatus::WaitingForData(resp.blocked_read_addr >> 1),
_ => bail!(TransportError::CommunicationError(
"Unrecognized I2C read status".to_string()
)),
};
Ok(DeviceStatus {
transfers,
read_status,
})
}
fn prepare_read_data(&self, data: &[u8], sticky: bool) -> Result<()> {
ensure!(
self.cmsis_encapsulation && self.supports_i2c_device,
TransportError::UnsupportedOperation
);
ensure!(self.mode.get() == Mode::Device, I2cError::NotInDeviceMode);
if data.len() > 1024 {
bail!(TransportError::CommunicationError(
"Data exceeds maximum length".to_string()
))
}
let flags = if sticky { I2C_DEVICE_FLAG_STICKY } else { 0 };
let mut req = CmdPrepareReadData {
encapsulation_header: Self::CMSIS_DAP_CUSTOM_COMMAND_I2C_DEVICE,
port: self.bus_idx | flags,
device_cmd: I2C_DEVICE_CMD_PREPARE_READ_DATA,
data_size: data.len() as u16,
data: [0; USB_MAX_SIZE - 4],
};
let mut index = std::cmp::min(64 - 5, data.len());
req.data[..index].clone_from_slice(&data[..index]);
self.usb_write_bulk(&req.as_bytes()[..5 + index])?;
while index < data.len() {
let packet_len = std::cmp::min(64, data.len() - index);
self.usb_write_bulk(&data[index..index + packet_len])?;
index += packet_len;
}
let mut resp = 0u8;
let c = self.usb_read_bulk(std::slice::from_mut(&mut resp))?;
ensure!(
c == 1 && resp == Self::CMSIS_DAP_CUSTOM_COMMAND_I2C_DEVICE,
TransportError::CommunicationError(
"Unrecognized CMSIS-DAP response to I2C request".to_string()
)
);
Ok(())
}
}