use crate::channel::Channel;
use crate::error::{Error, Result};
use crate::maybe_future::{NonWasmSend, Op};
use crate::protocol::nios::NiosPacketError;
use nusb::transfer::{
Buffer, Bulk, Completion, ControlIn, ControlOut, ControlType, EndpointDirection, In, Out,
Recipient, TransferError,
};
use nusb::{Device, Endpoint, Interface, MaybeFuture, Speed};
use std::future::Future;
use std::num::NonZero;
use std::task::{Context, Poll};
use std::time::Duration;
pub const CONTROL_ENDPOINT_OUT: u8 = 0x02;
pub const CONTROL_ENDPOINT_IN: u8 = 0x82;
pub const STREAM_ENDPOINT_RX: u8 = 0x81;
pub const STREAM_ENDPOINT_TX: u8 = 0x01;
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum UsbAltSetting {
Null = 0,
RfLink = 1,
SpiFlash = 2,
Config = 3,
}
impl TryFrom<u8> for UsbAltSetting {
type Error = u8;
fn try_from(value: u8) -> std::result::Result<Self, Self::Error> {
match value {
0 => Ok(Self::Null),
1 => Ok(Self::RfLink),
2 => Ok(Self::SpiFlash),
3 => Ok(Self::Config),
_ => Err(value),
}
}
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum VendorRequest {
QueryFpgaStatus = 1,
BeginProg = 2,
RfRx = 4,
RfTx = 5,
QueryDeviceReady = 6,
QueryFlashId = 7,
QueryFpgaSource = 8,
FlashRead = 100,
FlashWrite = 101,
FlashErase = 102,
Reset = 105,
ReadPageBuffer = 107,
WritePageBuffer = 108,
ReadCalCache = 110,
SetLoopback = 113,
GetLoopback = 114,
ReadLogEntry = 115,
}
const TIMEOUT: Duration = Duration::from_secs(3);
const RELEASE_TIMEOUT: Duration = Duration::from_secs(5);
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StringDescriptors {
Manufacturer = 0x1,
Product,
Serial,
Fx3Firmware,
}
pub trait DeviceCommands {
fn get_supported_languages(&self) -> impl MaybeFuture<Output = Result<Vec<u16>>>;
fn get_string_descriptor_simple(
&self,
descriptor_index: NonZero<u8>,
) -> impl MaybeFuture<Output = Result<String>>;
fn serial(&self) -> impl MaybeFuture<Output = Result<String>>;
fn manufacturer(&self) -> impl MaybeFuture<Output = Result<String>>;
fn product(&self) -> impl MaybeFuture<Output = Result<String>>;
}
impl DeviceCommands for Device {
fn get_supported_languages(&self) -> impl MaybeFuture<Output = Result<Vec<u16>>> {
self.get_string_descriptor_supported_languages(TIMEOUT)
.map_ok(|languages| languages.collect())
.map_err(Error::from)
}
fn get_string_descriptor_simple(
&self,
descriptor_index: NonZero<u8>,
) -> impl MaybeFuture<Output = Result<String>> {
self.get_string_descriptor(descriptor_index, 0x409, TIMEOUT)
.map_err(Error::from)
}
fn serial(&self) -> impl MaybeFuture<Output = Result<String>> {
self.get_string_descriptor_simple(
NonZero::new(StringDescriptors::Serial as u8)
.expect("Serial descriptor index is non-zero"),
)
}
fn manufacturer(&self) -> impl MaybeFuture<Output = Result<String>> {
self.get_string_descriptor_simple(
NonZero::new(StringDescriptors::Manufacturer as u8)
.expect("Manufacturer descriptor index is non-zero"),
)
}
fn product(&self) -> impl MaybeFuture<Output = Result<String>> {
self.get_string_descriptor_simple(
NonZero::new(StringDescriptors::Product as u8)
.expect("Product descriptor index is non-zero"),
)
}
}
pub trait BladeRf1DeviceCommands: DeviceCommands {
fn fx3_firmware_version(&self) -> impl MaybeFuture<Output = Result<String>>;
}
impl BladeRf1DeviceCommands for Device {
fn fx3_firmware_version(&self) -> impl MaybeFuture<Output = Result<String>> {
self.get_string_descriptor_simple(
NonZero::new(StringDescriptors::Fx3Firmware as u8)
.expect("Fx3Firmware descriptor index is non-zero"),
)
}
}
pub trait UsbInterfaceCommands {
fn usb_vendor_cmd_int(&self, cmd: VendorRequest) -> impl MaybeFuture<Output = Result<u32>>;
fn usb_vendor_cmd_int_w_value(
&self,
cmd: VendorRequest,
w_value: u16,
) -> impl MaybeFuture<Output = Result<u32>>;
fn usb_vendor_cmd_int_w_index(
&self,
cmd: VendorRequest,
w_index: u16,
) -> impl MaybeFuture<Output = Result<u32>>;
fn usb_vendor_cmd_out_w_index(
&self,
cmd: VendorRequest,
w_index: u16,
data: &[u8],
) -> impl MaybeFuture<Output = Result<()>>;
fn usb_vendor_cmd_in_w_index_data(
&self,
cmd: VendorRequest,
w_index: u16,
buf: &mut [u8],
) -> impl MaybeFuture<Output = Result<()>>;
}
impl UsbInterfaceCommands for Interface {
fn usb_vendor_cmd_int(&self, cmd: VendorRequest) -> impl MaybeFuture<Output = Result<u32>> {
vendor_cmd_in_u32(self, cmd, 0, 0)
}
fn usb_vendor_cmd_int_w_value(
&self,
cmd: VendorRequest,
w_value: u16,
) -> impl MaybeFuture<Output = Result<u32>> {
vendor_cmd_in_u32(self, cmd, w_value, 0)
}
fn usb_vendor_cmd_int_w_index(
&self,
cmd: VendorRequest,
w_index: u16,
) -> impl MaybeFuture<Output = Result<u32>> {
vendor_cmd_in_u32(self, cmd, 0, w_index)
}
fn usb_vendor_cmd_out_w_index(
&self,
cmd: VendorRequest,
w_index: u16,
data: &[u8],
) -> impl MaybeFuture<Output = Result<()>> {
let pkt = ControlOut {
control_type: ControlType::Vendor,
recipient: Recipient::Device,
request: cmd as u8,
value: 0,
index: w_index,
data,
};
self.control_out(pkt, TIMEOUT).map_err(Error::from)
}
fn usb_vendor_cmd_in_w_index_data(
&self,
cmd: VendorRequest,
w_index: u16,
buf: &mut [u8],
) -> impl MaybeFuture<Output = Result<()>> {
Op::new(async move {
let length = u16::try_from(buf.len())
.map_err(|_| Error::Argument("buffer length exceeds u16 maximum".into()))?;
let vec = vendor_cmd_in(self, cmd, 0, w_index, length).await?;
let copy_len = buf.len().min(vec.len());
buf[..copy_len].copy_from_slice(&vec[..copy_len]);
Ok(())
})
}
}
fn vendor_cmd_in(
iface: &Interface,
cmd: VendorRequest,
value: u16,
index: u16,
length: u16,
) -> impl MaybeFuture<Output = Result<Vec<u8>>> {
let pkt = ControlIn {
control_type: ControlType::Vendor,
recipient: Recipient::Device,
request: cmd as u8,
value,
index,
length,
};
iface.control_in(pkt, TIMEOUT).map(move |response| {
let vec = response?;
if length as usize >= 4 && vec.len() < 4 {
return Err(Error::UsbControlResponseTooShort {
expected: 4,
actual: vec.len(),
});
}
Ok(vec)
})
}
fn vendor_cmd_in_u32(
iface: &Interface,
cmd: VendorRequest,
value: u16,
index: u16,
) -> impl MaybeFuture<Output = Result<u32>> {
vendor_cmd_in(iface, cmd, value, index, 4)
.map_ok(|vec| u32::from_le_bytes(vec[0..4].try_into().unwrap()))
}
pub trait BladeRf1UsbInterfaceCommands: UsbInterfaceCommands {
fn usb_enable_module(
&self,
channel: Channel,
enable: bool,
) -> impl MaybeFuture<Output = Result<()>>;
fn usb_get_firmware_loopback(&self) -> impl MaybeFuture<Output = Result<bool>>;
fn usb_device_reset(&self) -> impl MaybeFuture<Output = Result<()>>;
fn usb_is_firmware_ready(&self) -> impl MaybeFuture<Output = Result<bool>>;
fn usb_is_fpga_configured(&self) -> impl MaybeFuture<Output = Result<bool>>;
fn usb_begin_fpga_prog(&self) -> impl MaybeFuture<Output = Result<()>>;
fn usb_bulk_out(
&self,
endpoint: u8,
data: &[u8],
timeout: Duration,
) -> impl MaybeFuture<Output = Result<()>>;
}
impl BladeRf1UsbInterfaceCommands for Interface {
fn usb_enable_module(
&self,
channel: Channel,
enable: bool,
) -> impl MaybeFuture<Output = Result<()>> {
let cmd = if channel.is_rx() {
VendorRequest::RfRx
} else {
VendorRequest::RfTx
};
self.usb_vendor_cmd_int_w_value(cmd, enable as u16)
.map_ok(move |fx3_ret| {
if fx3_ret != 0 {
log::warn!(
"usb_enable_module({channel:?}, {enable}): firmware returned {fx3_ret:#x}"
);
}
})
}
fn usb_get_firmware_loopback(&self) -> impl MaybeFuture<Output = Result<bool>> {
self.usb_vendor_cmd_int(VendorRequest::GetLoopback)
.map_ok(|result| result != 0)
}
fn usb_device_reset(&self) -> impl MaybeFuture<Output = Result<()>> {
let pkt = ControlOut {
control_type: ControlType::Vendor,
recipient: Recipient::Device,
request: VendorRequest::Reset as u8,
value: 0x0,
index: 0x0,
data: &[],
};
self.control_out(pkt, TIMEOUT).map_err(Error::from)
}
fn usb_is_firmware_ready(&self) -> impl MaybeFuture<Output = Result<bool>> {
self.usb_vendor_cmd_int(VendorRequest::QueryDeviceReady)
.map_ok(|result| result != 0)
}
fn usb_is_fpga_configured(&self) -> impl MaybeFuture<Output = Result<bool>> {
self.usb_vendor_cmd_int(VendorRequest::QueryFpgaStatus)
.map(|result| match result? {
0 => Ok(false),
1 => Ok(true),
_ => Err(Error::BoardState("unexpected FPGA status response")),
})
}
fn usb_begin_fpga_prog(&self) -> impl MaybeFuture<Output = Result<()>> {
self.usb_vendor_cmd_int(VendorRequest::BeginProg)
.map(|result| {
if result? != 0 {
Err(Error::BoardState("BEGIN_PROG returned non-zero status"))
} else {
Ok(())
}
})
}
fn usb_bulk_out(
&self,
endpoint: u8,
data: &[u8],
timeout: Duration,
) -> impl MaybeFuture<Output = Result<()>> {
Op::new(async move {
let mut ep = self
.endpoint::<Bulk, Out>(endpoint)
.map_err(Error::EndpointBusy)?;
let mut buf = ep.allocate(data.len());
buf.extend_from_slice(data);
ep.submit(buf);
let completion = next_complete(&mut ep, timeout).await?;
completion.status?;
Ok(())
})
}
}
pub(crate) trait BulkEndpoint: NonWasmSend {
fn address(&self) -> u8;
fn max_packet_size(&self) -> usize;
fn allocate(&self, len: usize) -> Buffer;
fn submit(&mut self, buffer: Buffer);
fn pending(&self) -> usize;
fn poll_next_complete(&mut self, cx: &mut Context<'_>) -> Poll<Completion>;
#[cfg(not(target_arch = "wasm32"))]
fn wait_next_complete(&mut self, timeout: Duration) -> Option<Completion>;
#[cfg(not(target_arch = "wasm32"))]
fn cancel_all(&mut self);
fn clear_halt(&mut self) -> impl MaybeFuture<Output = std::result::Result<(), nusb::Error>>;
fn next_complete(&mut self) -> impl Future<Output = Completion> + NonWasmSend + '_ {
std::future::poll_fn(|cx| self.poll_next_complete(cx))
}
}
impl<Dir: EndpointDirection> BulkEndpoint for Endpoint<Bulk, Dir> {
fn address(&self) -> u8 {
self.endpoint_address()
}
fn max_packet_size(&self) -> usize {
Endpoint::max_packet_size(self)
}
fn allocate(&self, len: usize) -> Buffer {
Endpoint::allocate(self, len)
}
fn submit(&mut self, buffer: Buffer) {
Endpoint::submit(self, buffer)
}
fn pending(&self) -> usize {
Endpoint::pending(self)
}
fn poll_next_complete(&mut self, cx: &mut Context<'_>) -> Poll<Completion> {
Endpoint::poll_next_complete(self, cx)
}
#[cfg(not(target_arch = "wasm32"))]
fn wait_next_complete(&mut self, timeout: Duration) -> Option<Completion> {
Endpoint::wait_next_complete(self, timeout)
}
#[cfg(not(target_arch = "wasm32"))]
fn cancel_all(&mut self) {
Endpoint::cancel_all(self)
}
fn clear_halt(&mut self) -> impl MaybeFuture<Output = std::result::Result<(), nusb::Error>> {
Endpoint::clear_halt(self)
}
}
pub(crate) async fn next_complete<E: BulkEndpoint>(
ep: &mut E,
timeout: Duration,
) -> Result<Completion> {
#[cfg(not(target_arch = "wasm32"))]
{
match crate::maybe_future::timeout(timeout, ep.next_complete()).await {
Some(completion) => Ok(completion),
None => {
ep.cancel_all();
drop(drain_pending(ep, RELEASE_TIMEOUT).await);
Err(Error::Timeout)
}
}
}
#[cfg(target_arch = "wasm32")]
{
let _ = timeout;
Ok(ep.next_complete().await)
}
}
pub(crate) async fn drain_pending<E: BulkEndpoint>(ep: &mut E, deadline: Duration) -> Vec<Buffer> {
let mut buffers = Vec::with_capacity(ep.pending());
#[cfg(not(target_arch = "wasm32"))]
let mut remaining = deadline;
#[cfg(target_arch = "wasm32")]
let _ = deadline;
while ep.pending() > 0 {
#[cfg(not(target_arch = "wasm32"))]
let completion = {
let start = std::time::Instant::now();
let Some(completion) =
crate::maybe_future::timeout(remaining, ep.next_complete()).await
else {
log::warn!(
"timeout draining endpoint {:#04x}, {} transfers remain",
ep.address(),
ep.pending()
);
break;
};
remaining = remaining.saturating_sub(start.elapsed());
completion
};
#[cfg(target_arch = "wasm32")]
let completion = ep.next_complete().await;
match completion.status {
Ok(()) | Err(TransferError::Cancelled) => {}
Err(e) => log::warn!(
"transfer error draining endpoint {:#04x}: {e}",
ep.address()
),
}
buffers.push(completion.buffer);
}
buffers
}
struct NiosEndpoints {
ep_out: Endpoint<Bulk, Out>,
ep_in: Endpoint<Bulk, In>,
buf_out: Option<Buffer>,
buf_in: Option<Buffer>,
}
pub struct UsbTransport {
interface: Interface,
nios_endpoints: Option<NiosEndpoints>,
current_alt_setting: UsbAltSetting,
speed: Speed,
}
impl UsbTransport {
const NIOS_PKT_SIZE: usize = 16;
pub fn new(interface: Interface, speed: Speed) -> Self {
let current_alt_setting =
UsbAltSetting::try_from(interface.get_alt_setting()).unwrap_or(UsbAltSetting::Null);
Self {
interface,
nios_endpoints: None,
current_alt_setting,
speed,
}
}
pub fn interface(&self) -> &Interface {
&self.interface
}
pub fn current_alt_setting(&self) -> UsbAltSetting {
self.current_alt_setting
}
pub fn speed(&self) -> Speed {
self.speed
}
pub fn usb_change_setting(
&mut self,
setting: UsbAltSetting,
) -> impl MaybeFuture<Output = Result<()>> {
Op::new(async move {
self.release_endpoints().await;
self.interface.set_alt_setting(setting as u8).await?;
self.current_alt_setting = setting;
Ok(())
})
}
pub fn usb_set_firmware_loopback(
&mut self,
enable: bool,
) -> impl MaybeFuture<Output = Result<()>> {
Op::new(async move {
let fx3_ret = self
.interface
.usb_vendor_cmd_int_w_value(VendorRequest::SetLoopback, enable as u16)
.await?;
if fx3_ret != 0 {
log::warn!("usb_set_firmware_loopback({enable}): firmware returned {fx3_ret:#x}");
}
self.usb_change_setting(UsbAltSetting::Null).await?;
self.usb_change_setting(UsbAltSetting::RfLink).await?;
Ok(())
})
}
pub fn release_endpoints(&mut self) -> impl MaybeFuture<Output = ()> {
Op::new(async move {
if let Some(mut endpoints) = self.nios_endpoints.take() {
#[cfg(not(target_arch = "wasm32"))]
{
endpoints.ep_out.cancel_all();
endpoints.ep_in.cancel_all();
}
drop(drain_pending(&mut endpoints.ep_out, RELEASE_TIMEOUT).await);
drop(drain_pending(&mut endpoints.ep_in, RELEASE_TIMEOUT).await);
}
})
}
fn ensure_nios_endpoints(&mut self) -> Result<&mut NiosEndpoints> {
if self.nios_endpoints.is_none() {
let ep_out = self
.interface
.endpoint::<Bulk, Out>(CONTROL_ENDPOINT_OUT)
.map_err(Error::EndpointBusy)?;
let ep_in = self
.interface
.endpoint::<Bulk, In>(CONTROL_ENDPOINT_IN)
.map_err(Error::EndpointBusy)?;
let buf_out = Some(ep_out.allocate(Self::NIOS_PKT_SIZE));
let buf_in = Some(ep_in.allocate(ep_in.max_packet_size()));
self.nios_endpoints = Some(NiosEndpoints {
ep_out,
ep_in,
buf_out,
buf_in,
});
}
self.nios_endpoints
.as_mut()
.ok_or(Error::EndpointNotAvailable)
}
pub fn out_buffer(&mut self) -> Result<&mut [u8]> {
let endpoints = self.ensure_nios_endpoints()?;
let buf = endpoints
.buf_out
.as_mut()
.ok_or(Error::EndpointNotAvailable)?;
buf.clear();
buf.extend_fill(Self::NIOS_PKT_SIZE, 0);
Ok(buf)
}
pub fn submit(
&mut self,
timeout: Option<Duration>,
) -> impl MaybeFuture<Output = Result<&[u8]>> {
Op::new(async move {
let t = timeout.unwrap_or(TIMEOUT);
let endpoints = self.ensure_nios_endpoints()?;
if let Err(e) = Self::transact(endpoints, t).await {
if matches!(e, Error::Timeout) {
self.release_endpoints().await;
}
return Err(e);
}
let in_buf = self
.nios_endpoints
.as_ref()
.and_then(|e| e.buf_in.as_ref())
.ok_or(Error::EndpointNotAvailable)?;
let in_len = in_buf.len();
if in_len < Self::NIOS_PKT_SIZE {
return Err(NiosPacketError::InvalidSize(in_len).into());
}
Ok(&in_buf[..Self::NIOS_PKT_SIZE])
})
}
async fn transact(endpoints: &mut NiosEndpoints, timeout: Duration) -> Result<()> {
let buf_out = endpoints
.buf_out
.take()
.ok_or(Error::EndpointNotAvailable)?;
log::trace!("submit: OUT buffer len = {}", buf_out.len());
endpoints.ep_out.submit(buf_out);
let response = next_complete(&mut endpoints.ep_out, timeout).await?;
endpoints.buf_out = Some(response.buffer);
response.status?;
let mut buf_in = endpoints.buf_in.take().ok_or(Error::EndpointNotAvailable)?;
buf_in.set_requested_len(endpoints.ep_in.max_packet_size());
endpoints.ep_in.submit(buf_in);
let response = next_complete(&mut endpoints.ep_in, timeout).await?;
endpoints.buf_in = Some(response.buffer);
response.status?;
Ok(())
}
pub fn acquire_streaming_rx_endpoint(&self) -> Result<Endpoint<Bulk, In>> {
self.interface
.endpoint::<Bulk, In>(STREAM_ENDPOINT_RX)
.map_err(Error::EndpointBusy)
}
pub fn acquire_streaming_tx_endpoint(&self) -> Result<Endpoint<Bulk, Out>> {
self.interface
.endpoint::<Bulk, Out>(STREAM_ENDPOINT_TX)
.map_err(Error::EndpointBusy)
}
}