blob: 859352725a208b3583b602f97e2dd71cfd0b451a [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
use anyhow::{bail, ensure, Result};
use std::cmp;
use std::rc::Rc;
use zerocopy::{AsBytes, FromBytes};
use crate::io::i2c::{Bus, I2cError, Transfer};
use crate::transport::hyperdebug::{BulkInterface, Inner};
use crate::transport::TransportError;
pub struct HyperdebugI2cBus {
inner: Rc<Inner>,
interface: BulkInterface,
bus_idx: u8,
max_write_size: usize,
max_read_size: usize,
}
const USB_MAX_SIZE: usize = 64;
/// Wire format of USB packet to request a short I2C transaction
/// (receiving at most 127 bytes).
#[derive(AsBytes, FromBytes, Debug)]
#[repr(C)]
struct CmdTransferShort {
port: u8,
addr: u8,
write_count: u8,
read_count: u8,
data: [u8; USB_MAX_SIZE - 4],
}
/// Wire format of USB packet to request a long I2C transaction
/// (receiving up to 32767 bytes).
#[derive(AsBytes, FromBytes, Debug)]
#[repr(C)]
struct CmdTransferLong {
port: u8,
addr: u8,
write_count: u8,
read_count: u8,
read_count1: u8,
reserved: u8,
data: [u8; USB_MAX_SIZE - 6],
}
/// Wire format of USB packet containing I2C transaction response.
#[derive(AsBytes, FromBytes, Debug)]
#[repr(C)]
struct RspTransfer {
status_code: u16,
reserved: u16,
data: [u8; USB_MAX_SIZE - 4],
}
impl RspTransfer {
fn new() -> Self {
Self {
status_code: 0,
reserved: 0,
data: [0; USB_MAX_SIZE - 4],
}
}
}
impl HyperdebugI2cBus {
pub fn open(inner: &Rc<Inner>, i2c_interface: &BulkInterface, idx: u8) -> Result<Self> {
let mut usb_handle = inner.usb_device.borrow_mut();
// Exclusively claim I2C interface, preparing for bulk transfers.
usb_handle.claim_interface(i2c_interface.interface)?;
Ok(Self {
inner: Rc::clone(inner),
interface: *i2c_interface,
bus_idx: idx,
max_read_size: 0x8000,
max_write_size: 0x1000,
})
}
/// 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<()> {
ensure!(
rbuf.len() < self.max_read_size,
I2cError::InvalidDataLength(rbuf.len())
);
ensure!(
wbuf.len() < self.max_write_size,
I2cError::InvalidDataLength(wbuf.len())
);
let mut index = if rbuf.len() < 128 {
// Short format header
let mut req = CmdTransferShort {
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, wbuf.len());
req.data[..databytes].clone_from_slice(&wbuf[..databytes]);
self.usb_write_bulk(&req.as_bytes()[..4 + databytes])?;
databytes
} else {
// Long format header
let mut req = CmdTransferLong {
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_count1: (rbuf.len() >> 7) as u8,
reserved: 0,
data: [0; USB_MAX_SIZE - 6],
};
let databytes = cmp::min(USB_MAX_SIZE - 6, wbuf.len());
req.data[..databytes].clone_from_slice(&wbuf[..databytes]);
self.usb_write_bulk(&req.as_bytes()[..6 + databytes])?;
databytes
};
// Transmit any more data without further header.
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 bytecount = self.usb_read_bulk(resp.as_bytes_mut())?;
ensure!(
bytecount >= 4,
TransportError::CommunicationError("Unrecognized 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;
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(())
}
/// Send one USB packet.
fn usb_write_bulk(&self, buf: &[u8]) -> Result<()> {
self.inner
.usb_device
.borrow()
.write_bulk(self.interface.out_endpoint, buf)?;
Ok(())
}
/// Receive one USB packet.
fn usb_read_bulk(&self, buf: &mut [u8]) -> Result<usize> {
self.inner
.usb_device
.borrow()
.read_bulk(self.interface.in_endpoint, buf)
}
}
impl Bus for HyperdebugI2cBus {
fn run_transaction(&self, addr: u8, mut transaction: &mut [Transfer]) -> Result<()> {
while !transaction.is_empty() {
match transaction {
[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
// 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)?;
// Skip two steps ahead, as two items were processed.
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 [])?;
transaction = &mut transaction[1..];
}
[Transfer::Read(rbuf), ..] => {
ensure!(
rbuf.len() <= self.max_read_size,
I2cError::InvalidDataLength(rbuf.len())
);
self.transmit_then_receive(addr, &[], rbuf)?;
transaction = &mut transaction[1..];
}
[] => (),
}
}
Ok(())
}
}