diff --git a/src/error.rs b/src/error.rs index 907968c..24fd3bd 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,16 +2,19 @@ use thiserror::Error; use std::ffi::CStr; use std::io; +use std::str; use super::ffi; #[derive(Debug, Error)] pub enum Error { + #[error("failed to initialize the ftdi context")] + InitFailed, #[error("failed to enumerate devices to open the correct one")] EnumerationFailed, #[error("the specified device could not be found")] DeviceNotFound, - #[error("failed to open the specified device")] + #[error("failed to open or close the specified device")] AccessFailed, #[error("the requested interface could not be claimed")] ClaimFailed, @@ -34,9 +37,14 @@ pub enum Error { impl Error { pub(crate) fn unknown(context: *mut ffi::ftdi_context) -> Self { - let message = unsafe { CStr::from_ptr(ffi::ftdi_get_error_string(context)) } - .to_str() - .expect("all error strings are expected to be ASCII"); + let message = unsafe { + // Null pointer returns empty string. And we otherwise can't + // use a context without Builder::new() returning first. + let err_raw = ffi::ftdi_get_error_string(context); + // Manually checked- every error string in libftdi1 is ASCII. + str::from_utf8_unchecked(CStr::from_ptr(err_raw).to_bytes()) + }; + Error::Unknown { source: LibFtdiError { message }, } @@ -51,7 +59,13 @@ pub struct LibFtdiError { message: &'static str, } +#[derive(Debug, Error)] +#[error("libusb error code {code}")] +pub struct LibUsbError { + code: i32, +} + // Ideally this should be using libusb bindings, but we don't depend on any specific USB crate yet pub(crate) fn libusb_to_io(code: i32) -> io::Error { - io::Error::new(io::ErrorKind::Other, format!("libusb error code {}", code)) + io::Error::new(io::ErrorKind::Other, LibUsbError { code }) } diff --git a/src/lib.rs b/src/lib.rs index f8997ff..a24d897 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ use libftdi1_sys as ffi; use std::convert::TryInto; use std::io::{self, Read, Write}; +use std::os::raw; pub mod error; @@ -20,6 +21,24 @@ pub enum Interface { Any, } +pub enum FlowControl { + Disable, + RtsCts, + DtrDsr, + XonXoff +} + +impl Into for FlowControl { + fn into(self) -> i32 { + match self { + FlowControl::Disable => 0x0, + FlowControl::RtsCts => (0x1 << 8), + FlowControl::DtrDsr => (0x2 << 8), + FlowControl::XonXoff => (0x4 << 8), + } + } +} + impl Into for Interface { fn into(self) -> ffi::ftdi_interface { match self { @@ -100,6 +119,26 @@ impl Device { } } + pub fn usb_close(mut self) -> Result { + let result = unsafe { ffi::ftdi_usb_close(self.context) }; + + match result { + 0 => { + let context = std::mem::replace(&mut self.context, std::ptr::null_mut()); + std::mem::forget(self); + + let builder = Builder { + context + }; + + Ok(builder) + }, + -1 => Err(Error::AccessFailed), // usb release failed + -3 => unreachable!("uninitialized context"), + _ => Err(Error::unknown(self.context)) + } + } + pub fn usb_purge_buffers(&mut self) -> Result<()> { let result = unsafe { ffi::ftdi_usb_purge_buffers(self.context) }; match result { @@ -169,6 +208,45 @@ impl Device { err => panic!("unknown get_write_chunksize retval {:?}", err), } } + + pub fn read_pins(&mut self) -> Result { + let mut pins : u8 = 0; + let pins_ptr = std::slice::from_mut(&mut pins).as_mut_ptr(); + + let result = unsafe { ffi::ftdi_read_pins(self.context, pins_ptr) }; + + match result { + 0 => Ok(pins), + -1 => Err(Error::RequestFailed), // read pins failed + -2 => unreachable!("uninitialized context"), + _ => Err(Error::unknown(self.context)), + } + } + + pub fn set_baudrate(&mut self, baudrate : u32) -> Result<()> { + // TODO: libftdi1 will multiply baud rates in bitbang mode + // by 4. This leads to UB if baudrate >= INT_MAX/4. + let result = unsafe { ffi::ftdi_set_baudrate(self.context, baudrate as raw::c_int) }; + + match result { + 0 => Ok(()), + -1 => Err(Error::InvalidInput("baud rate not supported")), + -2 => Err(Error::RequestFailed), // set baudrate failed + -3 => unreachable!("uninitialized context"), + _ => Err(Error::unknown(self.context)) + } + } + + pub fn set_flow_control(&mut self, flowctrl: FlowControl) -> Result<()> { + let result = unsafe { ffi::ftdi_setflowctrl(self.context, flowctrl.into()) }; + + match result { + 0 => Ok(()), + -1 => Err(Error::RequestFailed), // set flow control failed + -2 => unreachable!("uninitialized context"), + _ => Err(Error::unknown(self.context)) + } + } } impl Drop for Device {