From 21db23b22be5ff4805124f321e3b38e0598c7ae3 Mon Sep 17 00:00:00 2001 From: Mateusz Faderewski Date: Sun, 30 Jun 2024 11:53:50 +0200 Subject: [PATCH] made libftdi backend more robust --- sw/deployer/src/sc64/error.rs | 13 - sw/deployer/src/sc64/ftdi.rs | 632 ++++++++++++++++++++++++---------- sw/deployer/src/sc64/link.rs | 126 ++++--- 3 files changed, 519 insertions(+), 252 deletions(-) diff --git a/sw/deployer/src/sc64/error.rs b/sw/deployer/src/sc64/error.rs index a554267..0702fd5 100644 --- a/sw/deployer/src/sc64/error.rs +++ b/sw/deployer/src/sc64/error.rs @@ -1,4 +1,3 @@ -use super::ftdi::FtdiError; use std::fmt::{Display, Formatter, Result}; #[derive(Debug, Clone)] @@ -27,15 +26,3 @@ impl From for Error { Error::new(format!("IO error: {}", value).as_str()) } } - -impl From for Error { - fn from(value: serialport::Error) -> Self { - Error::new(format!("SerialPort error: {}", value.description).as_str()) - } -} - -impl From for Error { - fn from(value: FtdiError) -> Self { - Error::new(format!("libftdi error: {}", value.description).as_str()) - } -} diff --git a/sw/deployer/src/sc64/ftdi.rs b/sw/deployer/src/sc64/ftdi.rs index fa99f46..4adbe4b 100644 --- a/sw/deployer/src/sc64/ftdi.rs +++ b/sw/deployer/src/sc64/ftdi.rs @@ -1,227 +1,487 @@ -pub struct FtdiError { - pub description: String, +pub struct DeviceInfo { + pub port: String, + pub serial: String, } -impl FtdiError { - fn malloc() -> FtdiError { - FtdiError { - description: format!("Couldn't allocate memory for the context"), - } - } - - fn libftdi(context: *mut libftdi1_sys::ftdi_context) -> FtdiError { - let raw = unsafe { std::ffi::CStr::from_ptr(libftdi1_sys::ftdi_get_error_string(context)) }; - FtdiError { - description: format!("{}", raw.to_str().unwrap_or("Unknown error")), - } - } +#[allow(dead_code)] +enum InterfaceIndex { + Any, + A, + B, + C, + D, } -impl From for std::io::Error { - fn from(value: FtdiError) -> Self { - return Self::new(std::io::ErrorKind::Other, value.description); - } +#[allow(dead_code)] +enum ModuleDetachMode { + AutoDetach, + DontDetach, + AutoDetachReattach, } -struct Context { +struct ModemStatus { + dsr: bool, +} + +struct Wrapper { context: *mut libftdi1_sys::ftdi_context, -} - -impl Context { - fn new() -> Result { - let ctx = unsafe { libftdi1_sys::ftdi_new() }; - if ctx.is_null() { - return Err(FtdiError::malloc()); - } - let context = Context { context: ctx }; - let result = unsafe { - libftdi1_sys::ftdi_set_interface( - context.get(), - libftdi1_sys::ftdi_interface::INTERFACE_A, - ) - }; - context.check_result(result)?; - Ok(context) - } - - fn get(&self) -> *mut libftdi1_sys::ftdi_context { - return self.context; - } - - fn check_result(&self, result: i32) -> Result<(), FtdiError> { - if result < 0 { - return Err(FtdiError::libftdi(self.get())); - } - Ok(()) - } -} - -impl Drop for Context { - fn drop(&mut self) { - unsafe { libftdi1_sys::ftdi_free(self.get()) } - } -} - -pub struct FtdiDevice { - context: Context, - read_timeout: std::time::Duration, + unclog_buffer: std::collections::VecDeque, + write_buffer: Vec, write_timeout: std::time::Duration, + read_chunksize: usize, + write_chunksize: usize, } -impl FtdiDevice { - pub fn open( - port: &str, - read_timeout: std::time::Duration, - write_timeout: std::time::Duration, - ) -> Result { - let context = Context::new()?; - unsafe { - let mode = libftdi1_sys::ftdi_module_detach_mode::AUTO_DETACH_REATACH_SIO_MODULE; - (*context.get()).module_detach_mode = mode; +impl Wrapper { + const DEFAULT_POLL_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(16); + const DEFAULT_RW_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5); + const DEFAULT_CHUNKSIZE: usize = 4096; + + fn new( + read_timeout: Option, + write_timeout: Option, + ) -> std::io::Result { + let context = unsafe { libftdi1_sys::ftdi_new() }; + if context.is_null() { + return Err(std::io::ErrorKind::OutOfMemory.into()); } - let description = std::ffi::CString::new(port).unwrap_or_default().into_raw(); - let result = unsafe { libftdi1_sys::ftdi_usb_open_string(context.get(), description) }; - context.check_result(result)?; - let result = unsafe { libftdi1_sys::ftdi_set_latency_timer(context.get(), 1) }; - context.check_result(result)?; - Ok(FtdiDevice { + let mut wrapper = Wrapper { context, - read_timeout, - write_timeout, - }) + unclog_buffer: std::collections::VecDeque::new(), + write_buffer: vec![], + write_timeout: Wrapper::DEFAULT_RW_TIMEOUT, + read_chunksize: Wrapper::DEFAULT_CHUNKSIZE, + write_chunksize: Wrapper::DEFAULT_CHUNKSIZE, + }; + wrapper.set_timeouts(read_timeout, write_timeout)?; + wrapper.read_data_set_chunksize(wrapper.read_chunksize)?; + wrapper.write_data_set_chunksize(wrapper.write_chunksize)?; + Ok(wrapper) } - pub fn set_dtr(&self, value: bool) -> Result<(), FtdiError> { - let state = if value { 1 } else { 0 }; - let result = unsafe { libftdi1_sys::ftdi_setdtr(self.context.get(), state) }; - self.context.check_result(result)?; + fn list_devices(vendor: u16, product: u16) -> std::io::Result> { + let wrapper = Wrapper::new(None, None)?; + + let mut device_list: *mut libftdi1_sys::ftdi_device_list = std::ptr::null_mut(); + let devices = unsafe { + libftdi1_sys::ftdi_usb_find_all( + wrapper.context, + &mut device_list, + vendor as i32, + product as i32, + ) + }; + + let result = if devices > 0 { + let mut list: Vec = vec![]; + + let mut serial = [0i8; 128]; + + let mut device = device_list; + let mut index = 0; + while !device.is_null() { + let result = unsafe { + libftdi1_sys::ftdi_usb_get_strings( + wrapper.context, + (*device).dev, + std::ptr::null_mut(), + 0, + std::ptr::null_mut(), + 0, + serial.as_mut_ptr(), + serial.len() as i32, + ) + }; + + if result == 0 { + list.push(DeviceInfo { + port: format!("i:0x{vendor:04X}:0x{product:04X}:{index}"), + serial: unsafe { std::ffi::CStr::from_ptr(serial.as_ptr()) } + .to_string_lossy() + .into_owned(), + }); + } + + device = unsafe { (*device).next }; + index += 1; + } + + Ok(list) + } else { + match devices { + 0 => Ok(vec![]), + -3 => Err(std::io::ErrorKind::OutOfMemory.into()), + -5 => Err(std::io::ErrorKind::BrokenPipe.into()), + -6 => Err(std::io::ErrorKind::BrokenPipe.into()), + result => Err(std::io::Error::other(format!( + "Unexpected response from ftdi_usb_find_all: {result}" + ))), + } + }; + + unsafe { libftdi1_sys::ftdi_list_free(&mut device_list) } + + result + } + + fn libusb_convert_result(&self, result: i32) -> std::io::Error { + if result == libusb1_sys::constants::LIBUSB_ERROR_OVERFLOW { + return std::io::Error::other("libusb overflow"); + } + match result { + libusb1_sys::constants::LIBUSB_ERROR_IO => std::io::ErrorKind::UnexpectedEof, + libusb1_sys::constants::LIBUSB_ERROR_INVALID_PARAM => std::io::ErrorKind::InvalidInput, + libusb1_sys::constants::LIBUSB_ERROR_ACCESS => std::io::ErrorKind::PermissionDenied, + libusb1_sys::constants::LIBUSB_ERROR_NO_DEVICE => std::io::ErrorKind::NotConnected, + libusb1_sys::constants::LIBUSB_ERROR_NOT_FOUND => std::io::ErrorKind::NotFound, + libusb1_sys::constants::LIBUSB_ERROR_BUSY => std::io::ErrorKind::WouldBlock, + libusb1_sys::constants::LIBUSB_ERROR_TIMEOUT => std::io::ErrorKind::TimedOut, + libusb1_sys::constants::LIBUSB_ERROR_PIPE => std::io::ErrorKind::BrokenPipe, + libusb1_sys::constants::LIBUSB_ERROR_INTERRUPTED => std::io::ErrorKind::Interrupted, + libusb1_sys::constants::LIBUSB_ERROR_NO_MEM => std::io::ErrorKind::OutOfMemory, + libusb1_sys::constants::LIBUSB_ERROR_NOT_SUPPORTED => std::io::ErrorKind::Unsupported, + _ => std::io::ErrorKind::Other, + } + .into() + } + + fn set_timeouts( + &mut self, + read_timeout: Option, + write_timeout: Option, + ) -> std::io::Result<()> { + let read_timeout = read_timeout.unwrap_or(Wrapper::DEFAULT_RW_TIMEOUT); + let write_timeout = write_timeout.unwrap_or(Wrapper::DEFAULT_RW_TIMEOUT); + unsafe { + (*self.context).usb_read_timeout = i32::try_from(read_timeout.as_millis()) + .map_err(|_| std::io::ErrorKind::InvalidInput)?; + (*self.context).usb_write_timeout = i32::try_from(write_timeout.as_millis()) + .map_err(|_| std::io::ErrorKind::InvalidInput)?; + } + self.write_timeout = write_timeout; Ok(()) } - pub fn read_dsr(&self) -> Result { - const DSR_BIT: u16 = 1 << 5; - let mut status: u16 = 0; - let result = unsafe { - libftdi1_sys::ftdi_poll_modem_status( - self.context.get(), - std::slice::from_mut(&mut status).as_mut_ptr(), - ) + fn set_module_detach_mode(&mut self, mode: ModuleDetachMode) { + let mode = match mode { + ModuleDetachMode::AutoDetach => { + libftdi1_sys::ftdi_module_detach_mode::AUTO_DETACH_SIO_MODULE + } + ModuleDetachMode::DontDetach => { + libftdi1_sys::ftdi_module_detach_mode::DONT_DETACH_SIO_MODULE + } + ModuleDetachMode::AutoDetachReattach => { + libftdi1_sys::ftdi_module_detach_mode::AUTO_DETACH_REATACH_SIO_MODULE + } + }; + unsafe { + (*self.context).module_detach_mode = mode; }; - self.context.check_result(result)?; - Ok((status & DSR_BIT) != 0) } - pub fn read(&self, data: &mut [u8]) -> std::io::Result { - let timeout = std::time::Instant::now(); - loop { - let result = unsafe { - libftdi1_sys::ftdi_read_data( - self.context.get(), - data.as_mut_ptr(), - data.len() as i32, - ) - }; - self.context.check_result(result)?; - if result > 0 { - return Ok(result as usize); - } - if timeout.elapsed() > self.read_timeout { - return Err(std::io::ErrorKind::TimedOut.into()); - } + fn set_interface(&mut self, interface: InterfaceIndex) -> std::io::Result<()> { + let interface = match interface { + InterfaceIndex::Any => libftdi1_sys::ftdi_interface::INTERFACE_ANY, + InterfaceIndex::A => libftdi1_sys::ftdi_interface::INTERFACE_A, + InterfaceIndex::B => libftdi1_sys::ftdi_interface::INTERFACE_B, + InterfaceIndex::C => libftdi1_sys::ftdi_interface::INTERFACE_C, + InterfaceIndex::D => libftdi1_sys::ftdi_interface::INTERFACE_D, + }; + match unsafe { libftdi1_sys::ftdi_set_interface(self.context, interface) } { + 0 => Ok(()), + -1 => Err(std::io::ErrorKind::InvalidInput.into()), + -2 => Err(std::io::ErrorKind::NotConnected.into()), + -3 => Err(std::io::ErrorKind::InvalidData.into()), + result => Err(std::io::Error::other(format!( + "Unexpected response from ftdi_set_interface: {result}" + ))), } } - pub fn write(&self, data: &[u8]) -> std::io::Result { + fn usb_open_string(&mut self, description: &str) -> std::io::Result<()> { + let description = std::ffi::CString::new(description) + .unwrap_or_default() + .into_raw(); + match unsafe { libftdi1_sys::ftdi_usb_open_string(self.context, description) } { + 0 => Ok(()), + -2 => Err(std::io::ErrorKind::ConnectionRefused.into()), + -3 => Err(std::io::ErrorKind::NotFound.into()), + -4 => Err(std::io::ErrorKind::PermissionDenied.into()), + -5 => Err(std::io::ErrorKind::PermissionDenied.into()), + -6 => Err(std::io::ErrorKind::ConnectionRefused.into()), + -7 => Err(std::io::ErrorKind::ConnectionRefused.into()), + -8 => Err(std::io::ErrorKind::ConnectionRefused.into()), + -9 => Err(std::io::ErrorKind::ConnectionRefused.into()), + -10 => Err(std::io::ErrorKind::BrokenPipe.into()), + -11 => Err(std::io::ErrorKind::InvalidInput.into()), + -12 => Err(std::io::ErrorKind::InvalidData.into()), + result => Err(std::io::Error::other(format!( + "Unexpected response from ftdi_usb_open_string: {result}" + ))), + } + } + + fn set_latency_timer(&mut self, latency: Option) -> std::io::Result<()> { + let latency = u8::try_from(latency.unwrap_or(Wrapper::DEFAULT_POLL_TIMEOUT).as_millis()) + .map_err(|_| std::io::ErrorKind::InvalidInput)?; + match unsafe { libftdi1_sys::ftdi_set_latency_timer(self.context, latency) } { + 0 => Ok(()), + -1 => Err(std::io::ErrorKind::InvalidInput.into()), + -2 => Err(std::io::ErrorKind::BrokenPipe.into()), + -3 => Err(std::io::ErrorKind::NotConnected.into()), + result => Err(std::io::Error::other(format!( + "Unexpected response from ftdi_set_latency_timer: {result}" + ))), + } + } + + fn read_data_set_chunksize(&mut self, chunksize: usize) -> std::io::Result<()> { + match unsafe { + libftdi1_sys::ftdi_read_data_set_chunksize( + self.context, + u32::try_from(chunksize).map_err(|_| std::io::ErrorKind::InvalidInput)?, + ) + } { + 0 => { + self.read_chunksize = chunksize; + Ok(()) + } + -1 => Err(std::io::ErrorKind::NotConnected.into()), + result => Err(std::io::Error::other(format!( + "Unexpected response from ftdi_read_data_set_chunksize: {result}" + ))), + } + } + + fn write_data_set_chunksize(&mut self, chunksize: usize) -> std::io::Result<()> { + match unsafe { + libftdi1_sys::ftdi_write_data_set_chunksize( + self.context, + u32::try_from(chunksize).map_err(|_| std::io::ErrorKind::InvalidInput)?, + ) + } { + 0 => { + self.write_chunksize = chunksize; + self.commit_write() + } + -1 => Err(std::io::ErrorKind::NotConnected.into()), + result => Err(std::io::Error::other(format!( + "Unexpected response from ftdi_write_data_set_chunksize: {result}" + ))), + } + } + + pub fn set_dtr(&mut self, value: bool) -> std::io::Result<()> { + let state = if value { 1 } else { 0 }; + match unsafe { libftdi1_sys::ftdi_setdtr(self.context, state) } { + 0 => Ok(()), + -1 => Err(std::io::ErrorKind::BrokenPipe.into()), + -2 => Err(std::io::ErrorKind::NotConnected.into()), + result => Err(std::io::Error::other(format!( + "Unexpected response from ftdi_setdtr: {result}" + ))), + } + } + + fn poll_modem_status(&mut self) -> std::io::Result { + const DSR_BIT: u16 = 1 << 5; + + let mut status = 0; + + match unsafe { libftdi1_sys::ftdi_poll_modem_status(self.context, &mut status) } { + 0 => Ok(ModemStatus { + dsr: (status & DSR_BIT) != 0, + }), + -1 => Err(std::io::ErrorKind::BrokenPipe.into()), + -2 => Err(std::io::ErrorKind::NotConnected.into()), + result => Err(std::io::Error::other(format!( + "Unexpected response from ftdi_poll_modem_status: {result}" + ))), + } + } + + fn tcioflush(&mut self) -> std::io::Result<()> { + match unsafe { libftdi1_sys::ftdi_tcioflush(self.context) } { + 0 => Ok(()), + -1 => Err(std::io::ErrorKind::BrokenPipe.into()), + -2 => Err(std::io::ErrorKind::BrokenPipe.into()), + -3 => Err(std::io::ErrorKind::NotConnected.into()), + result => Err(std::io::Error::other(format!( + "Unexpected response from ftdi_tcioflush: {result}" + ))), + } + } + + pub fn read_data(&mut self, buffer: &mut [u8]) -> std::io::Result { + let length = i32::try_from(buffer.len()).map_err(|_| std::io::ErrorKind::InvalidInput)?; + let result = + unsafe { libftdi1_sys::ftdi_read_data(self.context, buffer.as_mut_ptr(), length) }; + match result { + 1.. => Ok(result as usize), + 0 => Err(std::io::ErrorKind::WouldBlock.into()), + -666 => Err(std::io::ErrorKind::NotConnected.into()), + result => Err(self.libusb_convert_result(result)), + } + } + + fn write_data(&mut self, buffer: &[u8], written: &mut usize) -> std::io::Result<()> { + *written = 0; + let mut transferred: i32 = 0; + let result = unsafe { + // NOTE: Nasty hack to overcome libftdi1 API limitation. + // Write can partially succeed, but the default ftdi_write_data + // function doesn't report number of transferred bytes in that case. + libusb1_sys::libusb_bulk_transfer( + (*self.context).usb_dev, + (*self.context).in_ep as u8, + Vec::from(buffer).as_mut_ptr(), + buffer.len() as i32, + &mut transferred, + 100, + ) + }; + *written += transferred as usize; + if result < 0 { + return Err(self.libusb_convert_result(result)); + } + Ok(()) + } + + fn try_unclog_pipe(&mut self) -> std::io::Result<()> { + let mut buffer = vec![0u8; self.read_chunksize]; + let read = match self.read_data(&mut buffer) { + Ok(read) => read, + Err(error) => match error.kind() { + std::io::ErrorKind::Interrupted | std::io::ErrorKind::WouldBlock => 0, + _ => return Err(error), + }, + }; + self.unclog_buffer.extend(buffer[0..read].iter()); + Ok(()) + } + + fn commit_write(&mut self) -> std::io::Result<()> { let timeout = std::time::Instant::now(); - loop { - let result = unsafe { - libftdi1_sys::ftdi_write_data(self.context.get(), data.as_ptr(), data.len() as i32) - }; - self.context.check_result(result)?; - if result > 0 { - return Ok(result as usize); + while !self.write_buffer.is_empty() { + let mut written = 0; + let result = self.write_data(&self.write_buffer.clone(), &mut written); + self.write_buffer.drain(..written); + if let Err(error) = result { + match error.kind() { + std::io::ErrorKind::TimedOut => self.try_unclog_pipe()?, + _ => return Err(error), + } } if timeout.elapsed() > self.write_timeout { return Err(std::io::ErrorKind::TimedOut.into()); } } + Ok(()) } - pub fn write_all(&self, data: &[u8]) -> std::io::Result<()> { - let mut data = data; - while !data.is_empty() { - let written = self.write(data)?; - data = &data[written..]; + fn read(&mut self, buffer: &mut [u8]) -> std::io::Result { + if buffer.is_empty() { + Err(std::io::ErrorKind::InvalidInput.into()) + } else if self.unclog_buffer.is_empty() { + self.read_data(buffer) + } else { + let mut read = 0; + for item in buffer.iter_mut() { + if let Some(byte) = self.unclog_buffer.pop_front() { + *item = byte; + read += 1; + } else { + break; + } + } + Ok(read) } - Ok(()) } - pub fn discard_buffers(&self) -> std::io::Result<()> { - let result = unsafe { libftdi1_sys::ftdi_tcioflush(self.context.get()) }; - self.context.check_result(result)?; - Ok(()) + fn write(&mut self, buffer: &[u8]) -> std::io::Result { + let remaining_space = self.write_chunksize - self.write_buffer.len(); + let length = buffer.len().min(remaining_space); + self.write_buffer.extend(&buffer[..length]); + if self.write_buffer.len() >= self.write_chunksize { + self.commit_write()? + } + Ok(length) + } + + fn flush(&mut self) -> std::io::Result<()> { + self.commit_write() + } +} + +impl Drop for Wrapper { + fn drop(&mut self) { + unsafe { libftdi1_sys::ftdi_free(self.context) } + } +} + +pub struct FtdiDevice { + wrapper: Wrapper, +} + +impl FtdiDevice { + pub fn list(vendor: u16, product: u16) -> std::io::Result> { + Wrapper::list_devices(vendor, product) + } + + pub fn open( + description: &str, + poll_timeout: Option, + read_timeout: Option, + write_timeout: Option, + ) -> std::io::Result { + let mut wrapper = Wrapper::new(read_timeout, write_timeout)?; + + wrapper.set_module_detach_mode(ModuleDetachMode::AutoDetachReattach); + wrapper.set_interface(InterfaceIndex::A)?; + + const CHUNK_SIZE: usize = 64 * 1024; + + wrapper.read_data_set_chunksize(CHUNK_SIZE)?; + wrapper.write_data_set_chunksize(CHUNK_SIZE)?; + + wrapper.usb_open_string(description)?; + + wrapper.set_latency_timer(poll_timeout)?; + + Ok(FtdiDevice { wrapper }) + } + + pub fn set_dtr(&mut self, value: bool) -> std::io::Result<()> { + self.wrapper.set_dtr(value) + } + + pub fn read_dsr(&mut self) -> std::io::Result { + Ok(self.wrapper.poll_modem_status()?.dsr) + } + + pub fn discard_buffers(&mut self) -> std::io::Result<()> { + self.wrapper.tcioflush() + } +} + +impl std::io::Read for FtdiDevice { + fn read(&mut self, buffer: &mut [u8]) -> std::io::Result { + self.wrapper.read(buffer) + } +} + +impl std::io::Write for FtdiDevice { + fn write(&mut self, buffer: &[u8]) -> std::io::Result { + self.wrapper.write(buffer) + } + + fn flush(&mut self) -> std::io::Result<()> { + self.wrapper.flush() } } impl Drop for FtdiDevice { fn drop(&mut self) { - unsafe { libftdi1_sys::ftdi_usb_close(self.context.get()) }; + unsafe { libftdi1_sys::ftdi_usb_close(self.wrapper.context) }; } } - -pub struct FtdiDeviceInfo { - pub port: String, - pub serial: String, -} - -pub fn list_ftdi_devices(vendor: u16, product: u16) -> Result, FtdiError> { - let context = Context::new()?; - - let mut device_list: *mut libftdi1_sys::ftdi_device_list = std::ptr::null_mut(); - let result = unsafe { - libftdi1_sys::ftdi_usb_find_all( - context.get(), - &mut device_list, - vendor as i32, - product as i32, - ) - }; - context.check_result(result)?; - - let mut list: Vec = vec![]; - - let mut serial = [0i8; 128]; - - let mut device = device_list; - let mut index = 0; - while !device.is_null() { - let result = unsafe { - libftdi1_sys::ftdi_usb_get_strings( - context.get(), - (*device).dev, - std::ptr::null_mut(), - 0, - std::ptr::null_mut(), - 0, - serial.as_mut_ptr(), - serial.len() as i32, - ) - }; - - if let Ok(()) = context.check_result(result) { - list.push(FtdiDeviceInfo { - port: format!("i:0x{vendor:04X}:0x{product:04X}:{index}"), - serial: unsafe { std::ffi::CStr::from_ptr(serial.as_ptr()) } - .to_string_lossy() - .into_owned(), - }); - } - - device = unsafe { (*device).next }; - index += 1; - } - - unsafe { libftdi1_sys::ftdi_list_free(&mut device_list) } - - Ok(list) -} diff --git a/sw/deployer/src/sc64/link.rs b/sw/deployer/src/sc64/link.rs index 1a14b0b..76c1d55 100644 --- a/sw/deployer/src/sc64/link.rs +++ b/sw/deployer/src/sc64/link.rs @@ -1,12 +1,9 @@ -use super::{ - error::Error, - ftdi::{list_ftdi_devices, FtdiDevice, FtdiError}, -}; +use super::{error::Error, ftdi::FtdiDevice}; use serial2::SerialPort; use std::{ collections::VecDeque, fmt::Display, - io::{BufReader, BufWriter, ErrorKind, Read, Write}, + io::{BufReader, BufWriter, Read, Write}, net::TcpStream, time::{Duration, Instant}, }; @@ -68,18 +65,18 @@ const SERIAL_PREFIX: &str = "serial://"; const FTDI_PREFIX: &str = "ftdi://"; const RESET_TIMEOUT: Duration = Duration::from_secs(1); -const POLL_TIMEOUT: Duration = Duration::from_millis(1); +const POLL_TIMEOUT: Duration = Duration::from_millis(10); const READ_TIMEOUT: Duration = Duration::from_secs(5); const WRITE_TIMEOUT: Duration = Duration::from_secs(5); pub trait Backend { fn read(&mut self, buffer: &mut [u8]) -> std::io::Result; - fn write(&mut self, buffer: &[u8]) -> std::io::Result<()>; + fn write_all(&mut self, buffer: &[u8]) -> std::io::Result<()>; fn flush(&mut self) -> std::io::Result<()>; - fn reset(&mut self) -> Result<(), Error> { + fn reset(&mut self) -> std::io::Result<()> { Ok(()) } @@ -94,29 +91,33 @@ pub trait Backend { _ => {} }, Err(error) => match error.kind() { - ErrorKind::TimedOut => return Ok(()), + std::io::ErrorKind::Interrupted + | std::io::ErrorKind::TimedOut + | std::io::ErrorKind::WouldBlock => return Ok(()), _ => return Err(error), }, } if timeout.elapsed() >= RESET_TIMEOUT { return Err(std::io::Error::new( - ErrorKind::TimedOut, + std::io::ErrorKind::TimedOut, "SC64 read buffer flush took too long", )); } } } - fn try_read_exact(&mut self, buffer: &mut [u8], block: bool) -> Result, Error> { + fn try_read_exact(&mut self, buffer: &mut [u8], block: bool) -> std::io::Result> { let mut position = 0; let length = buffer.len(); let timeout = Instant::now(); while position < length { match self.read(&mut buffer[position..length]) { - Ok(0) => return Err(Error::new("Unexpected end of stream data")), + Ok(0) => return Err(std::io::ErrorKind::UnexpectedEof.into()), Ok(bytes) => position += bytes, Err(error) => match error.kind() { - ErrorKind::Interrupted | ErrorKind::TimedOut | ErrorKind::WouldBlock => { + std::io::ErrorKind::Interrupted + | std::io::ErrorKind::TimedOut + | std::io::ErrorKind::WouldBlock => { if !block && position == 0 { return Ok(None); } @@ -125,32 +126,32 @@ pub trait Backend { }, } if timeout.elapsed() > READ_TIMEOUT { - return Err(Error::new("Read timeout")); + return Err(std::io::ErrorKind::TimedOut.into()); } } Ok(Some(())) } - fn try_read_header(&mut self, block: bool) -> Result, Error> { + fn try_read_header(&mut self, block: bool) -> std::io::Result> { let mut header = [0u8; 4]; Ok(self.try_read_exact(&mut header, block)?.map(|_| header)) } - fn read_exact(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + fn read_exact(&mut self, buffer: &mut [u8]) -> std::io::Result<()> { match self.try_read_exact(buffer, true)? { Some(()) => Ok(()), - None => Err(Error::new("Unexpected end of data")), + None => Err(std::io::ErrorKind::UnexpectedEof.into()), } } - fn send_command(&mut self, command: &Command) -> Result<(), Error> { - self.write(b"CMD")?; - self.write(&command.id.to_be_bytes())?; + fn send_command(&mut self, command: &Command) -> std::io::Result<()> { + self.write_all(b"CMD")?; + self.write_all(&command.id.to_be_bytes())?; - self.write(&command.args[0].to_be_bytes())?; - self.write(&command.args[1].to_be_bytes())?; + self.write_all(&command.args[0].to_be_bytes())?; + self.write_all(&command.args[1].to_be_bytes())?; - self.write(&command.data)?; + self.write_all(&command.data)?; self.flush()?; @@ -161,16 +162,16 @@ pub trait Backend { &mut self, data_type: DataType, packets: &mut VecDeque, - ) -> Result, Error> { + ) -> std::io::Result> { let block = matches!(data_type, DataType::Response); while let Some(header) = self.try_read_header(block)? { - let (packet_token, error) = (match &header[0..3] { - b"CMP" => Ok((false, false)), - b"PKT" => Ok((true, false)), - b"ERR" => Ok((false, true)), - _ => Err(Error::new("Unknown response token")), - })?; + let (packet_token, error) = match &header[0..3] { + b"CMP" => (false, false), + b"PKT" => (true, false), + b"ERR" => (false, true), + _ => return Err(std::io::ErrorKind::InvalidData.into()), + }; let id = header[3]; let mut buffer = [0u8; 4]; @@ -204,7 +205,7 @@ impl Backend for SerialBackend { self.device.read(buffer) } - fn write(&mut self, buffer: &[u8]) -> std::io::Result<()> { + fn write_all(&mut self, buffer: &[u8]) -> std::io::Result<()> { self.device.write_all(buffer) } @@ -212,7 +213,7 @@ impl Backend for SerialBackend { self.device.flush() } - fn reset(&mut self) -> Result<(), Error> { + fn reset(&mut self) -> std::io::Result<()> { self.device.set_dtr(true)?; let timeout = Instant::now(); loop { @@ -221,7 +222,10 @@ impl Backend for SerialBackend { break; } if timeout.elapsed() > RESET_TIMEOUT { - return Err(Error::new("Couldn't reset SC64 device (on)")); + return Err(std::io::Error::new( + std::io::ErrorKind::TimedOut, + "Couldn't reset SC64 device (on)", + )); } } @@ -234,7 +238,10 @@ impl Backend for SerialBackend { break; } if timeout.elapsed() > RESET_TIMEOUT { - return Err(Error::new("Couldn't reset SC64 device (off)")); + return Err(std::io::Error::new( + std::io::ErrorKind::TimedOut, + "Couldn't reset SC64 device (off)", + )); } } @@ -258,15 +265,15 @@ impl Backend for FtdiBackend { self.device.read(buffer) } - fn write(&mut self, buffer: &[u8]) -> std::io::Result<()> { + fn write_all(&mut self, buffer: &[u8]) -> std::io::Result<()> { self.device.write_all(buffer) } fn flush(&mut self) -> std::io::Result<()> { - Ok(()) + self.device.flush() } - fn reset(&mut self) -> Result<(), Error> { + fn reset(&mut self) -> std::io::Result<()> { self.device.set_dtr(true)?; let timeout = Instant::now(); loop { @@ -275,7 +282,10 @@ impl Backend for FtdiBackend { break; } if timeout.elapsed() > RESET_TIMEOUT { - return Err(Error::new("Couldn't reset SC64 device (on)")); + return Err(std::io::Error::new( + std::io::ErrorKind::TimedOut, + "Couldn't reset SC64 device (on)", + )); } } @@ -288,7 +298,10 @@ impl Backend for FtdiBackend { break; } if timeout.elapsed() > RESET_TIMEOUT { - return Err(Error::new("Couldn't reset SC64 device (off)")); + return Err(std::io::Error::new( + std::io::ErrorKind::TimedOut, + "Couldn't reset SC64 device (off)", + )); } } @@ -296,9 +309,14 @@ impl Backend for FtdiBackend { } } -fn new_ftdi_backend(port: &str) -> Result { +fn new_ftdi_backend(port: &str) -> std::io::Result { Ok(FtdiBackend { - device: FtdiDevice::open(port, POLL_TIMEOUT, WRITE_TIMEOUT)?, + device: FtdiDevice::open( + port, + Some(POLL_TIMEOUT), + Some(READ_TIMEOUT), + Some(WRITE_TIMEOUT), + )?, }) } @@ -313,7 +331,7 @@ impl Backend for TcpBackend { self.reader.read(buffer) } - fn write(&mut self, buffer: &[u8]) -> std::io::Result<()> { + fn write_all(&mut self, buffer: &[u8]) -> std::io::Result<()> { self.writer.write_all(buffer) } @@ -325,17 +343,17 @@ impl Backend for TcpBackend { self.stream.shutdown(std::net::Shutdown::Both).ok(); } - fn send_command(&mut self, command: &Command) -> Result<(), Error> { + fn send_command(&mut self, command: &Command) -> std::io::Result<()> { let payload_data_type: u32 = DataType::Command.into(); - self.write(&payload_data_type.to_be_bytes())?; + self.write_all(&payload_data_type.to_be_bytes())?; - self.write(&command.id.to_be_bytes())?; - self.write(&command.args[0].to_be_bytes())?; - self.write(&command.args[1].to_be_bytes())?; + self.write_all(&command.id.to_be_bytes())?; + self.write_all(&command.args[0].to_be_bytes())?; + self.write_all(&command.args[1].to_be_bytes())?; let command_data_length = command.data.len() as u32; - self.write(&command_data_length.to_be_bytes())?; - self.write(&command.data)?; + self.write_all(&command_data_length.to_be_bytes())?; + self.write_all(&command.data)?; self.flush()?; @@ -346,10 +364,12 @@ impl Backend for TcpBackend { &mut self, data_type: DataType, packets: &mut VecDeque, - ) -> Result, Error> { + ) -> std::io::Result> { let block = matches!(data_type, DataType::Response); while let Some(header) = self.try_read_header(block)? { - let payload_data_type: DataType = u32::from_be_bytes(header).try_into()?; + let payload_data_type: DataType = u32::from_be_bytes(header) + .try_into() + .map_err(|_| std::io::ErrorKind::InvalidData)?; let mut buffer = [0u8; 4]; match payload_data_type { DataType::Response => { @@ -387,7 +407,7 @@ impl Backend for TcpBackend { } } DataType::KeepAlive => {} - _ => return Err(Error::new("Unexpected payload data type received")), + _ => return Err(std::io::ErrorKind::InvalidData.into()), }; } @@ -548,7 +568,7 @@ pub fn list_local_devices() -> Result, Error> { let mut devices: Vec = Vec::new(); - if let Ok(list) = list_ftdi_devices(SC64_VID, SC64_PID) { + if let Ok(list) = FtdiDevice::list(SC64_VID, SC64_PID) { for device in list.into_iter() { if device.serial.starts_with(SC64_SID) { devices.push(DeviceInfo {