blob: 9796cc54d0f98de917140c8f73ec7f2b01899521 [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::{ensure, Result};
use rusb::{Direction, Recipient, RequestType};
use std::cell::Cell;
use std::mem::size_of;
use std::rc::Rc;
use zerocopy::{AsBytes, FromBytes};
use crate::io::spi::{
AssertChipSelect, SpiError, Target, TargetChipDeassert, Transfer, TransferMode,
};
use crate::transport::hyperdebug::{BulkInterface, Inner};
use crate::transport::TransportError;
pub struct HyperdebugSpiTarget {
inner: Rc<Inner>,
interface: BulkInterface,
target_idx: u8,
max_chunk_size: usize,
cs_asserted_count: Cell<u32>,
}
const USB_SPI_PKT_ID_CMD_GET_USB_SPI_CONFIG: u16 = 0;
const USB_SPI_PKT_ID_RSP_USB_SPI_CONFIG: u16 = 1;
const USB_SPI_PKT_ID_CMD_TRANSFER_START: u16 = 2;
const USB_SPI_PKT_ID_CMD_TRANSFER_CONTINUE: u16 = 3;
//const USB_SPI_PKT_ID_CMD_RESTART_RESPONSE: u16 = 4;
const USB_SPI_PKT_ID_RSP_TRANSFER_START: u16 = 5;
const USB_SPI_PKT_ID_RSP_TRANSFER_CONTINUE: u16 = 6;
const USB_SPI_PKT_ID_CMD_CHIP_SELECT: u16 = 7;
const USB_SPI_PKT_ID_RSP_CHIP_SELECT: u16 = 8;
//const USB_SPI_REQ_DISABLE: u8 = 1;
const USB_SPI_REQ_ENABLE: u8 = 0;
const USB_MAX_SIZE: usize = 64;
const FULL_DUPLEX: usize = 65535;
#[derive(AsBytes, FromBytes, Debug, Default)]
#[repr(C)]
struct RspUsbSpiConfig {
packet_id: u16,
max_write_chunk: u16,
max_read_chunk: u16,
feature_bitmap: u16,
}
#[derive(AsBytes, FromBytes, Debug)]
#[repr(C)]
struct CmdTransferStart {
packet_id: u16,
write_count: u16,
read_count: u16,
data: [u8; USB_MAX_SIZE - 6],
}
impl CmdTransferStart {
fn new() -> Self {
Self {
packet_id: USB_SPI_PKT_ID_CMD_TRANSFER_START,
write_count: 0,
read_count: 0,
data: [0; USB_MAX_SIZE - 6],
}
}
}
#[derive(AsBytes, FromBytes, Debug)]
#[repr(C)]
struct CmdTransferContinue {
packet_id: u16,
data_index: u16,
data: [u8; USB_MAX_SIZE - 4],
}
impl CmdTransferContinue {
fn new() -> Self {
Self {
packet_id: USB_SPI_PKT_ID_CMD_TRANSFER_CONTINUE,
data_index: 0,
data: [0; USB_MAX_SIZE - 4],
}
}
}
#[derive(AsBytes, FromBytes, Debug)]
#[repr(C)]
struct RspTransferStart {
packet_id: u16,
status_code: u16,
data: [u8; USB_MAX_SIZE - 4],
}
impl RspTransferStart {
fn new() -> Self {
Self {
packet_id: 0,
status_code: 0,
data: [0; USB_MAX_SIZE - 4],
}
}
}
#[derive(AsBytes, FromBytes, Debug)]
#[repr(C)]
struct RspTransferContinue {
packet_id: u16,
data_index: u16,
data: [u8; USB_MAX_SIZE - 4],
}
impl RspTransferContinue {
fn new() -> Self {
Self {
packet_id: 0,
data_index: 0,
data: [0; USB_MAX_SIZE - 4],
}
}
}
#[derive(AsBytes, FromBytes, Debug)]
#[repr(C)]
struct CmdChipSelect {
packet_id: u16,
flags: u16,
}
impl CmdChipSelect {
fn new(assert_chip_select: bool) -> Self {
Self {
packet_id: USB_SPI_PKT_ID_CMD_CHIP_SELECT,
flags: u16::from(assert_chip_select),
}
}
}
#[derive(AsBytes, FromBytes, Debug, Default)]
#[repr(C)]
struct RspChipSelect {
packet_id: u16,
status_code: u16,
}
impl RspChipSelect {
fn new() -> Self {
Self {
packet_id: 0,
status_code: 0,
}
}
}
impl HyperdebugSpiTarget {
pub fn open(inner: &Rc<Inner>, spi_interface: &BulkInterface, idx: u8) -> Result<Self> {
let mut usb_handle = inner.usb_device.borrow_mut();
// Tell HyperDebug to enable SPI bridge, and to address particular SPI device.
inner.selected_spi.set(idx);
usb_handle.write_control(
rusb::request_type(Direction::Out, RequestType::Vendor, Recipient::Interface),
USB_SPI_REQ_ENABLE,
idx as u16,
spi_interface.interface as u16,
&[],
)?;
// Exclusively claim SPI interface, preparing for bulk transfers.
usb_handle.claim_interface(spi_interface.interface)?;
// Initial bulk request/response to query capabilities.
usb_handle.write_bulk(
spi_interface.out_endpoint,
&USB_SPI_PKT_ID_CMD_GET_USB_SPI_CONFIG.to_le_bytes(),
)?;
let mut resp: RspUsbSpiConfig = Default::default();
let rc = usb_handle.read_bulk(spi_interface.in_endpoint, resp.as_bytes_mut())?;
ensure!(
rc == size_of::<RspUsbSpiConfig>(),
TransportError::CommunicationError(
"Unrecognized reponse to GET_USB_SPI_CONFIG".to_string()
)
);
ensure!(
resp.packet_id == USB_SPI_PKT_ID_RSP_USB_SPI_CONFIG,
TransportError::CommunicationError(
"Unrecognized reponse to GET_USB_SPI_CONFIG".to_string()
)
);
// Verify that interface supports concurrent read/write.
ensure!(
(resp.feature_bitmap & 0x0001) != 0,
TransportError::CommunicationError(
"HyperDebug does not support bidirectional SPI".to_string()
)
);
// Round the max chunk size down to the nearest power of two.
let chunk =
std::cmp::min(resp.max_write_chunk, resp.max_read_chunk).next_power_of_two() / 2;
Ok(Self {
inner: Rc::clone(inner),
interface: *spi_interface,
target_idx: idx,
max_chunk_size: chunk as usize,
cs_asserted_count: Cell::new(0),
})
}
/// Instruct HyperDebug device which SPI bus subsequent transactions should be forwarded to.
fn select_my_spi_bus(&self) -> Result<()> {
if self.inner.selected_spi.get() != self.target_idx {
self.inner.selected_spi.set(self.target_idx);
self.inner.usb_device.borrow().write_control(
rusb::request_type(Direction::Out, RequestType::Vendor, Recipient::Interface),
USB_SPI_REQ_ENABLE,
self.target_idx as u16,
self.interface.interface as u16,
&[],
)?;
}
Ok(())
}
/// Transmit data for a single SPI operation, using one or more USB packets.
fn transmit(&self, wbuf: &[u8], rbuf_len: usize) -> Result<()> {
let mut req = CmdTransferStart::new();
req.write_count = wbuf.len() as u16;
req.read_count = rbuf_len as u16;
let databytes = std::cmp::min(USB_MAX_SIZE - 6, wbuf.len());
req.data[0..databytes].clone_from_slice(&wbuf[0..databytes]);
self.usb_write_bulk(&req.as_bytes()[0..6 + 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();
let bytecount = self.usb_read_bulk(resp.as_bytes_mut())?;
ensure!(
bytecount >= 4,
TransportError::CommunicationError(
"Unrecognized reponse to TRANSFER_START".to_string()
)
);
ensure!(
resp.packet_id == USB_SPI_PKT_ID_RSP_TRANSFER_START,
TransportError::CommunicationError(
"Unrecognized reponse to TRANSFER_START".to_string()
)
);
ensure!(
resp.status_code == 0,
TransportError::CommunicationError(format!("SPI error ({})", resp.status_code))
);
let databytes = bytecount - 4;
rbuf[0..databytes].clone_from_slice(&resp.data[0..databytes]);
let mut index = databytes;
while index < rbuf.len() {
let mut resp = RspTransferContinue::new();
let bytecount = self.usb_read_bulk(resp.as_bytes_mut())?;
ensure!(
bytecount > 4,
TransportError::CommunicationError(
"Unrecognized reponse to TRANSFER_START".to_string()
)
);
ensure!(
resp.packet_id == USB_SPI_PKT_ID_RSP_TRANSFER_CONTINUE,
TransportError::CommunicationError(
"Unrecognized reponse to TRANSFER_START".to_string()
)
);
ensure!(
resp.data_index == index as u16,
TransportError::CommunicationError(
"Unexpected byte index in reponse to TRANSFER_START".to_string()
)
);
let databytes = bytecount - 4;
rbuf[index..index + databytes].clone_from_slice(&resp.data[0..databytes]);
index += databytes;
}
Ok(())
}
/// Request assertion or deassertion of chip select
fn do_assert_cs(&self, assert: bool) -> Result<()> {
let mut count = self.cs_asserted_count.get();
if assert {
if count == 0 {
self._do_assert_cs(assert)?;
}
count += 1;
} else {
if count == 1 {
self._do_assert_cs(assert)?;
}
count -= 1;
}
self.cs_asserted_count.set(count);
Ok(())
}
fn _do_assert_cs(&self, assert: bool) -> Result<()> {
let req = CmdChipSelect::new(assert);
self.usb_write_bulk(req.as_bytes())?;
let mut resp = RspChipSelect::new();
let bytecount = self.usb_read_bulk(resp.as_bytes_mut())?;
ensure!(
bytecount >= 4,
TransportError::CommunicationError("Unrecognized reponse to CHIP_SELECT".to_string())
);
ensure!(
resp.packet_id == USB_SPI_PKT_ID_RSP_CHIP_SELECT,
TransportError::CommunicationError("Unrecognized reponse to CHIP_SELECT".to_string())
);
ensure!(
resp.status_code == 0,
TransportError::CommunicationError("SPI error".to_string())
);
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 Target for HyperdebugSpiTarget {
fn get_transfer_mode(&self) -> Result<TransferMode> {
Ok(TransferMode::Mode0)
}
fn set_transfer_mode(&self, _mode: TransferMode) -> Result<()> {
todo!();
}
fn get_bits_per_word(&self) -> Result<u32> {
Ok(8)
}
fn set_bits_per_word(&self, bits_per_word: u32) -> Result<()> {
match bits_per_word {
8 => Ok(()),
_ => Err(SpiError::InvalidWordSize(bits_per_word).into()),
}
}
fn get_max_speed(&self) -> Result<u32> {
let mut buf = String::new();
let mut buf2 = String::new();
let captures = self
.inner
.cmd_one_line_output_match(
&format!("spi info {}", &self.target_idx),
&super::SPI_REGEX,
&mut buf,
)
.or_else(|_| {
self.inner.cmd_one_line_output_match(
&format!("spiget {}", &self.target_idx),
&super::SPI_REGEX,
&mut buf2,
)
})?;
Ok(captures.get(3).unwrap().as_str().parse().unwrap())
}
fn set_max_speed(&self, frequency: u32) -> Result<()> {
self.inner
.cmd_no_output(&format!("spi set speed {} {}", &self.target_idx, frequency))
.or_else(|_| {
self.inner
.cmd_no_output(&format!("spisetspeed {} {}", &self.target_idx, frequency))
})
}
fn get_max_transfer_count(&self) -> Result<usize> {
// The protocol imposes no limits to the number of Transfers
// in a transaction.
Ok(usize::MAX)
}
fn max_chunk_size(&self) -> Result<usize> {
Ok(self.max_chunk_size)
}
fn run_transaction(&self, transaction: &mut [Transfer]) -> Result<()> {
let mut idx: usize = 0;
self.select_my_spi_bus()?;
self.do_assert_cs(true)?;
while idx < transaction.len() {
match &mut transaction[idx..] {
[Transfer::Write(wbuf), Transfer::Read(rbuf), ..] => {
// Hyperdebug can do SPI write followed by SPI 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_chunk_size,
SpiError::InvalidDataLength(wbuf.len())
);
ensure!(
rbuf.len() <= self.max_chunk_size,
SpiError::InvalidDataLength(rbuf.len())
);
self.transmit(wbuf, rbuf.len())?;
self.receive(rbuf)?;
// Skip two steps ahead, as two items were processed.
idx += 2;
continue;
}
[Transfer::Write(wbuf), ..] => {
ensure!(
wbuf.len() <= self.max_chunk_size,
SpiError::InvalidDataLength(wbuf.len())
);
self.transmit(wbuf, 0)?;
self.receive(&mut [])?;
}
[Transfer::Read(rbuf), ..] => {
ensure!(
rbuf.len() <= self.max_chunk_size,
SpiError::InvalidDataLength(rbuf.len())
);
self.transmit(&[], rbuf.len())?;
self.receive(rbuf)?;
}
[Transfer::Both(wbuf, rbuf), ..] => {
ensure!(
rbuf.len() == wbuf.len(),
SpiError::MismatchedDataLength(wbuf.len(), rbuf.len())
);
ensure!(
wbuf.len() <= self.max_chunk_size,
SpiError::InvalidDataLength(wbuf.len())
);
self.transmit(wbuf, FULL_DUPLEX)?;
self.receive(rbuf)?;
}
[] => (),
}
idx += 1;
}
self.do_assert_cs(false)?;
Ok(())
}
fn assert_cs(self: Rc<Self>) -> Result<AssertChipSelect> {
self.do_assert_cs(true)?;
Ok(AssertChipSelect::new(self))
}
}
impl TargetChipDeassert for HyperdebugSpiTarget {
fn deassert_cs(&self) {
// We cannot propagate errors through `Drop::drop()`, so panic on any error. (Logging
// would be another option.)
self.do_assert_cs(false)
.expect("Error while deasserting CS");
}
}