mirror of
https://github.com/Polprzewodnikowy/SummerCart64.git
synced 2025-02-16 20:29:12 +01:00
made libftdi backend more robust
This commit is contained in:
parent
f3837287b0
commit
21db23b22b
@ -1,4 +1,3 @@
|
|||||||
use super::ftdi::FtdiError;
|
|
||||||
use std::fmt::{Display, Formatter, Result};
|
use std::fmt::{Display, Formatter, Result};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -27,15 +26,3 @@ impl From<std::io::Error> for Error {
|
|||||||
Error::new(format!("IO error: {}", value).as_str())
|
Error::new(format!("IO error: {}", value).as_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<serialport::Error> for Error {
|
|
||||||
fn from(value: serialport::Error) -> Self {
|
|
||||||
Error::new(format!("SerialPort error: {}", value.description).as_str())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<FtdiError> for Error {
|
|
||||||
fn from(value: FtdiError) -> Self {
|
|
||||||
Error::new(format!("libftdi error: {}", value.description).as_str())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,227 +1,487 @@
|
|||||||
pub struct FtdiError {
|
pub struct DeviceInfo {
|
||||||
pub description: String,
|
pub port: String,
|
||||||
|
pub serial: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FtdiError {
|
#[allow(dead_code)]
|
||||||
fn malloc() -> FtdiError {
|
enum InterfaceIndex {
|
||||||
FtdiError {
|
Any,
|
||||||
description: format!("Couldn't allocate memory for the context"),
|
A,
|
||||||
}
|
B,
|
||||||
}
|
C,
|
||||||
|
D,
|
||||||
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")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<FtdiError> for std::io::Error {
|
#[allow(dead_code)]
|
||||||
fn from(value: FtdiError) -> Self {
|
enum ModuleDetachMode {
|
||||||
return Self::new(std::io::ErrorKind::Other, value.description);
|
AutoDetach,
|
||||||
}
|
DontDetach,
|
||||||
|
AutoDetachReattach,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Context {
|
struct ModemStatus {
|
||||||
|
dsr: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Wrapper {
|
||||||
context: *mut libftdi1_sys::ftdi_context,
|
context: *mut libftdi1_sys::ftdi_context,
|
||||||
}
|
unclog_buffer: std::collections::VecDeque<u8>,
|
||||||
|
write_buffer: Vec<u8>,
|
||||||
impl Context {
|
|
||||||
fn new() -> Result<Context, FtdiError> {
|
|
||||||
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,
|
|
||||||
write_timeout: std::time::Duration,
|
write_timeout: std::time::Duration,
|
||||||
|
read_chunksize: usize,
|
||||||
|
write_chunksize: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FtdiDevice {
|
impl Wrapper {
|
||||||
pub fn open(
|
const DEFAULT_POLL_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(16);
|
||||||
port: &str,
|
const DEFAULT_RW_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5);
|
||||||
read_timeout: std::time::Duration,
|
const DEFAULT_CHUNKSIZE: usize = 4096;
|
||||||
write_timeout: std::time::Duration,
|
|
||||||
) -> Result<FtdiDevice, FtdiError> {
|
fn new(
|
||||||
let context = Context::new()?;
|
read_timeout: Option<std::time::Duration>,
|
||||||
unsafe {
|
write_timeout: Option<std::time::Duration>,
|
||||||
let mode = libftdi1_sys::ftdi_module_detach_mode::AUTO_DETACH_REATACH_SIO_MODULE;
|
) -> std::io::Result<Wrapper> {
|
||||||
(*context.get()).module_detach_mode = mode;
|
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 mut wrapper = Wrapper {
|
||||||
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 {
|
|
||||||
context,
|
context,
|
||||||
read_timeout,
|
unclog_buffer: std::collections::VecDeque::new(),
|
||||||
write_timeout,
|
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> {
|
fn list_devices(vendor: u16, product: u16) -> std::io::Result<Vec<DeviceInfo>> {
|
||||||
let state = if value { 1 } else { 0 };
|
let wrapper = Wrapper::new(None, None)?;
|
||||||
let result = unsafe { libftdi1_sys::ftdi_setdtr(self.context.get(), state) };
|
|
||||||
self.context.check_result(result)?;
|
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<DeviceInfo> = 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<std::time::Duration>,
|
||||||
|
write_timeout: Option<std::time::Duration>,
|
||||||
|
) -> 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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_dsr(&self) -> Result<bool, FtdiError> {
|
fn set_module_detach_mode(&mut self, mode: ModuleDetachMode) {
|
||||||
const DSR_BIT: u16 = 1 << 5;
|
let mode = match mode {
|
||||||
let mut status: u16 = 0;
|
ModuleDetachMode::AutoDetach => {
|
||||||
let result = unsafe {
|
libftdi1_sys::ftdi_module_detach_mode::AUTO_DETACH_SIO_MODULE
|
||||||
libftdi1_sys::ftdi_poll_modem_status(
|
}
|
||||||
self.context.get(),
|
ModuleDetachMode::DontDetach => {
|
||||||
std::slice::from_mut(&mut status).as_mut_ptr(),
|
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<usize> {
|
fn set_interface(&mut self, interface: InterfaceIndex) -> std::io::Result<()> {
|
||||||
let timeout = std::time::Instant::now();
|
let interface = match interface {
|
||||||
loop {
|
InterfaceIndex::Any => libftdi1_sys::ftdi_interface::INTERFACE_ANY,
|
||||||
let result = unsafe {
|
InterfaceIndex::A => libftdi1_sys::ftdi_interface::INTERFACE_A,
|
||||||
libftdi1_sys::ftdi_read_data(
|
InterfaceIndex::B => libftdi1_sys::ftdi_interface::INTERFACE_B,
|
||||||
self.context.get(),
|
InterfaceIndex::C => libftdi1_sys::ftdi_interface::INTERFACE_C,
|
||||||
data.as_mut_ptr(),
|
InterfaceIndex::D => libftdi1_sys::ftdi_interface::INTERFACE_D,
|
||||||
data.len() as i32,
|
};
|
||||||
)
|
match unsafe { libftdi1_sys::ftdi_set_interface(self.context, interface) } {
|
||||||
};
|
0 => Ok(()),
|
||||||
self.context.check_result(result)?;
|
-1 => Err(std::io::ErrorKind::InvalidInput.into()),
|
||||||
if result > 0 {
|
-2 => Err(std::io::ErrorKind::NotConnected.into()),
|
||||||
return Ok(result as usize);
|
-3 => Err(std::io::ErrorKind::InvalidData.into()),
|
||||||
}
|
result => Err(std::io::Error::other(format!(
|
||||||
if timeout.elapsed() > self.read_timeout {
|
"Unexpected response from ftdi_set_interface: {result}"
|
||||||
return Err(std::io::ErrorKind::TimedOut.into());
|
))),
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write(&self, data: &[u8]) -> std::io::Result<usize> {
|
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::time::Duration>) -> 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<ModemStatus> {
|
||||||
|
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<usize> {
|
||||||
|
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();
|
let timeout = std::time::Instant::now();
|
||||||
loop {
|
while !self.write_buffer.is_empty() {
|
||||||
let result = unsafe {
|
let mut written = 0;
|
||||||
libftdi1_sys::ftdi_write_data(self.context.get(), data.as_ptr(), data.len() as i32)
|
let result = self.write_data(&self.write_buffer.clone(), &mut written);
|
||||||
};
|
self.write_buffer.drain(..written);
|
||||||
self.context.check_result(result)?;
|
if let Err(error) = result {
|
||||||
if result > 0 {
|
match error.kind() {
|
||||||
return Ok(result as usize);
|
std::io::ErrorKind::TimedOut => self.try_unclog_pipe()?,
|
||||||
|
_ => return Err(error),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if timeout.elapsed() > self.write_timeout {
|
if timeout.elapsed() > self.write_timeout {
|
||||||
return Err(std::io::ErrorKind::TimedOut.into());
|
return Err(std::io::ErrorKind::TimedOut.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_all(&self, data: &[u8]) -> std::io::Result<()> {
|
fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> {
|
||||||
let mut data = data;
|
if buffer.is_empty() {
|
||||||
while !data.is_empty() {
|
Err(std::io::ErrorKind::InvalidInput.into())
|
||||||
let written = self.write(data)?;
|
} else if self.unclog_buffer.is_empty() {
|
||||||
data = &data[written..];
|
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<()> {
|
fn write(&mut self, buffer: &[u8]) -> std::io::Result<usize> {
|
||||||
let result = unsafe { libftdi1_sys::ftdi_tcioflush(self.context.get()) };
|
let remaining_space = self.write_chunksize - self.write_buffer.len();
|
||||||
self.context.check_result(result)?;
|
let length = buffer.len().min(remaining_space);
|
||||||
Ok(())
|
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<Vec<DeviceInfo>> {
|
||||||
|
Wrapper::list_devices(vendor, product)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open(
|
||||||
|
description: &str,
|
||||||
|
poll_timeout: Option<std::time::Duration>,
|
||||||
|
read_timeout: Option<std::time::Duration>,
|
||||||
|
write_timeout: Option<std::time::Duration>,
|
||||||
|
) -> std::io::Result<FtdiDevice> {
|
||||||
|
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<bool> {
|
||||||
|
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<usize> {
|
||||||
|
self.wrapper.read(buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::io::Write for FtdiDevice {
|
||||||
|
fn write(&mut self, buffer: &[u8]) -> std::io::Result<usize> {
|
||||||
|
self.wrapper.write(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> std::io::Result<()> {
|
||||||
|
self.wrapper.flush()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for FtdiDevice {
|
impl Drop for FtdiDevice {
|
||||||
fn drop(&mut self) {
|
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<Vec<FtdiDeviceInfo>, 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<FtdiDeviceInfo> = 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)
|
|
||||||
}
|
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
use super::{
|
use super::{error::Error, ftdi::FtdiDevice};
|
||||||
error::Error,
|
|
||||||
ftdi::{list_ftdi_devices, FtdiDevice, FtdiError},
|
|
||||||
};
|
|
||||||
use serial2::SerialPort;
|
use serial2::SerialPort;
|
||||||
use std::{
|
use std::{
|
||||||
collections::VecDeque,
|
collections::VecDeque,
|
||||||
fmt::Display,
|
fmt::Display,
|
||||||
io::{BufReader, BufWriter, ErrorKind, Read, Write},
|
io::{BufReader, BufWriter, Read, Write},
|
||||||
net::TcpStream,
|
net::TcpStream,
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
@ -68,18 +65,18 @@ const SERIAL_PREFIX: &str = "serial://";
|
|||||||
const FTDI_PREFIX: &str = "ftdi://";
|
const FTDI_PREFIX: &str = "ftdi://";
|
||||||
|
|
||||||
const RESET_TIMEOUT: Duration = Duration::from_secs(1);
|
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 READ_TIMEOUT: Duration = Duration::from_secs(5);
|
||||||
const WRITE_TIMEOUT: Duration = Duration::from_secs(5);
|
const WRITE_TIMEOUT: Duration = Duration::from_secs(5);
|
||||||
|
|
||||||
pub trait Backend {
|
pub trait Backend {
|
||||||
fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize>;
|
fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize>;
|
||||||
|
|
||||||
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 flush(&mut self) -> std::io::Result<()>;
|
||||||
|
|
||||||
fn reset(&mut self) -> Result<(), Error> {
|
fn reset(&mut self) -> std::io::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,29 +91,33 @@ pub trait Backend {
|
|||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
Err(error) => match error.kind() {
|
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),
|
_ => return Err(error),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if timeout.elapsed() >= RESET_TIMEOUT {
|
if timeout.elapsed() >= RESET_TIMEOUT {
|
||||||
return Err(std::io::Error::new(
|
return Err(std::io::Error::new(
|
||||||
ErrorKind::TimedOut,
|
std::io::ErrorKind::TimedOut,
|
||||||
"SC64 read buffer flush took too long",
|
"SC64 read buffer flush took too long",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_read_exact(&mut self, buffer: &mut [u8], block: bool) -> Result<Option<()>, Error> {
|
fn try_read_exact(&mut self, buffer: &mut [u8], block: bool) -> std::io::Result<Option<()>> {
|
||||||
let mut position = 0;
|
let mut position = 0;
|
||||||
let length = buffer.len();
|
let length = buffer.len();
|
||||||
let timeout = Instant::now();
|
let timeout = Instant::now();
|
||||||
while position < length {
|
while position < length {
|
||||||
match self.read(&mut buffer[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,
|
Ok(bytes) => position += bytes,
|
||||||
Err(error) => match error.kind() {
|
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 {
|
if !block && position == 0 {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
@ -125,32 +126,32 @@ pub trait Backend {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
if timeout.elapsed() > READ_TIMEOUT {
|
if timeout.elapsed() > READ_TIMEOUT {
|
||||||
return Err(Error::new("Read timeout"));
|
return Err(std::io::ErrorKind::TimedOut.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Some(()))
|
Ok(Some(()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_read_header(&mut self, block: bool) -> Result<Option<[u8; 4]>, Error> {
|
fn try_read_header(&mut self, block: bool) -> std::io::Result<Option<[u8; 4]>> {
|
||||||
let mut header = [0u8; 4];
|
let mut header = [0u8; 4];
|
||||||
Ok(self.try_read_exact(&mut header, block)?.map(|_| header))
|
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)? {
|
match self.try_read_exact(buffer, true)? {
|
||||||
Some(()) => Ok(()),
|
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> {
|
fn send_command(&mut self, command: &Command) -> std::io::Result<()> {
|
||||||
self.write(b"CMD")?;
|
self.write_all(b"CMD")?;
|
||||||
self.write(&command.id.to_be_bytes())?;
|
self.write_all(&command.id.to_be_bytes())?;
|
||||||
|
|
||||||
self.write(&command.args[0].to_be_bytes())?;
|
self.write_all(&command.args[0].to_be_bytes())?;
|
||||||
self.write(&command.args[1].to_be_bytes())?;
|
self.write_all(&command.args[1].to_be_bytes())?;
|
||||||
|
|
||||||
self.write(&command.data)?;
|
self.write_all(&command.data)?;
|
||||||
|
|
||||||
self.flush()?;
|
self.flush()?;
|
||||||
|
|
||||||
@ -161,16 +162,16 @@ pub trait Backend {
|
|||||||
&mut self,
|
&mut self,
|
||||||
data_type: DataType,
|
data_type: DataType,
|
||||||
packets: &mut VecDeque<AsynchronousPacket>,
|
packets: &mut VecDeque<AsynchronousPacket>,
|
||||||
) -> Result<Option<Response>, Error> {
|
) -> std::io::Result<Option<Response>> {
|
||||||
let block = matches!(data_type, DataType::Response);
|
let block = matches!(data_type, DataType::Response);
|
||||||
|
|
||||||
while let Some(header) = self.try_read_header(block)? {
|
while let Some(header) = self.try_read_header(block)? {
|
||||||
let (packet_token, error) = (match &header[0..3] {
|
let (packet_token, error) = match &header[0..3] {
|
||||||
b"CMP" => Ok((false, false)),
|
b"CMP" => (false, false),
|
||||||
b"PKT" => Ok((true, false)),
|
b"PKT" => (true, false),
|
||||||
b"ERR" => Ok((false, true)),
|
b"ERR" => (false, true),
|
||||||
_ => Err(Error::new("Unknown response token")),
|
_ => return Err(std::io::ErrorKind::InvalidData.into()),
|
||||||
})?;
|
};
|
||||||
let id = header[3];
|
let id = header[3];
|
||||||
|
|
||||||
let mut buffer = [0u8; 4];
|
let mut buffer = [0u8; 4];
|
||||||
@ -204,7 +205,7 @@ impl Backend for SerialBackend {
|
|||||||
self.device.read(buffer)
|
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)
|
self.device.write_all(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,7 +213,7 @@ impl Backend for SerialBackend {
|
|||||||
self.device.flush()
|
self.device.flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reset(&mut self) -> Result<(), Error> {
|
fn reset(&mut self) -> std::io::Result<()> {
|
||||||
self.device.set_dtr(true)?;
|
self.device.set_dtr(true)?;
|
||||||
let timeout = Instant::now();
|
let timeout = Instant::now();
|
||||||
loop {
|
loop {
|
||||||
@ -221,7 +222,10 @@ impl Backend for SerialBackend {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if timeout.elapsed() > RESET_TIMEOUT {
|
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;
|
break;
|
||||||
}
|
}
|
||||||
if timeout.elapsed() > RESET_TIMEOUT {
|
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)
|
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)
|
self.device.write_all(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flush(&mut self) -> std::io::Result<()> {
|
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)?;
|
self.device.set_dtr(true)?;
|
||||||
let timeout = Instant::now();
|
let timeout = Instant::now();
|
||||||
loop {
|
loop {
|
||||||
@ -275,7 +282,10 @@ impl Backend for FtdiBackend {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if timeout.elapsed() > RESET_TIMEOUT {
|
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;
|
break;
|
||||||
}
|
}
|
||||||
if timeout.elapsed() > RESET_TIMEOUT {
|
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<FtdiBackend, FtdiError> {
|
fn new_ftdi_backend(port: &str) -> std::io::Result<FtdiBackend> {
|
||||||
Ok(FtdiBackend {
|
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)
|
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)
|
self.writer.write_all(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -325,17 +343,17 @@ impl Backend for TcpBackend {
|
|||||||
self.stream.shutdown(std::net::Shutdown::Both).ok();
|
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();
|
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_all(&command.id.to_be_bytes())?;
|
||||||
self.write(&command.args[0].to_be_bytes())?;
|
self.write_all(&command.args[0].to_be_bytes())?;
|
||||||
self.write(&command.args[1].to_be_bytes())?;
|
self.write_all(&command.args[1].to_be_bytes())?;
|
||||||
|
|
||||||
let command_data_length = command.data.len() as u32;
|
let command_data_length = command.data.len() as u32;
|
||||||
self.write(&command_data_length.to_be_bytes())?;
|
self.write_all(&command_data_length.to_be_bytes())?;
|
||||||
self.write(&command.data)?;
|
self.write_all(&command.data)?;
|
||||||
|
|
||||||
self.flush()?;
|
self.flush()?;
|
||||||
|
|
||||||
@ -346,10 +364,12 @@ impl Backend for TcpBackend {
|
|||||||
&mut self,
|
&mut self,
|
||||||
data_type: DataType,
|
data_type: DataType,
|
||||||
packets: &mut VecDeque<AsynchronousPacket>,
|
packets: &mut VecDeque<AsynchronousPacket>,
|
||||||
) -> Result<Option<Response>, Error> {
|
) -> std::io::Result<Option<Response>> {
|
||||||
let block = matches!(data_type, DataType::Response);
|
let block = matches!(data_type, DataType::Response);
|
||||||
while let Some(header) = self.try_read_header(block)? {
|
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];
|
let mut buffer = [0u8; 4];
|
||||||
match payload_data_type {
|
match payload_data_type {
|
||||||
DataType::Response => {
|
DataType::Response => {
|
||||||
@ -387,7 +407,7 @@ impl Backend for TcpBackend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType::KeepAlive => {}
|
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<Vec<DeviceInfo>, Error> {
|
|||||||
|
|
||||||
let mut devices: Vec<DeviceInfo> = Vec::new();
|
let mut devices: Vec<DeviceInfo> = 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() {
|
for device in list.into_iter() {
|
||||||
if device.serial.starts_with(SC64_SID) {
|
if device.serial.starts_with(SC64_SID) {
|
||||||
devices.push(DeviceInfo {
|
devices.push(DeviceInfo {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user