#![cfg_attr(docsrs, feature(doc_cfg))]
#![deny(missing_docs)]
mod errors;
pub use errors::{DeviceTypeError, EepromStringsError, EepromValueError, FtStatus, TimeoutError};
mod mpsse;
pub use mpsse::{FtdiMpsse, Ftx232hMpsse};
mod types;
use types::{vid_pid_from_id, STRING_LEN};
pub use types::{
BitMode, BitsPerWord, ByteOrder, Cbus232h, Cbus232r, CbusX, ClockPolarity, DeviceInfo,
DeviceStatus, DeviceType, DriveCurrent, DriverType, Eeprom2232h, Eeprom232h, Eeprom232r,
Eeprom4232h, EepromHeader, EepromStrings, EepromXSeries, ModemStatus, Parity, Speed, StopBits,
Version,
};
mod util;
use util::slice_into_string;
pub use ftdi_mpsse::{
ClockBits, ClockBitsIn, ClockBitsOut, ClockData, ClockDataIn, ClockDataOut, MpsseCmd,
MpsseCmdBuilder, MpsseCmdExecutor, MpsseSettings,
};
use libftd2xx_ffi::{
FT_Close, FT_ClrDtr, FT_ClrRts, FT_CreateDeviceInfoList, FT_EEPROM_Program, FT_EEPROM_Read,
FT_EE_UARead, FT_EE_UASize, FT_EE_UAWrite, FT_EraseEE, FT_GetBitMode, FT_GetDeviceInfo,
FT_GetDeviceInfoList, FT_GetDriverVersion, FT_GetLatencyTimer, FT_GetLibraryVersion,
FT_GetModemStatus, FT_GetQueueStatus, FT_GetStatus, FT_ListDevices, FT_Open, FT_OpenEx,
FT_Purge, FT_Read, FT_ReadEE, FT_ResetDevice, FT_SetBaudRate, FT_SetBitMode, FT_SetBreakOff,
FT_SetBreakOn, FT_SetChars, FT_SetDataCharacteristics, FT_SetDeadmanTimeout, FT_SetDtr,
FT_SetFlowControl, FT_SetLatencyTimer, FT_SetRts, FT_SetTimeouts, FT_SetUSBParameters,
FT_Write, FT_WriteEE, FT_DEVICE_LIST_INFO_NODE, FT_EEPROM_2232H, FT_EEPROM_232H,
FT_EEPROM_232R, FT_EEPROM_4232H, FT_EEPROM_X_SERIES, FT_FLOW_DTR_DSR, FT_FLOW_NONE,
FT_FLOW_RTS_CTS, FT_FLOW_XON_XOFF, FT_HANDLE, FT_LIST_NUMBER_ONLY, FT_OPEN_BY_DESCRIPTION,
FT_OPEN_BY_SERIAL_NUMBER, FT_PURGE_RX, FT_PURGE_TX, FT_STATUS,
};
#[cfg(target_os = "windows")]
use libftd2xx_ffi::{FT_CyclePort, FT_GetComPortNumber, FT_Rescan, FT_ResetPort};
#[cfg(any(target_os = "linux", target_os = "macos"))]
use libftd2xx_ffi::{FT_GetVIDPID, FT_SetVIDPID};
use log::trace;
use std::ffi::c_void;
use std::fs;
use std::io;
use std::mem;
use std::os::raw::c_char;
use std::path::Path;
use std::time::Duration;
use std::vec::Vec;
pub const FTDI_VID: u16 = 0x0403;
fn ft_result<T>(value: T, status: FT_STATUS) -> Result<T, FtStatus> {
if status != 0 {
Err(status.into())
} else {
Ok(value)
}
}
pub fn num_devices() -> Result<u32, FtStatus> {
let mut num_devs: u32 = 0;
let dummy = std::ptr::null_mut();
trace!(
"FT_ListDevices({}, NULL, {})",
num_devs,
FT_LIST_NUMBER_ONLY
);
let status: FT_STATUS = unsafe {
FT_ListDevices(
&mut num_devs as *mut u32 as *mut c_void,
dummy,
FT_LIST_NUMBER_ONLY,
)
};
ft_result(num_devs, status)
}
#[cfg(all(any(unix, doc), not(all(windows, doctest))))]
#[cfg_attr(docsrs, doc(cfg(unix)))]
pub fn set_vid_pid(vid: u16, pid: u16) -> Result<(), FtStatus> {
trace!("FT_SetVIDPID({}, {})", vid, pid);
let status: FT_STATUS = unsafe { FT_SetVIDPID(vid.into(), pid.into()) };
ft_result((), status)
}
#[cfg(all(any(unix, doc), not(all(windows, doctest))))]
#[cfg_attr(docsrs, doc(cfg(unix)))]
pub fn vid_pid() -> Result<(u32, u32), FtStatus> {
let mut vid: u32 = 0;
let mut pid: u32 = 0;
trace!("FT_GetVIDPID(_, _)");
let status: FT_STATUS = unsafe { FT_GetVIDPID(&mut vid, &mut pid) };
ft_result((vid, pid), status)
}
pub fn library_version() -> Result<Version, FtStatus> {
let mut version: u32 = 0;
trace!("FT_GetLibraryVersion(_)");
let status: FT_STATUS = unsafe { FT_GetLibraryVersion(&mut version) };
ft_result(Version::with_raw(version), status)
}
fn create_device_info_list() -> Result<u32, FtStatus> {
let mut num_devices: u32 = 0;
trace!("FT_CreateDeviceInfoList(_)");
let status: FT_STATUS = unsafe { FT_CreateDeviceInfoList(&mut num_devices) };
ft_result(num_devices, status)
}
pub fn list_devices() -> Result<Vec<DeviceInfo>, FtStatus> {
let mut devices: Vec<DeviceInfo> = Vec::new();
let mut num_devices: u32 = create_device_info_list()?;
let num_devices_usize: usize = usize::try_from(num_devices).unwrap();
if num_devices == 0 {
return Ok(devices);
}
let mut list_info_vec: Vec<FT_DEVICE_LIST_INFO_NODE> = vec![
FT_DEVICE_LIST_INFO_NODE {
Flags: 0,
Type: 0,
ID: 0,
LocId: 0,
SerialNumber: [0; 16],
Description: [0; 64],
ftHandle: std::ptr::null_mut(),
};
num_devices_usize
];
trace!("FT_GetDeviceInfoList(_, _)");
let status: FT_STATUS = unsafe {
FT_GetDeviceInfoList(
list_info_vec.as_mut_ptr() as *mut FT_DEVICE_LIST_INFO_NODE,
&mut num_devices,
)
};
if status != 0 {
Err(status.into())
} else {
while let Some(info_node) = list_info_vec.pop() {
let (vid, pid) = vid_pid_from_id(info_node.ID);
devices.push(DeviceInfo {
port_open: info_node.Flags & 0x1 == 0x1,
speed: Some((info_node.Flags & 0x2).into()),
device_type: info_node.Type.into(),
product_id: pid,
vendor_id: vid,
serial_number: slice_into_string(info_node.SerialNumber.as_ref()),
description: slice_into_string(info_node.Description.as_ref()),
});
}
devices.sort_unstable();
Ok(devices)
}
}
#[allow(clippy::redundant_field_names)]
pub fn list_devices_fs() -> io::Result<Vec<DeviceInfo>> {
let sys_bus_usb_devices = Path::new("/sys/bus/usb/devices");
let mut devices: Vec<DeviceInfo> = Vec::new();
if sys_bus_usb_devices.is_dir() {
for entry in fs::read_dir(sys_bus_usb_devices)? {
let entry = entry?;
let path = entry.path();
let mut vendor_path = path.clone();
vendor_path.push("idVendor");
if vendor_path.is_file() {
let vid: String = fs::read_to_string(vendor_path)?;
let vid: u16 = u16::from_str_radix(vid.trim(), 16)
.expect("idVendor file contains non-hex digits");
if vid != FTDI_VID {
continue;
}
} else {
continue;
}
let mut id_product_path = path.clone();
id_product_path.push("idProduct");
if !id_product_path.exists() {
continue;
}
let pid: String = fs::read_to_string(id_product_path)?;
let pid: u16 = u16::from_str_radix(pid.trim(), 16)
.expect("idProduct file contains non-hex digits");
let device_type: DeviceType = match DeviceType::with_pid(pid) {
Some(device_type) => device_type,
None => continue,
};
let serial: String = {
let mut serial_path = path.clone();
serial_path.push("serial");
if !serial_path.exists() {
continue;
}
let mut data: String = fs::read_to_string(serial_path)?;
let ch = data.pop(); debug_assert_eq!(ch, Some('\n'));
data
};
let description: String = {
let mut product_path = path.clone();
product_path.push("product");
if !product_path.exists() {
continue;
}
let mut data: String = fs::read_to_string(product_path)?;
let ch = data.pop(); debug_assert_eq!(ch, Some('\n'));
data
};
let port_letters: Option<&'static [char]> = match device_type {
DeviceType::FT2232H => Some(&['A', 'B']),
DeviceType::FT4232H | DeviceType::FT4232HA => Some(&['A', 'B', 'C', 'D']),
_ => None,
};
if let Some(port_letters) = port_letters {
for letter in port_letters {
let mut port_serial = serial.clone();
port_serial.push(*letter);
let mut port_description = description.clone();
port_description.push(' ');
port_description.push(*letter);
devices.push(DeviceInfo {
port_open: false,
speed: None,
device_type: device_type,
product_id: pid,
vendor_id: FTDI_VID,
serial_number: port_serial,
description: port_description,
})
}
} else {
devices.push(DeviceInfo {
port_open: false,
speed: None,
device_type: device_type,
product_id: pid,
vendor_id: FTDI_VID,
serial_number: serial,
description: description,
})
}
}
devices.sort_unstable();
Ok(devices)
} else {
Ok(devices)
}
}
#[cfg(all(any(windows, doc), not(doctest)))]
#[cfg_attr(docsrs, doc(cfg(windows)))]
pub fn rescan() -> Result<(), FtStatus> {
trace!("FT_Rescan()");
let status: FT_STATUS = unsafe { FT_Rescan() };
ft_result((), status)
}
#[derive(Debug)]
pub struct Ftdi {
handle: FT_HANDLE,
}
#[derive(Debug)]
pub struct Ft232h {
ftdi: Ftdi,
}
#[derive(Debug)]
pub struct Ft232r {
ftdi: Ftdi,
}
#[derive(Debug)]
pub struct Ft2232h {
ftdi: Ftdi,
}
#[derive(Debug)]
pub struct Ft4232h {
ftdi: Ftdi,
}
#[derive(Debug)]
pub struct Ft4232ha {
ftdi: Ftdi,
}
#[derive(Debug)]
pub struct FtXSeries {
ftdi: Ftdi,
}
pub trait FtdiCommon {
const DEVICE_TYPE: DeviceType;
fn handle(&mut self) -> FT_HANDLE;
fn device_type(&mut self) -> Result<DeviceType, FtStatus> {
if let Ok(info) = self.device_info() {
Ok(info.device_type)
} else {
match self.eeprom_word_read(0x3)? {
0x0200 => Ok(DeviceType::FTAM),
0x0400 => Ok(DeviceType::FTBM),
0x0500 => Ok(DeviceType::FT2232C),
0x0600 => Ok(DeviceType::FT232R),
0x0700 => Ok(DeviceType::FT2232H),
0x0800 => Ok(DeviceType::FT4232H),
0x0900 => Ok(DeviceType::FT232H),
0x1000 => Ok(DeviceType::FT_X_SERIES),
0x1700 => Ok(DeviceType::FT4222H_3),
0x1800 => Ok(DeviceType::FT4222H_0),
0x1900 => Ok(DeviceType::FT4222H_1_2),
0x2100 => Ok(DeviceType::FT4222_PROG),
0x3600 => Ok(DeviceType::FT4232HA),
_ => Err(FtStatus::OTHER_ERROR),
}
}
}
fn device_info(&mut self) -> Result<DeviceInfo, FtStatus> {
let mut device_type: u32 = 0;
let mut device_id: u32 = 0;
let mut serial_number: [i8; STRING_LEN] = [0; STRING_LEN];
let mut description: [i8; STRING_LEN] = [0; STRING_LEN];
trace!("FT_GetDeviceInfo({:?}, _, _, _, _, NULL)", self.handle());
let status: FT_STATUS = unsafe {
FT_GetDeviceInfo(
self.handle(),
&mut device_type,
&mut device_id,
serial_number.as_mut_ptr() as *mut c_char,
description.as_mut_ptr() as *mut c_char,
std::ptr::null_mut(),
)
};
let (vid, pid) = vid_pid_from_id(device_id);
ft_result(
DeviceInfo {
port_open: true,
speed: None,
device_type: device_type.into(),
product_id: pid,
vendor_id: vid,
serial_number: slice_into_string(serial_number.as_ref()),
description: slice_into_string(description.as_ref()),
},
status,
)
}
fn driver_version(&mut self) -> Result<Version, FtStatus> {
let mut version: u32 = 0;
trace!("FT_GetDriverVersion({:?}, _)", self.handle());
let status: FT_STATUS = unsafe { FT_GetDriverVersion(self.handle(), &mut version) };
ft_result(Version::with_raw(version), status)
}
fn reset(&mut self) -> Result<(), FtStatus> {
trace!("FT_ResetDevice({:?})", self.handle());
let status: FT_STATUS = unsafe { FT_ResetDevice(self.handle()) };
ft_result((), status)
}
fn set_usb_parameters(&mut self, in_transfer_size: u32) -> Result<(), FtStatus> {
const MAX: u32 = 64 * 1024;
const MIN: u32 = 64;
assert!(
in_transfer_size <= MAX,
"in_transfer_size of {in_transfer_size} exceeds maximum of {MAX}"
);
assert!(
in_transfer_size >= MIN,
"in_transfer_size of {in_transfer_size} exceeds minimum of {MIN}"
);
assert!(
in_transfer_size % MIN == 0,
"in_transfer_size of {in_transfer_size} not a multiple of {MIN}"
);
trace!(
"FT_SetUSBParameters({:?}, {}, {})",
self.handle(),
in_transfer_size,
in_transfer_size
);
let status: FT_STATUS =
unsafe { FT_SetUSBParameters(self.handle(), in_transfer_size, in_transfer_size) };
ft_result((), status)
}
fn set_chars(
&mut self,
event_char: u8,
event_enable: bool,
error_char: u8,
error_enable: bool,
) -> Result<(), FtStatus> {
trace!(
"FT_SetChars({:?}, {}, {}, {}, {})",
self.handle(),
event_char,
u8::from(event_enable),
error_char,
u8::from(error_enable)
);
let status: FT_STATUS = unsafe {
FT_SetChars(
self.handle(),
event_char,
u8::from(event_enable),
error_char,
u8::from(error_enable),
)
};
ft_result((), status)
}
fn set_timeouts(
&mut self,
read_timeout: Duration,
write_timeout: Duration,
) -> Result<(), FtStatus> {
let read_timeout_ms =
u32::try_from(read_timeout.as_millis()).expect("read_timeout integer overflow");
let write_timeout_ms =
u32::try_from(write_timeout.as_millis()).expect("write_timeout integer overflow");
trace!(
"FT_SetTimeouts({:?}, {}, {})",
self.handle(),
read_timeout_ms,
write_timeout_ms,
);
let status: FT_STATUS =
unsafe { FT_SetTimeouts(self.handle(), read_timeout_ms, write_timeout_ms) };
ft_result((), status)
}
fn set_deadman_timeout(&mut self, timeout: Duration) -> Result<(), FtStatus> {
let timeout_ms = u32::try_from(timeout.as_millis()).expect("timeout integer overflow");
trace!("FT_SetDeadmanTimeout({:?}, {})", self.handle(), timeout_ms);
let status: FT_STATUS = unsafe { FT_SetDeadmanTimeout(self.handle(), timeout_ms) };
ft_result((), status)
}
fn set_latency_timer(&mut self, timer: Duration) -> Result<(), FtStatus> {
let millis = timer.as_millis();
debug_assert!(millis <= 255, "duration must be <= 255ms, got {timer:?}");
let millis = u8::try_from(millis).unwrap();
trace!("FT_SetLatencyTimer({:?}, {})", self.handle(), millis);
let status: FT_STATUS = unsafe { FT_SetLatencyTimer(self.handle(), millis) };
ft_result((), status)
}
fn latency_timer(&mut self) -> Result<Duration, FtStatus> {
let mut timer: u8 = 0;
trace!("FT_GetLatencyTimer({:?}, _)", self.handle());
let status: FT_STATUS = unsafe { FT_GetLatencyTimer(self.handle(), &mut timer as *mut u8) };
ft_result(Duration::from_millis(timer as u64), status)
}
fn set_flow_control_none(&mut self) -> Result<(), FtStatus> {
trace!(
"FT_SetFlowControl({:?}, {}, 0, 0)",
self.handle(),
FT_FLOW_NONE
);
let status: FT_STATUS =
unsafe { FT_SetFlowControl(self.handle(), FT_FLOW_NONE as u16, 0, 0) };
ft_result((), status)
}
fn set_flow_control_rts_cts(&mut self) -> Result<(), FtStatus> {
trace!(
"FT_SetFlowControl({:?}, {}, 0, 0)",
self.handle(),
FT_FLOW_RTS_CTS
);
let status: FT_STATUS =
unsafe { FT_SetFlowControl(self.handle(), FT_FLOW_RTS_CTS as u16, 0, 0) };
ft_result((), status)
}
fn set_flow_control_dtr_dsr(&mut self) -> Result<(), FtStatus> {
trace!(
"FT_SetFlowControl({:?}, {}, 0, 0)",
self.handle(),
FT_FLOW_DTR_DSR
);
let status: FT_STATUS =
unsafe { FT_SetFlowControl(self.handle(), FT_FLOW_DTR_DSR as u16, 0, 0) };
ft_result((), status)
}
fn set_flow_control_xon_xoff(&mut self, xon: u8, xoff: u8) -> Result<(), FtStatus> {
trace!(
"FT_SetFlowControl({:?}, {}, {}, {})",
self.handle(),
FT_FLOW_XON_XOFF,
xon,
xoff
);
let status: FT_STATUS =
unsafe { FT_SetFlowControl(self.handle(), FT_FLOW_XON_XOFF as u16, xon, xoff) };
ft_result((), status)
}
fn set_baud_rate(&mut self, baud_rate: u32) -> Result<(), FtStatus> {
trace!("FT_SetBaudRate({:?}, {})", self.handle(), baud_rate);
let status: FT_STATUS = unsafe { FT_SetBaudRate(self.handle(), baud_rate) };
ft_result((), status)
}
fn set_data_characteristics(
&mut self,
bits_per_word: BitsPerWord,
stop_bits: StopBits,
parity: Parity,
) -> Result<(), FtStatus> {
trace!(
"FT_SetDataCharacteristics({:?}, {}, {}, {})",
self.handle(),
u8::from(bits_per_word),
u8::from(stop_bits),
u8::from(parity),
);
let status: FT_STATUS = unsafe {
FT_SetDataCharacteristics(
self.handle(),
bits_per_word.into(),
stop_bits.into(),
parity.into(),
)
};
ft_result((), status)
}
fn set_dtr(&mut self) -> Result<(), FtStatus> {
trace!("FT_SetDtr({:?})", self.handle());
let status: FT_STATUS = unsafe { FT_SetDtr(self.handle()) };
ft_result((), status)
}
fn clear_dtr(&mut self) -> Result<(), FtStatus> {
trace!("FT_ClrtDtr({:?})", self.handle());
let status: FT_STATUS = unsafe { FT_ClrDtr(self.handle()) };
ft_result((), status)
}
fn set_rts(&mut self) -> Result<(), FtStatus> {
trace!("FT_SetRts({:?})", self.handle());
let status: FT_STATUS = unsafe { FT_SetRts(self.handle()) };
ft_result((), status)
}
fn clear_rts(&mut self) -> Result<(), FtStatus> {
trace!("FT_ClrtRts({:?})", self.handle());
let status: FT_STATUS = unsafe { FT_ClrRts(self.handle()) };
ft_result((), status)
}
fn set_bit_mode(&mut self, mask: u8, mode: BitMode) -> Result<(), FtStatus> {
trace!(
"FT_SetBitMode({:?}, 0x{:02X}, 0x{:02X})",
self.handle(),
mask,
mode as u8
);
let status: FT_STATUS = unsafe { FT_SetBitMode(self.handle(), mask, mode as u8) };
ft_result((), status)
}
fn bit_mode(&mut self) -> Result<u8, FtStatus> {
let mut mode: u8 = 0;
trace!("FT_GetBitMode({:?}, _)", self.handle());
let status: FT_STATUS = unsafe { FT_GetBitMode(self.handle(), &mut mode) };
ft_result(mode, status)
}
fn set_break_on(&mut self) -> Result<(), FtStatus> {
trace!("FT_SetBreakOn({:?})", self.handle());
let status: FT_STATUS = unsafe { FT_SetBreakOn(self.handle()) };
ft_result((), status)
}
fn set_break_off(&mut self) -> Result<(), FtStatus> {
trace!("FT_SetBreakOff({:?})", self.handle());
let status: FT_STATUS = unsafe { FT_SetBreakOff(self.handle()) };
ft_result((), status)
}
fn queue_status(&mut self) -> Result<usize, FtStatus> {
let mut queue_status: u32 = 0;
trace!("FT_GetQueueStatus({:?}, _)", self.handle());
let status: FT_STATUS = unsafe { FT_GetQueueStatus(self.handle(), &mut queue_status) };
ft_result(usize::try_from(queue_status).unwrap(), status)
}
fn status(&mut self) -> Result<DeviceStatus, FtStatus> {
let mut ammount_in_rx_queue: u32 = 0;
let mut ammount_in_tx_queue: u32 = 0;
let mut event_status: u32 = 0;
trace!("FT_GetStatus({:?}, _, _, _)", self.handle());
let status: FT_STATUS = unsafe {
FT_GetStatus(
self.handle(),
&mut ammount_in_rx_queue,
&mut ammount_in_tx_queue,
&mut event_status,
)
};
ft_result(
DeviceStatus {
ammount_in_rx_queue,
ammount_in_tx_queue,
event_status,
},
status,
)
}
fn read(&mut self, buf: &mut [u8]) -> Result<usize, FtStatus> {
let mut bytes_returned: u32 = 0;
let len: u32 = u32::try_from(buf.len()).unwrap();
trace!("FT_Read({:?}, _, {}, _)", self.handle(), len);
let status: FT_STATUS = unsafe {
FT_Read(
self.handle(),
buf.as_mut_ptr() as *mut c_void,
len,
&mut bytes_returned,
)
};
ft_result(usize::try_from(bytes_returned).unwrap(), status)
}
fn read_all(&mut self, buf: &mut [u8]) -> Result<(), TimeoutError> {
let num_read = self.read(buf)?;
if num_read != buf.len() {
Err(TimeoutError::Timeout {
expected: buf.len(),
actual: num_read,
})
} else {
Ok(())
}
}
fn write_all(&mut self, buf: &[u8]) -> Result<(), TimeoutError> {
let num_written = self.write(buf)?;
if num_written != buf.len() {
Err(TimeoutError::Timeout {
expected: buf.len(),
actual: num_written,
})
} else {
Ok(())
}
}
fn write(&mut self, buf: &[u8]) -> Result<usize, FtStatus> {
let mut bytes_written: u32 = 0;
let len: u32 = u32::try_from(buf.len()).unwrap();
trace!("FT_Write({:?}, _, {}, _)", self.handle(), len);
let status: FT_STATUS = unsafe {
FT_Write(
self.handle(),
buf.as_ptr() as *mut c_void,
len,
&mut bytes_written,
)
};
ft_result(usize::try_from(bytes_written).unwrap(), status)
}
fn purge_tx(&mut self) -> Result<(), FtStatus> {
trace!("FT_Purge({:?}, {})", self.handle(), FT_PURGE_TX);
let status: FT_STATUS = unsafe { FT_Purge(self.handle(), FT_PURGE_TX) };
ft_result((), status)
}
fn purge_rx(&mut self) -> Result<(), FtStatus> {
trace!("FT_Purge({:?}, {})", self.handle(), FT_PURGE_RX);
let status: FT_STATUS = unsafe { FT_Purge(self.handle(), FT_PURGE_RX) };
ft_result((), status)
}
fn purge_all(&mut self) -> Result<(), FtStatus> {
trace!(
"FT_Purge({:?}, {})",
self.handle(),
FT_PURGE_TX | FT_PURGE_RX
);
let status: FT_STATUS = unsafe { FT_Purge(self.handle(), FT_PURGE_TX | FT_PURGE_RX) };
ft_result((), status)
}
fn close(&mut self) -> Result<(), FtStatus> {
trace!("FT_Close({:?})", self.handle());
let status: FT_STATUS = unsafe { FT_Close(self.handle()) };
ft_result((), status)
}
#[cfg(all(any(windows, doc), not(doctest)))]
#[cfg_attr(docsrs, doc(cfg(windows)))]
fn com_port_number(&mut self) -> Result<Option<u32>, FtStatus> {
let mut num: i32 = -1;
trace!("FT_GetComPortNumber({:?}, _)", self.handle());
let status: FT_STATUS = unsafe { FT_GetComPortNumber(self.handle(), &mut num as *mut i32) };
ft_result(
if num == -1 {
None
} else {
Some(u32::try_from(num).unwrap())
},
status,
)
}
#[cfg(all(any(windows, doc), not(doctest)))]
#[cfg_attr(docsrs, doc(cfg(windows)))]
fn reset_port(&mut self) -> Result<(), FtStatus> {
trace!("FT_ResetPort({:?})", self.handle());
let status: FT_STATUS = unsafe { FT_ResetPort(self.handle()) };
ft_result((), status)
}
#[cfg(all(any(windows, doc), not(doctest)))]
#[cfg_attr(docsrs, doc(cfg(windows)))]
fn cycle_port(&mut self) -> Result<(), FtStatus> {
trace!("FT_CyclePort({:?})", self.handle());
let status: FT_STATUS = unsafe { FT_CyclePort(self.handle()) };
ft_result((), status)
}
fn modem_status(&mut self) -> Result<ModemStatus, FtStatus> {
let mut modem_status: u32 = 0;
trace!("FT_GetModemStatus({:?}, _)", self.handle());
let status: FT_STATUS = unsafe { FT_GetModemStatus(self.handle(), &mut modem_status) };
ft_result(ModemStatus::new(modem_status), status)
}
fn eeprom_word_read(&mut self, offset: u32) -> Result<u16, FtStatus> {
let mut value: u16 = 0;
trace!("FT_ReadEE({:?}, {}, _)", self.handle(), offset);
let status: FT_STATUS = unsafe { FT_ReadEE(self.handle(), offset, &mut value) };
ft_result(value, status)
}
fn eeprom_word_write(&mut self, offset: u32, value: u16) -> Result<(), FtStatus> {
trace!(
"FT_WriteEE({:?}, 0x{:X}, 0x{:04X})",
self.handle(),
offset,
value
);
let status: FT_STATUS = unsafe { FT_WriteEE(self.handle(), offset, value) };
ft_result((), status)
}
fn eeprom_erase(&mut self) -> Result<(), FtStatus> {
trace!("FT_EraseEE({:?})", self.handle());
let status: FT_STATUS = unsafe { FT_EraseEE(self.handle()) };
ft_result((), status)
}
fn eeprom_user_size(&mut self) -> Result<usize, FtStatus> {
let mut value: u32 = 0;
trace!("FT_EE_UASize({:?}, _)", self.handle());
let status: FT_STATUS = unsafe { FT_EE_UASize(self.handle(), &mut value) };
ft_result(usize::try_from(value).unwrap(), status)
}
fn eeprom_user_read(&mut self, buf: &mut [u8]) -> Result<usize, FtStatus> {
let mut num_read: u32 = 0;
let len: u32 = u32::try_from(buf.len()).unwrap();
trace!("FT_EE_UARead({:?}, _, {}, _)", self.handle(), len);
let status: FT_STATUS =
unsafe { FT_EE_UARead(self.handle(), buf.as_mut_ptr(), len, &mut num_read) };
ft_result(usize::try_from(num_read).unwrap(), status)
}
fn eeprom_user_write(&mut self, buf: &[u8]) -> Result<(), FtStatus> {
let len: u32 = u32::try_from(buf.len()).unwrap();
trace!("FT_EE_UAWrite({:?}, _, {})", self.handle(), len);
let status: FT_STATUS =
unsafe { FT_EE_UAWrite(self.handle(), buf.as_ptr() as *mut u8, len) };
ft_result((), status)
}
}
pub trait FtdiEeprom<
T: std::fmt::Debug + std::convert::From<Eeprom>,
Eeprom: Default + std::fmt::Debug + std::convert::From<T>,
>: FtdiCommon
{
fn eeprom_read(&mut self) -> Result<(Eeprom, EepromStrings), FtStatus> {
let mut manufacturer: [i8; STRING_LEN] = [0; STRING_LEN];
let mut manufacturer_id: [i8; STRING_LEN] = [0; STRING_LEN];
let mut description: [i8; STRING_LEN] = [0; STRING_LEN];
let mut serial_number: [i8; STRING_LEN] = [0; STRING_LEN];
let mut eeprom_data: T = Eeprom::default().into();
let eeprom_data_size = u32::try_from(mem::size_of::<T>()).unwrap();
trace!(
"FT_EEPROM_Read({:?}, _, {}, _, _, _, _)",
self.handle(),
eeprom_data_size
);
let status: FT_STATUS = unsafe {
FT_EEPROM_Read(
self.handle(),
&mut eeprom_data as *mut T as *mut c_void,
eeprom_data_size,
manufacturer.as_mut_ptr() as *mut c_char,
manufacturer_id.as_mut_ptr() as *mut c_char,
description.as_mut_ptr() as *mut c_char,
serial_number.as_mut_ptr() as *mut c_char,
)
};
ft_result(
(
eeprom_data.into(),
EepromStrings::with_slices(
&manufacturer,
&manufacturer_id,
&description,
&serial_number,
)
.unwrap(),
),
status,
)
}
fn eeprom_program(&mut self, eeprom: Eeprom, strings: EepromStrings) -> Result<(), FtStatus> {
let manufacturer = std::ffi::CString::new(strings.manufacturer()).unwrap();
let manufacturer_id = std::ffi::CString::new(strings.manufacturer_id()).unwrap();
let description = std::ffi::CString::new(strings.description()).unwrap();
let serial_number = std::ffi::CString::new(strings.serial_number()).unwrap();
let mut eeprom_data: T = eeprom.into();
let eeprom_data_size = u32::try_from(mem::size_of::<T>()).unwrap();
trace!(
r#"FT_EEPROM_Program({:?}, {:?}, {}, "{}", "{}", "{}", "{}")"#,
self.handle(),
eeprom_data,
eeprom_data_size,
strings.manufacturer(),
strings.manufacturer_id(),
strings.description(),
strings.serial_number(),
);
let status: FT_STATUS = unsafe {
FT_EEPROM_Program(
self.handle(),
&mut eeprom_data as *mut T as *mut c_void,
eeprom_data_size,
manufacturer.as_ptr() as *mut c_char,
manufacturer_id.as_ptr() as *mut c_char,
description.as_ptr() as *mut c_char,
serial_number.as_ptr() as *mut c_char,
)
};
ft_result((), status)
}
}
fn str_to_cstring(s: &str) -> std::ffi::CString {
std::ffi::CString::new(s).unwrap_or(std::ffi::CString::from(c""))
}
fn ft_open_ex(arg: &str, flag: u32) -> Result<FT_HANDLE, FtStatus> {
let mut handle: FT_HANDLE = std::ptr::null_mut();
let cstr_arg = str_to_cstring(arg);
trace!(r#"FT_OpenEx("{}", {}, _)"#, arg, flag);
let status: FT_STATUS =
unsafe { FT_OpenEx(cstr_arg.as_ptr() as *mut c_void, flag, &mut handle) };
ft_result(handle, status)
}
impl Ftdi {
pub fn new() -> Result<Ftdi, FtStatus> {
Ftdi::with_index(0)
}
pub fn with_index(index: i32) -> Result<Ftdi, FtStatus> {
let mut handle: FT_HANDLE = std::ptr::null_mut();
trace!("FT_Open({}, _)", index);
let status: FT_STATUS = unsafe { FT_Open(index, &mut handle) };
ft_result(Ftdi { handle }, status)
}
pub fn with_serial_number(serial_number: &str) -> Result<Ftdi, FtStatus> {
let handle = ft_open_ex(serial_number, FT_OPEN_BY_SERIAL_NUMBER)?;
Ok(Ftdi { handle })
}
pub fn with_description(description: &str) -> Result<Ftdi, FtStatus> {
let handle = ft_open_ex(description, FT_OPEN_BY_DESCRIPTION)?;
Ok(Ftdi { handle })
}
}
impl Ft232h {
pub unsafe fn with_serial_number_unchecked(serial_number: &str) -> Result<Ft232h, FtStatus> {
let handle = ft_open_ex(serial_number, FT_OPEN_BY_SERIAL_NUMBER)?;
Ok(Ft232h {
ftdi: Ftdi { handle },
})
}
pub fn with_serial_number(serial_number: &str) -> Result<Ft232h, DeviceTypeError> {
Ftdi::with_serial_number(serial_number)?.try_into()
}
pub fn with_description(description: &str) -> Result<Ft232h, DeviceTypeError> {
Ftdi::with_description(description)?.try_into()
}
}
impl Ft232r {
pub unsafe fn with_serial_number_unchecked(serial_number: &str) -> Result<Ft232r, FtStatus> {
let handle = ft_open_ex(serial_number, FT_OPEN_BY_SERIAL_NUMBER)?;
Ok(Ft232r {
ftdi: Ftdi { handle },
})
}
pub fn with_serial_number(serial_number: &str) -> Result<Ft232r, DeviceTypeError> {
Ftdi::with_serial_number(serial_number)?.try_into()
}
pub fn with_description(description: &str) -> Result<Ft232r, DeviceTypeError> {
Ftdi::with_description(description)?.try_into()
}
}
impl Ft2232h {
pub unsafe fn with_serial_number_unchecked(serial_number: &str) -> Result<Ft2232h, FtStatus> {
let handle = ft_open_ex(serial_number, FT_OPEN_BY_SERIAL_NUMBER)?;
Ok(Ft2232h {
ftdi: Ftdi { handle },
})
}
pub fn with_serial_number(serial_number: &str) -> Result<Ft2232h, DeviceTypeError> {
Ftdi::with_serial_number(serial_number)?.try_into()
}
pub fn with_description(description: &str) -> Result<Ft2232h, DeviceTypeError> {
Ftdi::with_description(description)?.try_into()
}
}
impl Ft4232h {
pub unsafe fn with_serial_number_unchecked(serial_number: &str) -> Result<Ft4232h, FtStatus> {
let handle = ft_open_ex(serial_number, FT_OPEN_BY_SERIAL_NUMBER)?;
Ok(Ft4232h {
ftdi: Ftdi { handle },
})
}
pub fn with_serial_number(serial_number: &str) -> Result<Ft4232h, DeviceTypeError> {
Ftdi::with_serial_number(serial_number)?.try_into()
}
pub fn with_description(description: &str) -> Result<Ft4232h, DeviceTypeError> {
Ftdi::with_description(description)?.try_into()
}
}
impl Ft4232ha {
pub unsafe fn with_serial_number_unchecked(serial_number: &str) -> Result<Ft4232ha, FtStatus> {
let handle = ft_open_ex(serial_number, FT_OPEN_BY_SERIAL_NUMBER)?;
Ok(Ft4232ha {
ftdi: Ftdi { handle },
})
}
pub fn with_serial_number(serial_number: &str) -> Result<Ft4232ha, DeviceTypeError> {
Ftdi::with_serial_number(serial_number)?.try_into()
}
pub fn with_description(description: &str) -> Result<Ft4232ha, DeviceTypeError> {
Ftdi::with_description(description)?.try_into()
}
}
impl FtdiCommon for Ftdi {
const DEVICE_TYPE: DeviceType = DeviceType::Unknown;
fn handle(&mut self) -> FT_HANDLE {
self.handle
}
}
impl Drop for Ftdi {
fn drop(&mut self) {
self.close().ok();
}
}
unsafe impl Send for Ftdi {}
unsafe impl Sync for Ftdi {}
macro_rules! impl_ftdi_common_for {
($DEVICE:ident, $TYPE:expr) => {
impl FtdiCommon for $DEVICE {
const DEVICE_TYPE: DeviceType = $TYPE;
fn handle(&mut self) -> FT_HANDLE {
self.ftdi.handle
}
fn device_type(&mut self) -> Result<DeviceType, FtStatus> {
Ok(Self::DEVICE_TYPE)
}
}
};
}
macro_rules! impl_mpsse_cmd_executor_for {
($DEVICE:ident, $TYPE:expr) => {
impl MpsseCmdExecutor for $DEVICE {
type Error = TimeoutError;
fn init(&mut self, settings: &MpsseSettings) -> Result<(), Self::Error> {
self.initialize_mpsse(settings)
}
fn send(&mut self, data: &[u8]) -> Result<(), Self::Error> {
self.write_all(data)
}
fn recv(&mut self, data: &mut [u8]) -> Result<(), Self::Error> {
self.read_all(data)
}
}
};
}
macro_rules! impl_try_from_for {
($DEVICE:ident) => {
impl TryFrom<Ftdi> for $DEVICE {
type Error = DeviceTypeError;
fn try_from(mut ft: Ftdi) -> Result<Self, Self::Error> {
let device_type: DeviceType = ft.device_type()?;
if device_type != Self::DEVICE_TYPE {
Err(DeviceTypeError::DeviceType {
expected: $DEVICE::DEVICE_TYPE,
detected: device_type,
})
} else {
Ok($DEVICE { ftdi: ft })
}
}
}
};
}
impl_ftdi_common_for!(Ft232h, DeviceType::FT232H);
impl_ftdi_common_for!(Ft232r, DeviceType::FT232R);
impl_ftdi_common_for!(Ft2232h, DeviceType::FT2232H);
impl_ftdi_common_for!(Ft4232h, DeviceType::FT4232H);
impl_ftdi_common_for!(Ft4232ha, DeviceType::FT4232HA);
impl_ftdi_common_for!(FtXSeries, DeviceType::FT_X_SERIES);
impl_mpsse_cmd_executor_for!(Ft232h, DeviceType::FT232H);
impl_mpsse_cmd_executor_for!(Ft2232h, DeviceType::FT2232H);
impl_mpsse_cmd_executor_for!(Ft4232h, DeviceType::FT4232H);
impl_mpsse_cmd_executor_for!(Ft4232ha, DeviceType::FT4232HA);
impl_try_from_for!(Ft232h);
impl_try_from_for!(Ft232r);
impl_try_from_for!(Ft2232h);
impl_try_from_for!(Ft4232h);
impl_try_from_for!(Ft4232ha);
impl_try_from_for!(FtXSeries);
impl FtdiEeprom<FT_EEPROM_232H, Eeprom232h> for Ft232h {}
impl FtdiEeprom<FT_EEPROM_232R, Eeprom232r> for Ft232r {}
impl FtdiEeprom<FT_EEPROM_2232H, Eeprom2232h> for Ft2232h {}
impl FtdiEeprom<FT_EEPROM_4232H, Eeprom4232h> for Ft4232h {}
impl FtdiEeprom<FT_EEPROM_X_SERIES, EepromXSeries> for FtXSeries {}
impl FtdiMpsse for Ft232h {}
impl FtdiMpsse for Ft2232h {}
impl FtdiMpsse for Ft4232h {}
impl FtdiMpsse for Ft4232ha {}
impl Ftx232hMpsse for Ft232h {}
impl Ftx232hMpsse for Ft2232h {}
impl Ftx232hMpsse for Ft4232h {}
impl Ftx232hMpsse for Ft4232ha {}
#[cfg(test)]
mod tests {
use super::str_to_cstring;
#[test]
fn str_to_cstr_basic() {
assert_eq!(
str_to_cstring("Hello, World!"),
std::ffi::CString::from(c"Hello, World!")
);
}
#[test]
fn str_to_cstr_interior_null() {
str_to_cstring("\0\u{e}.r");
}
}