├── .github └── workflows │ └── tests.yml ├── .gitignore ├── Cargo.toml ├── README.md ├── examples └── dlp-loopback-tester.rs └── src ├── error.rs ├── lib.rs └── opener.rs /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | linux: 11 | strategy: 12 | matrix: 13 | include: 14 | - name: MSRV (minimal) 15 | toolchain: 1.40.0 16 | features: "" 17 | - name: MSRV (vendored) 18 | toolchain: 1.40.0 19 | features: vendored 20 | native_deps: libusb-1.0-0-dev 21 | - name: MSRV (libusb1-sys) 22 | toolchain: 1.40.0 23 | features: libusb1-sys 24 | - name: vendored,libusb1-sys 25 | toolchain: 1.40.0 26 | features: libusb1-sys,vendored 27 | native_deps: libusb-1.0-0-dev 28 | - name: Nightly 29 | toolchain: nightly 30 | features: libusb1-sys,vendored 31 | native_deps: libusb-1.0-0-dev 32 | fail-fast: false 33 | 34 | name: ${{ matrix.name }} 35 | runs-on: ubuntu-latest 36 | steps: 37 | - uses: actions/checkout@v2 38 | with: 39 | submodules: true 40 | fetch-depth: 0 41 | - name: Install toolchain 42 | uses: actions-rs/toolchain@v1.0.3 43 | with: 44 | toolchain: ${{ matrix.toolchain }} 45 | profile: minimal 46 | default: true 47 | - if: ${{ matrix.native_deps || 'libftdi1-dev' }} 48 | name: Install native packages (${{ matrix.native_deps || 'libftdi1-dev' }}) 49 | run: sudo apt-get install ${{ matrix.native_deps || 'libftdi1-dev' }} 50 | - run: cargo build --verbose --features=${{ matrix.features }} 51 | - run: cargo test --verbose --features=${{ matrix.features }} 52 | 53 | windows-msvc: 54 | runs-on: windows-latest 55 | env: 56 | VCPKGRS_DYNAMIC: 1 57 | steps: 58 | - uses: actions/checkout@v2 59 | with: 60 | fetch-depth: 0 61 | - name: Install toolchain 62 | uses: actions-rs/toolchain@v1.0.3 63 | with: 64 | toolchain: 1.40.0 65 | target: x86_64-pc-windows-msvc 66 | profile: minimal 67 | default: true 68 | - name: Restore / setup vcpkg and libftdi1 69 | uses: lukka/run-vcpkg@v7 70 | with: 71 | vcpkgArguments: 'libftdi1:x64-windows' 72 | vcpkgGitCommitId: '7dbc05515b44bf54d2a42b4da9d1e1f910868b86' # Match probe-rs after cmake 3.21 problems 73 | - name: Build and run tests (pregenerated) 74 | run: cargo test --verbose 75 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | 4 | .idea 5 | .vscode 6 | rusty-tags.vi 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ftdi" 3 | version = "0.1.3" 4 | edition = "2018" 5 | 6 | description = "A Rust wrapper over libftdi1 library for FTDI devices" 7 | #documentation = "" 8 | #readme = "" 9 | homepage = "https://github.com/tanriol/ftdi-rs" 10 | repository = "https://github.com/tanriol/ftdi-rs" 11 | 12 | license = "MIT" 13 | keywords = ["hardware", "ftdi", "libftdi1"] 14 | categories = ["api-bindings", "hardware-support"] 15 | 16 | [features] 17 | libusb1-sys = ["libftdi1-sys/libusb1-sys"] 18 | vendored = ["libftdi1-sys/vendored"] 19 | 20 | [dependencies] 21 | libftdi1-sys = "1.1" 22 | thiserror = "1.0.15" 23 | ftdi-mpsse = "0.1.0" 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `ftdi` 2 | 3 | This is a library wrapping `libftdi1-sys` and providing a more idiomatic interface for working with FTDI devices in Rust. 4 | 5 | # MSRV 6 | 7 | At the moment the MSRV of this crate is Rust 1.40.0. 8 | 9 | # Changelog 10 | 11 | ## 0.1.3 12 | 13 | MSRV increased from 1.34.0 to 1.40.0. 14 | 15 | Additions: 16 | 17 | - extended MPSSE support via `ftdi-mpsse` 18 | - event char and error char configuration 19 | - `libftdi` context getter 20 | 21 | ## 0.1.2 22 | 23 | Additions: 24 | 25 | - bitmode configuration 26 | - line properties configuration 27 | - flow control configuration 28 | - baud rate configuration 29 | - a number of useful derives on Interface 30 | 31 | ## 0.1.1 32 | 33 | Additions: 34 | 35 | - a `vendored` feature for `libftdi1-sys`. 36 | 37 | ## 0.1.0 38 | 39 | The initial release with something like a stable API. 40 | -------------------------------------------------------------------------------- /examples/dlp-loopback-tester.rs: -------------------------------------------------------------------------------- 1 | //! A simple test script for testing the loopback features 2 | //! of the FTDI DLP-HS-FPGA3's default FPGA firmware. 3 | //! Mainly used as a compile test 4 | 5 | extern crate ftdi; 6 | 7 | use std::io::{Read, Write}; 8 | 9 | fn main() { 10 | println!("Starting tester..."); 11 | let device = ftdi::find_by_vid_pid(0x0403, 0x6010) 12 | .interface(ftdi::Interface::A) 13 | .open(); 14 | 15 | if let Ok(mut device) = device { 16 | println!("Device found and opened"); 17 | device.usb_reset().unwrap(); 18 | device.usb_purge_buffers().unwrap(); 19 | device.set_latency_timer(2).unwrap(); 20 | 21 | // Junk test 22 | let mut junk = vec![]; 23 | device.read_to_end(&mut junk).unwrap(); 24 | if junk.len() > 0 { 25 | println!("Junk in line: {:?}", junk); 26 | } 27 | 28 | // Ping test 29 | device.write_all(&vec![0x00]).unwrap(); 30 | let mut reply = vec![]; 31 | device.read_to_end(&mut reply).unwrap(); 32 | if reply != vec![0x56] { 33 | println!("Wrong ping reply {:?} (expected {:?}", reply, vec![0x56]); 34 | } 35 | 36 | for num in 0u16..256 { 37 | let num = num as u8; 38 | 39 | // Loopback test 40 | device.write_all(&vec![0x20, num]).unwrap(); 41 | let mut reply = vec![]; 42 | device.read_to_end(&mut reply).unwrap(); 43 | if reply != vec![num] { 44 | println!("Wrong loopback reply {:?} (expected {:?}", reply, vec![num]); 45 | } 46 | 47 | // Complement loopback test 48 | device.write_all(&vec![0x21, num]).unwrap(); 49 | let mut reply = vec![]; 50 | device.read_to_end(&mut reply).unwrap(); 51 | let complement = 255 - num; 52 | if reply != vec![complement] { 53 | println!( 54 | "Wrong complement reply {:?} (expected {:?}", 55 | reply, 56 | vec![complement] 57 | ); 58 | } 59 | } 60 | println!("Testing finished"); 61 | } else { 62 | println!("Cannot find/open device, runtime tests are NOP"); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | use std::ffi::CStr; 4 | use std::io; 5 | 6 | use super::ffi; 7 | 8 | #[derive(Debug, Error)] 9 | pub enum Error { 10 | #[error("failed to enumerate devices to open the correct one")] 11 | EnumerationFailed, 12 | #[error("the specified device could not be found")] 13 | DeviceNotFound, 14 | #[error("libftdi context allocation failed")] 15 | AllocationFailed, 16 | #[error("failed to open the specified device")] 17 | AccessFailed, 18 | #[error("the requested interface could not be claimed")] 19 | ClaimFailed, 20 | #[error("the device has been disconnected from the system")] 21 | Disconnected, 22 | #[error("the device does not have the specified interface")] 23 | NoSuchInterface, 24 | #[error("libftdi reported error to perform operation")] 25 | RequestFailed, 26 | #[error("input value invalid: {0}")] 27 | InvalidInput(&'static str), 28 | 29 | #[error("unknown or unexpected libftdi error")] 30 | Unknown { source: LibFtdiError }, 31 | 32 | #[error("INTERNAL, DO NOT USE")] 33 | #[doc(hidden)] 34 | __NonExhaustive, 35 | } 36 | 37 | impl Error { 38 | pub(crate) fn unknown(context: *mut ffi::ftdi_context) -> Self { 39 | let message = unsafe { CStr::from_ptr(ffi::ftdi_get_error_string(context)) } 40 | .to_str() 41 | .expect("all error strings are expected to be ASCII"); 42 | Error::Unknown { 43 | source: LibFtdiError { message }, 44 | } 45 | } 46 | } 47 | 48 | pub type Result = std::result::Result; 49 | 50 | #[derive(Debug, Error)] 51 | #[error("libftdi: {message}")] 52 | pub struct LibFtdiError { 53 | message: &'static str, 54 | } 55 | 56 | // Ideally this should be using libusb bindings, but we don't depend on any specific USB crate yet 57 | pub(crate) fn libusb_to_io(code: i32) -> io::Error { 58 | io::Error::new(io::ErrorKind::Other, format!("libusb error code {}", code)) 59 | } 60 | 61 | pub(crate) fn libftdi_to_io(err: Error) -> io::Error { 62 | io::Error::new(io::ErrorKind::Other, err) 63 | } 64 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! An incomplete Rust wrapper over the `libftdi1` library for working with FTDI devices 2 | //! 3 | //! Note: the library interface is *definitely* unstable for now 4 | 5 | use ftdi_mpsse::MpsseCmdBuilder; 6 | use ftdi_mpsse::MpsseCmdExecutor; 7 | use ftdi_mpsse::MpsseSettings; 8 | 9 | use libftdi1_sys as ffi; 10 | 11 | use std::convert::TryFrom; 12 | use std::convert::TryInto; 13 | use std::io::{self, Read, Write}; 14 | 15 | pub mod error; 16 | mod opener; 17 | 18 | pub use error::{Error, Result}; 19 | #[cfg(feature = "libusb1-sys")] 20 | pub use opener::find_by_raw_libusb_device; 21 | pub use opener::{find_by_bus_address, find_by_vid_pid, Opener}; 22 | 23 | use error::libftdi_to_io; 24 | use error::libusb_to_io; 25 | 26 | /// The target interface 27 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 28 | pub enum Interface { 29 | A, 30 | B, 31 | C, 32 | D, 33 | Any, 34 | } 35 | 36 | impl Into for Interface { 37 | fn into(self) -> ffi::ftdi_interface { 38 | match self { 39 | Interface::A => ffi::ftdi_interface::INTERFACE_A, 40 | Interface::B => ffi::ftdi_interface::INTERFACE_B, 41 | Interface::C => ffi::ftdi_interface::INTERFACE_C, 42 | Interface::D => ffi::ftdi_interface::INTERFACE_D, 43 | Interface::Any => ffi::ftdi_interface::INTERFACE_ANY, 44 | } 45 | } 46 | } 47 | 48 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 49 | pub enum Parity { 50 | None, 51 | Odd, 52 | Even, 53 | Mark, 54 | Space, 55 | } 56 | 57 | impl Into for Parity { 58 | fn into(self) -> ffi::ftdi_parity_type { 59 | match self { 60 | Parity::None => ffi::ftdi_parity_type::NONE, 61 | Parity::Odd => ffi::ftdi_parity_type::ODD, 62 | Parity::Even => ffi::ftdi_parity_type::EVEN, 63 | Parity::Mark => ffi::ftdi_parity_type::MARK, 64 | Parity::Space => ffi::ftdi_parity_type::SPACE, 65 | } 66 | } 67 | } 68 | 69 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 70 | pub enum Bits { 71 | Seven, 72 | Eight, 73 | } 74 | 75 | impl Into for Bits { 76 | fn into(self) -> ffi::ftdi_bits_type { 77 | match self { 78 | Bits::Seven => ffi::ftdi_bits_type::BITS_7, 79 | Bits::Eight => ffi::ftdi_bits_type::BITS_8, 80 | } 81 | } 82 | } 83 | 84 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 85 | pub enum StopBits { 86 | One, 87 | OneHalf, 88 | Two, 89 | } 90 | 91 | impl Into for StopBits { 92 | fn into(self) -> ffi::ftdi_stopbits_type { 93 | match self { 94 | StopBits::One => ffi::ftdi_stopbits_type::STOP_BIT_1, 95 | StopBits::OneHalf => ffi::ftdi_stopbits_type::STOP_BIT_15, 96 | StopBits::Two => ffi::ftdi_stopbits_type::STOP_BIT_2, 97 | } 98 | } 99 | } 100 | 101 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 102 | pub enum FlowControl { 103 | Disabled, 104 | RtsCts, 105 | DtrDsr, 106 | XonXoff, 107 | } 108 | 109 | impl FlowControl { 110 | pub fn to_ffi(self) -> i32 { 111 | match self { 112 | FlowControl::Disabled => ffi::SIO_XON_XOFF_HS, 113 | FlowControl::RtsCts => ffi::SIO_RTS_CTS_HS, 114 | FlowControl::DtrDsr => ffi::SIO_DTR_DSR_HS, 115 | FlowControl::XonXoff => ffi::SIO_XON_XOFF_HS, 116 | } 117 | } 118 | } 119 | 120 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 121 | pub enum BitMode { 122 | Reset, 123 | Bitbang, 124 | Mpsse, 125 | SyncBB, 126 | Mcu, 127 | Opto, 128 | CBus, 129 | Syncff, 130 | Ft1284, 131 | } 132 | 133 | impl BitMode { 134 | pub fn to_ffi(self) -> ffi::ftdi_mpsse_mode { 135 | match self { 136 | BitMode::Reset => ffi::ftdi_mpsse_mode::BITMODE_RESET, 137 | BitMode::Bitbang => ffi::ftdi_mpsse_mode::BITMODE_BITBANG, 138 | BitMode::Mpsse => ffi::ftdi_mpsse_mode::BITMODE_MPSSE, 139 | BitMode::SyncBB => ffi::ftdi_mpsse_mode::BITMODE_SYNCBB, 140 | BitMode::Mcu => ffi::ftdi_mpsse_mode::BITMODE_MCU, 141 | BitMode::Opto => ffi::ftdi_mpsse_mode::BITMODE_OPTO, 142 | BitMode::CBus => ffi::ftdi_mpsse_mode::BITMODE_CBUS, 143 | BitMode::Syncff => ffi::ftdi_mpsse_mode::BITMODE_SYNCFF, 144 | BitMode::Ft1284 => ffi::ftdi_mpsse_mode::BITMODE_FT1284, 145 | } 146 | } 147 | } 148 | 149 | pub struct Device { 150 | context: *mut ffi::ftdi_context, 151 | } 152 | 153 | impl Device { 154 | pub fn set_baud_rate(&mut self, rate: u32) -> Result<()> { 155 | let rate = rate.try_into().expect("baud rate should fit in an i32"); 156 | let result = unsafe { ffi::ftdi_set_baudrate(self.context, rate) }; 157 | match result { 158 | 0 => Ok(()), 159 | -1 => Err(Error::InvalidInput("unsupported baudrate")), 160 | -2 => Err(Error::RequestFailed), 161 | -3 => unreachable!("uninitialized context"), 162 | _ => Err(Error::unknown(self.context)), 163 | } 164 | } 165 | 166 | pub fn configure(&mut self, bits: Bits, stop_bits: StopBits, parity: Parity) -> Result<()> { 167 | let result = unsafe { 168 | ffi::ftdi_set_line_property(self.context, bits.into(), stop_bits.into(), parity.into()) 169 | }; 170 | match result { 171 | 0 => Ok(()), 172 | -1 => Err(Error::RequestFailed), 173 | -2 => unreachable!("uninitialized context"), 174 | _ => Err(Error::unknown(self.context)), 175 | } 176 | } 177 | 178 | pub fn usb_reset(&mut self) -> Result<()> { 179 | let result = unsafe { ffi::ftdi_usb_reset(self.context) }; 180 | match result { 181 | 0 => Ok(()), 182 | -1 => Err(Error::RequestFailed), 183 | -2 => unreachable!("uninitialized context"), 184 | _ => Err(Error::unknown(self.context)), 185 | } 186 | } 187 | 188 | pub fn usb_purge_buffers(&mut self) -> Result<()> { 189 | let result = unsafe { ffi::ftdi_usb_purge_buffers(self.context) }; 190 | match result { 191 | 0 => Ok(()), 192 | -1 /* read */ | -2 /* write */ => Err(Error::RequestFailed), 193 | -3 => unreachable!("uninitialized context"), 194 | _ => Err(Error::unknown(self.context)), 195 | } 196 | } 197 | 198 | pub fn usb_purge_tx_buffer(&mut self) -> Result<()> { 199 | let result = unsafe { ffi::ftdi_usb_purge_tx_buffer(self.context) }; 200 | match result { 201 | 0 => Ok(()), 202 | -1 => Err(Error::RequestFailed), 203 | -2 => unreachable!("uninitialized context"), 204 | _ => Err(Error::unknown(self.context)), 205 | } 206 | } 207 | 208 | pub fn usb_set_event_char(&mut self, value: Option) -> Result<()> { 209 | let result = if let Some(v) = value { 210 | unsafe { ffi::ftdi_set_event_char(self.context, v, 1) } 211 | } else { 212 | unsafe { ffi::ftdi_set_event_char(self.context, 0, 0) } 213 | }; 214 | 215 | match result { 216 | 0 => Ok(()), 217 | -1 => Err(Error::RequestFailed), 218 | -2 => unreachable!("uninitialized context"), 219 | _ => Err(Error::unknown(self.context)), 220 | } 221 | } 222 | 223 | pub fn usb_set_error_char(&mut self, value: Option) -> Result<()> { 224 | let result = if let Some(v) = value { 225 | unsafe { ffi::ftdi_set_error_char(self.context, v, 1) } 226 | } else { 227 | unsafe { ffi::ftdi_set_error_char(self.context, 0, 0) } 228 | }; 229 | 230 | match result { 231 | 0 => Ok(()), 232 | -1 => Err(Error::RequestFailed), 233 | -2 => unreachable!("uninitialized context"), 234 | _ => Err(Error::unknown(self.context)), 235 | } 236 | } 237 | 238 | pub fn set_latency_timer(&mut self, value: u8) -> Result<()> { 239 | let result = unsafe { ffi::ftdi_set_latency_timer(self.context, value) }; 240 | match result { 241 | 0 => Ok(()), 242 | -1 => Err(Error::InvalidInput("latency value out of range")), 243 | -2 => Err(Error::RequestFailed), 244 | -3 => unreachable!("uninitialized context"), 245 | _ => Err(Error::unknown(self.context)), 246 | } 247 | } 248 | 249 | pub fn latency_timer(&mut self) -> Result { 250 | let mut value = 0u8; 251 | let result = unsafe { ffi::ftdi_get_latency_timer(self.context, &mut value) }; 252 | match result { 253 | 0 => Ok(value), 254 | -1 => Err(Error::RequestFailed), 255 | -2 => unreachable!("uninitialized context"), 256 | _ => Err(Error::unknown(self.context)), 257 | } 258 | } 259 | 260 | pub fn set_write_chunksize(&mut self, value: u32) { 261 | let result = unsafe { ffi::ftdi_write_data_set_chunksize(self.context, value) }; 262 | match result { 263 | 0 => (), 264 | -1 => unreachable!("uninitialized context"), 265 | err => panic!("unknown set_write_chunksize retval {:?}", err), 266 | } 267 | } 268 | 269 | pub fn write_chunksize(&mut self) -> u32 { 270 | let mut value = 0; 271 | let result = unsafe { ffi::ftdi_write_data_get_chunksize(self.context, &mut value) }; 272 | match result { 273 | 0 => value, 274 | -1 => unreachable!("uninitialized context"), 275 | err => panic!("unknown get_write_chunksize retval {:?}", err), 276 | } 277 | } 278 | 279 | pub fn set_read_chunksize(&mut self, value: u32) { 280 | let result = unsafe { ffi::ftdi_read_data_set_chunksize(self.context, value) }; 281 | match result { 282 | 0 => (), 283 | -1 => unreachable!("uninitialized context"), 284 | err => panic!("unknown set_write_chunksize retval {:?}", err), 285 | } 286 | } 287 | 288 | pub fn read_chunksize(&mut self) -> u32 { 289 | let mut value = 0; 290 | let result = unsafe { ffi::ftdi_read_data_get_chunksize(self.context, &mut value) }; 291 | match result { 292 | 0 => value, 293 | -1 => unreachable!("uninitialized context"), 294 | err => panic!("unknown get_write_chunksize retval {:?}", err), 295 | } 296 | } 297 | 298 | pub fn set_flow_control(&mut self, flowctrl: FlowControl) -> Result<()> { 299 | let result = unsafe { ffi::ftdi_setflowctrl(self.context, flowctrl.to_ffi()) }; 300 | match result { 301 | 0 => Ok(()), 302 | -1 => Err(Error::RequestFailed), 303 | -2 => unreachable!("uninitialized context"), 304 | _ => Err(Error::unknown(self.context)), 305 | } 306 | } 307 | 308 | pub fn set_bitmode(&mut self, output_mask: u8, mode: BitMode) -> Result<()> { 309 | let mode = mode.to_ffi().0.try_into().unwrap(); 310 | let result = unsafe { ffi::ftdi_set_bitmode(self.context, output_mask, mode) }; 311 | match result { 312 | 0 => Ok(()), 313 | -1 => Err(Error::RequestFailed), 314 | -2 => unreachable!("uninitialized context"), 315 | _ => Err(Error::unknown(self.context)), 316 | } 317 | } 318 | 319 | pub fn libftdi_context(&mut self) -> *mut ffi::ftdi_context { 320 | self.context 321 | } 322 | 323 | pub fn readbuffer_remaining(&self) -> usize { 324 | unsafe { (*self.context).readbuffer_remaining } 325 | .try_into() 326 | .expect("should be nonnegative") 327 | } 328 | 329 | pub fn read_packet(&mut self, buf: &mut [u8]) -> io::Result { 330 | if self.readbuffer_remaining() > 0 { 331 | let size = std::cmp::min(buf.len(), self.readbuffer_remaining()); 332 | self.read(&mut buf[..size]) 333 | } else { 334 | if self.read(&mut buf[0..1])? == 0 { 335 | return Ok(0); 336 | } 337 | let size = std::cmp::min(buf.len(), self.readbuffer_remaining() + 1); 338 | self.read(&mut buf[1..size]).map(|len| len + 1) 339 | } 340 | } 341 | } 342 | 343 | impl Drop for Device { 344 | fn drop(&mut self) { 345 | let result = unsafe { ffi::ftdi_usb_close(self.context) }; 346 | match result { 347 | 0 => {} 348 | -1 => { /* TODO emit warning ("usb_release failed") */ } 349 | -3 => unreachable!("uninitialized context"), 350 | _ => panic!("undocumented ftdi_usb_close return value"), 351 | }; 352 | unsafe { 353 | ffi::ftdi_free(self.context); 354 | } 355 | } 356 | } 357 | 358 | impl Read for Device { 359 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 360 | let len = buf.len().try_into().unwrap_or(std::i32::MAX); 361 | let result = unsafe { ffi::ftdi_read_data(self.context, buf.as_mut_ptr(), len) }; 362 | match result { 363 | count if count >= 0 => Ok(count as usize), 364 | -666 => unreachable!("uninitialized context"), 365 | err => Err(libusb_to_io(err)), 366 | } 367 | } 368 | } 369 | 370 | impl Write for Device { 371 | fn write(&mut self, buf: &[u8]) -> io::Result { 372 | let len = buf.len().try_into().unwrap_or(std::i32::MAX); 373 | let result = unsafe { ffi::ftdi_write_data(self.context, buf.as_ptr(), len) }; 374 | match result { 375 | count if count >= 0 => Ok(count as usize), 376 | -666 => unreachable!("uninitialized context"), 377 | err => Err(libusb_to_io(err)), 378 | } 379 | } 380 | 381 | fn flush(&mut self) -> io::Result<()> { 382 | Ok(()) 383 | } 384 | } 385 | 386 | impl Device { 387 | pub fn set_mpsse_clock(&mut self, freq: u32) -> std::result::Result<(), io::Error> { 388 | const MAX: u32 = 30_000_000; 389 | const MIN: u32 = 92; 390 | 391 | assert!( 392 | freq >= MIN, 393 | "frequency of {} exceeds minimum of {}", 394 | freq, 395 | MIN 396 | ); 397 | assert!( 398 | freq <= MAX, 399 | "frequency of {} exceeds maximum of {}", 400 | freq, 401 | MAX 402 | ); 403 | 404 | let (divisor, clkdiv) = if freq <= 6_000_000 { 405 | (6_000_000 / freq - 1, Some(true)) 406 | } else { 407 | (30_000_000 / freq - 1, Some(false)) 408 | }; 409 | 410 | self.write_all(MpsseCmdBuilder::new().set_clock(divisor, clkdiv).as_slice())?; 411 | 412 | Ok(()) 413 | } 414 | } 415 | 416 | impl MpsseCmdExecutor for Device { 417 | type Error = io::Error; 418 | 419 | /// Initialize the MPSSE controller. 420 | /// 421 | /// According to AN135 [FTDI MPSSE basics], this method does the following: 422 | /// 1. Optionally resets the peripheral side of FTDI port. 423 | /// 2. Configures the maximum USB transfer sizes. 424 | /// 3. Disables any event or error special characters. 425 | /// 4. Configures the read and write timeouts (not yet implemented). 426 | /// 5. Configures the latency timer to wait before sending an incomplete USB packet 427 | /// from the peripheral back to the host. 428 | /// 6. Configures for RTS/CTS flow control to ensure that the driver will not issue 429 | /// IN requests if the buffer is unable to accept data. 430 | /// 7. Resets and then enables the MPSSE controller 431 | /// 8. Optionally configures the MPSSE clock frequency. 432 | /// 433 | /// [FTDI MPSSE Basics]: https://www.ftdichip.com/Support/Documents/AppNotes/AN_135_MPSSE_Basics.pdf 434 | fn init(&mut self, settings: &MpsseSettings) -> std::result::Result<(), io::Error> { 435 | let millis = u8::try_from(settings.latency_timer.as_millis()) 436 | .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; 437 | 438 | if settings.reset { 439 | self.usb_reset().map_err(libftdi_to_io)?; 440 | } 441 | 442 | self.usb_purge_buffers().map_err(libftdi_to_io)?; 443 | self.set_write_chunksize(settings.in_transfer_size); 444 | self.set_read_chunksize(settings.in_transfer_size); 445 | self.set_latency_timer(millis).map_err(libftdi_to_io)?; 446 | self.usb_set_event_char(None).map_err(libftdi_to_io)?; 447 | self.usb_set_error_char(None).map_err(libftdi_to_io)?; 448 | self.set_flow_control(FlowControl::RtsCts) 449 | .map_err(libftdi_to_io)?; 450 | self.set_bitmode(0, BitMode::Reset).map_err(libftdi_to_io)?; 451 | self.set_bitmode(settings.mask, BitMode::Mpsse) 452 | .map_err(libftdi_to_io)?; 453 | 454 | if let Some(frequency) = settings.clock_frequency { 455 | self.set_mpsse_clock(frequency)?; 456 | } 457 | 458 | Ok(()) 459 | } 460 | 461 | /// Write the MPSSE command to the device. 462 | /// 463 | /// Workaround: perform Tx flush before sending data. 464 | /// Otherwise several first readings from FTDI chip in the exchange sequence may be broken. 465 | /// E.g. reading during the first exchange returns nothing, but reading during the second 466 | /// exchange returns both the first and second replies. So far it is not clear how to get 467 | /// rid of this workaround. Note that such a workaround is not needed when FTDI proprietary 468 | /// libftd2xx library is used. 469 | fn send(&mut self, data: &[u8]) -> std::result::Result<(), io::Error> { 470 | self.usb_purge_tx_buffer() 471 | .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; 472 | self.write_all(data) 473 | } 474 | 475 | /// Read the MPSSE response from the device. 476 | fn recv(&mut self, data: &mut [u8]) -> std::result::Result<(), io::Error> { 477 | self.read_exact(data) 478 | } 479 | } 480 | 481 | pub struct AsyncWrite { 482 | data: Vec, 483 | control: *mut ffi::ftdi_transfer_control, 484 | } 485 | 486 | impl AsyncWrite { 487 | pub fn is_cancelled(&self) -> bool { 488 | let status = self.completed(); 489 | status != 0 && status != 1 490 | } 491 | 492 | pub fn is_in_progress(&self) -> bool { 493 | self.completed() == 0 494 | } 495 | 496 | fn completed(&self) -> i32 { 497 | unsafe { (*self.control).completed } 498 | } 499 | 500 | pub fn wait(mut self) -> io::Result { 501 | let result = unsafe { ffi::ftdi_transfer_data_done(self.control) }; 502 | self.data = vec![]; 503 | std::mem::forget(self); 504 | match result { 505 | count if count >= 0 => Ok(count as usize), 506 | err => Err(error::libusb_to_io(err)), 507 | } 508 | } 509 | } 510 | 511 | impl Drop for AsyncWrite { 512 | fn drop(&mut self) { 513 | unsafe { 514 | ffi::ftdi_transfer_data_cancel(self.control, std::ptr::null_mut()); 515 | } 516 | self.data.clear(); 517 | } 518 | } 519 | 520 | impl Device { 521 | pub fn write_async(&mut self, buf: &[u8]) -> io::Result { 522 | let mut data = buf.to_owned(); 523 | let len = data.len().try_into().unwrap_or(std::i32::MAX); 524 | let control = unsafe { ffi::ftdi_write_data_submit(self.context, data.as_mut_ptr(), len) }; 525 | if control.is_null() { 526 | Err(io::Error::new( 527 | io::ErrorKind::Other, 528 | "unknown libftdi or libusb error", 529 | )) 530 | } else { 531 | Ok(AsyncWrite { control, data }) 532 | } 533 | } 534 | } 535 | -------------------------------------------------------------------------------- /src/opener.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CString; 2 | 3 | use super::{ffi, Device, Error, Interface, Result}; 4 | 5 | pub trait Target { 6 | fn open_in_context(self, context: *mut ffi::ftdi_context) -> Result<()>; 7 | } 8 | 9 | pub struct BusAddress { 10 | bus: u8, 11 | address: u8, 12 | } 13 | 14 | impl Target for BusAddress { 15 | fn open_in_context(self, context: *mut ffi::ftdi_context) -> Result<()> { 16 | let result = unsafe { ffi::ftdi_usb_open_bus_addr(context, self.bus, self.address) }; 17 | match result { 18 | 0 => Ok(()), 19 | -1 => Err(Error::EnumerationFailed), // usb_find_busses() failed 20 | -2 => Err(Error::EnumerationFailed), // usb_find_devices() failed 21 | -3 => Err(Error::DeviceNotFound), // usb device not found 22 | -4 => Err(Error::AccessFailed), // unable to open device 23 | -5 => Err(Error::ClaimFailed), // unable to claim device 24 | -6 => Err(Error::RequestFailed), // reset failed 25 | -7 => Err(Error::RequestFailed), // set baudrate failed 26 | -8 => Err(Error::EnumerationFailed), // get product description failed 27 | -9 => Err(Error::EnumerationFailed), // get serial number failed 28 | -10 => Err(Error::unknown(context)), // unable to close device 29 | -11 => unreachable!("uninitialized context"), // ftdi context invalid 30 | -12 => Err(Error::EnumerationFailed), // libusb_get_device_list() failed 31 | _ => Err(Error::unknown(context)), 32 | } 33 | } 34 | } 35 | 36 | pub struct UsbProperties { 37 | vid: u16, 38 | pid: u16, 39 | description: Option, 40 | serial: Option, 41 | index: Option, 42 | } 43 | 44 | impl Target for UsbProperties { 45 | fn open_in_context(self, context: *mut ffi::ftdi_context) -> Result<()> { 46 | let description = self 47 | .description 48 | .map(|s| s.as_ptr()) 49 | .unwrap_or(std::ptr::null()); 50 | let serial = self.serial.map(|s| s.as_ptr()).unwrap_or(std::ptr::null()); 51 | let index = self.index.unwrap_or(0).into(); 52 | let result = unsafe { 53 | ffi::ftdi_usb_open_desc_index( 54 | context, 55 | self.vid.into(), 56 | self.pid.into(), 57 | description, 58 | serial, 59 | index, 60 | ) 61 | }; 62 | match result { 63 | 0 => Ok(()), 64 | -1 => Err(Error::EnumerationFailed), // usb_find_busses() failed 65 | -2 => Err(Error::EnumerationFailed), // usb_find_devices() failed 66 | -3 => Err(Error::DeviceNotFound), // usb device not found 67 | -4 => Err(Error::AccessFailed), // unable to open device 68 | -5 => Err(Error::ClaimFailed), // unable to claim device 69 | -6 => Err(Error::RequestFailed), // reset failed 70 | -7 => Err(Error::RequestFailed), // set baudrate failed 71 | -8 => Err(Error::EnumerationFailed), // get product description failed 72 | -9 => Err(Error::EnumerationFailed), // get serial number failed 73 | -10 => Err(Error::unknown(context)), // unable to close device 74 | -11 => unreachable!("uninitialized context"), // ftdi context invalid 75 | -12 => Err(Error::EnumerationFailed), // libusb_get_device_list() failed 76 | _ => Err(Error::unknown(context)), 77 | } 78 | } 79 | } 80 | 81 | pub struct Opener { 82 | target: T, 83 | interface: Option, 84 | } 85 | 86 | impl Opener { 87 | fn new(target: T) -> Self { 88 | Self { 89 | target, 90 | interface: None, 91 | } 92 | } 93 | 94 | pub fn interface(mut self, interface: Interface) -> Self { 95 | assert!(self.interface.is_none(), "interface already set"); 96 | self.interface = Some(interface); 97 | self 98 | } 99 | 100 | pub fn open(self) -> Result { 101 | let context = unsafe { ffi::ftdi_new() }; 102 | 103 | if context.is_null() { 104 | return Err(Error::AllocationFailed); 105 | } 106 | 107 | if let Some(interface) = self.interface { 108 | let result = unsafe { ffi::ftdi_set_interface(context, interface.into()) }; 109 | match result { 110 | 0 => Ok(()), 111 | -1 => unreachable!("unknown interface from ftdi.h"), 112 | -2 => unreachable!("missing context"), 113 | -3 => unreachable!("device already opened in Builder"), 114 | _ => Err(Error::unknown(context)), 115 | }?; 116 | } 117 | 118 | self.target.open_in_context(context)?; 119 | 120 | Ok(Device { context }) 121 | } 122 | } 123 | 124 | impl Opener { 125 | pub fn description(mut self, description: &str) -> Self { 126 | assert!(self.target.description.is_none(), "description already set"); 127 | self.target.description = 128 | Some(CString::new(description).expect("serial should not contain NUL")); 129 | self 130 | } 131 | 132 | pub fn serial(mut self, serial: &str) -> Self { 133 | assert!(self.target.serial.is_none(), "serial already set"); 134 | self.target.serial = Some(CString::new(serial).expect("serial should not contain NUL")); 135 | self 136 | } 137 | 138 | pub fn nth(mut self, index: u32) -> Self { 139 | assert!(self.target.index.is_none(), "index already set"); 140 | self.target.index = Some(index); 141 | self 142 | } 143 | } 144 | 145 | pub fn find_by_vid_pid(vid: u16, pid: u16) -> Opener { 146 | Opener::new(UsbProperties { 147 | vid, 148 | pid, 149 | description: None, 150 | serial: None, 151 | index: None, 152 | }) 153 | } 154 | 155 | pub fn find_by_bus_address(bus: u8, address: u8) -> Opener { 156 | Opener::new(BusAddress { bus, address }) 157 | } 158 | 159 | #[cfg(feature = "libusb1-sys")] 160 | use ffi::libusb1_sys::libusb_device; 161 | 162 | #[cfg(feature = "libusb1-sys")] 163 | pub struct LibusbDevice { 164 | device: *mut libusb_device, 165 | } 166 | 167 | #[cfg(feature = "libusb1-sys")] 168 | impl Target for LibusbDevice { 169 | fn open_in_context(self, context: *mut ffi::ftdi_context) -> Result<()> { 170 | let result = unsafe { ffi::ftdi_usb_open_dev(context, self.device) }; 171 | match result { 172 | 0 => Ok(()), 173 | -3 => Err(Error::AccessFailed), // unable to config device 174 | -4 => Err(Error::AccessFailed), // unable to open device 175 | -5 => Err(Error::ClaimFailed), // unable to claim device 176 | -6 => Err(Error::RequestFailed), // reset failed 177 | -7 => Err(Error::RequestFailed), // set baudrate failed 178 | -8 => unreachable!("uninitialized context"), // ftdi context invalid 179 | -9 => Err(Error::AccessFailed), // libusb_get_device_descriptor() failed 180 | -10 => Err(Error::AccessFailed), // libusb_get_config_descriptor() failed 181 | -11 => Err(Error::AccessFailed), // libusb_detach_kernel_driver() failed 182 | -12 => Err(Error::AccessFailed), // libusb_get_configuration() failed 183 | _ => Err(Error::unknown(context)), 184 | } 185 | } 186 | } 187 | 188 | #[cfg(feature = "libusb1-sys")] 189 | pub unsafe fn find_by_raw_libusb_device(device: *mut libusb_device) -> Opener { 190 | Opener::new(LibusbDevice { device }) 191 | } 192 | --------------------------------------------------------------------------------