blob: b8de05918f132ac0f9b3b4b57b37f1e36d3bfc2b [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,RequestType,Recipient};
use std::mem::size_of;
use std::rc::Rc;
use zerocopy::{AsBytes, FromBytes};
use crate::io::spi::{SpiError, Target, Transfer, TransferMode};
use crate::transport::hyperdebug::{Hyperdebug, Inner, BulkInterface, Error};
pub struct HyperdebugSpiTarget {
inner: Rc<Inner>,
interface: BulkInterface,
_target_idx: u8,
max_chunk_size: usize,
}
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_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],
}
}
}
impl HyperdebugSpiTarget {
pub fn open(hyperdebug: &Hyperdebug, idx: u8) -> Result<Self> {
let mut usb_handle = hyperdebug.inner.usb_device.borrow_mut();
// Tell HyperDebug to enable SPI bridge.
usb_handle.write_control(
rusb::request_type(Direction::Out, RequestType::Vendor, Recipient::Interface),
USB_SPI_REQ_ENABLE,
0 /* wValue */,
hyperdebug.spi_interface.interface as u16,
&mut [])?;
// Exclusively claim SPI interface, preparing for bulk transfers.
usb_handle.claim_interface(hyperdebug.spi_interface.interface)?;
// Initial bulk request/response to query capabilities.
usb_handle.write_bulk(hyperdebug.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(hyperdebug.spi_interface.in_endpoint, resp.as_bytes_mut())?;
ensure!(
rc == size_of::<RspUsbSpiConfig>(),
Error::CommunicationError("Unrecognized reponse to GET_USB_SPI_CONFIG")
);
ensure!(
resp.packet_id == USB_SPI_PKT_ID_RSP_USB_SPI_CONFIG,
Error::CommunicationError("Unrecognized reponse to GET_USB_SPI_CONFIG")
);
// Verify that interface supports concurrent read/write.
ensure!(
(resp.feature_bitmap & 0x0001) != 0,
Error::CommunicationError("HyperDebug does not support bidirectional SPI")
);
Ok(Self {
inner: Rc::clone(&hyperdebug.inner),
interface: hyperdebug.spi_interface,
_target_idx: idx,
max_chunk_size: std::cmp::min(resp.max_write_chunk, resp.max_read_chunk) as usize,
})
}
/// 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(&mut resp.as_bytes_mut())?;
ensure!(
bytecount >= 4,
Error::CommunicationError("Unrecognized reponse to TRANSFER_START")
);
ensure!(
resp.packet_id == USB_SPI_PKT_ID_RSP_TRANSFER_START,
Error::CommunicationError("Unrecognized reponse to TRANSFER_START")
);
ensure!(resp.status_code == 0, Error::CommunicationError("SPI error"));
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(&mut resp.as_bytes_mut())?;
ensure!(
bytecount > 4,
Error::CommunicationError("Unrecognized reponse to TRANSFER_START")
);
ensure!(
resp.packet_id == USB_SPI_PKT_ID_RSP_TRANSFER_CONTINUE,
Error::CommunicationError("Unrecognized reponse to TRANSFER_START")
);
ensure!(
resp.data_index == index as u16,
Error::CommunicationError("Unexpected byte index in reponse to TRANSFER_START")
);
let databytes = bytecount - 4;
rbuf[index..index + databytes].clone_from_slice(&resp.data[0..0 + databytes]);
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> {
Ok(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> {
todo!();
}
fn set_max_speed(&self, _frequency: u32) -> Result<()> {
log::info!(
"Setting of SPI speed not implemented for HyperDebug, ignoring\n",
);
Ok(())
}
fn get_max_transfer_count(&self) -> usize {
// The protocol imposes no limits to the number of Transfers
// in a transaction.
usize::MAX
}
fn max_chunk_size(&self) -> usize {
self.max_chunk_size
}
fn run_transaction(&self, transaction: &mut [Transfer]) -> Result<()> {
let mut idx: usize = 0;
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;
}
Ok(())
}
}