├── .gitignore ├── Cargo.toml ├── .github └── workflows │ └── rust.yml ├── examples ├── config.rs ├── datastreamer.rs ├── perf_debug.rs └── stream_checker.rs ├── src ├── lib.rs ├── notes.org ├── ringbuf.rs ├── ft60x.rs └── ft60x_config.rs ├── README.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | flamegraph.svg 3 | perf.data 4 | perf.data.old 5 | .idea/ 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ft60x" 3 | version = "0.1.1" 4 | authors = ["Robin Ole Heinemann "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | rusb = { version = "0.4.0", git = "https://github.com/apertus-open-source-cinema/rusb", rev="async" } 9 | byteorder = "1.3.4" 10 | bitflags = "1.2.1" 11 | owning_ref = "0.4.1" 12 | thiserror = "1.0.22" 13 | 14 | [features] 15 | ringbuf = [] -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: cargo check 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: install nightly rust 16 | uses: actions-rs/toolchain@v1 17 | with: 18 | override: true 19 | profile: minimal 20 | toolchain: nightly 21 | - name: check 22 | uses: actions-rs/cargo@v1 23 | with: 24 | command: check 25 | -------------------------------------------------------------------------------- /examples/config.rs: -------------------------------------------------------------------------------- 1 | // configures the ft601 in a way that can be used with this lib. 2 | // run this tool before running any other tools. 3 | 4 | use ft60x::ft60x::{FT60x, DEFAULT_PID, DEFAULT_VID}; 5 | use ft60x::ft60x_config::{FT60xChannelConfig, FT60xFifoClock, FT60xFifoMode}; 6 | 7 | type Result = std::result::Result; 8 | 9 | fn main() -> Result<()> { 10 | let mut ft60x = FT60x::new(DEFAULT_VID, DEFAULT_PID)?; 11 | 12 | let mut config = ft60x.get_config()?; 13 | 14 | config.fifo_clock = FT60xFifoClock::Clock100MHz; 15 | config.fifo_mode = FT60xFifoMode::Mode245; 16 | config.channel_config = FT60xChannelConfig::OneChannelInPipe; 17 | 18 | ft60x.set_config(config)?; 19 | 20 | println!("successfully set config :)"); 21 | 22 | Ok(()) 23 | } 24 | -------------------------------------------------------------------------------- /examples/datastreamer.rs: -------------------------------------------------------------------------------- 1 | // prints the received data from the ft60x to stdout. 2 | // useful for building quick experiments in bash or storing the data. 3 | 4 | use ft60x::ft60x::{FT60x, DEFAULT_PID, DEFAULT_VID}; 5 | use std::io::{self, Write}; 6 | use std::thread; 7 | use std::time::SystemTime; 8 | 9 | type Result = std::result::Result; 10 | 11 | fn main() -> Result<()> { 12 | let ft60x = FT60x::new(DEFAULT_VID, DEFAULT_PID)?; 13 | let (empty_buffer_tx, full_buffer_rx, _) = ft60x.data_stream_mpsc(10); 14 | 15 | thread::spawn(move || loop { 16 | empty_buffer_tx.send(vec![0; 1024 * 1024 * 128]).unwrap(); 17 | }); 18 | 19 | let mut start = SystemTime::now(); 20 | for buf in full_buffer_rx.iter() { 21 | let buffer = buf?; 22 | io::stdout().write_all(&*buffer).unwrap(); 23 | 24 | let bytes = buffer.len() as f64; 25 | let elapsed = start.elapsed().unwrap().as_secs_f64(); 26 | start = SystemTime::now(); 27 | eprintln!( 28 | "elapsed (for {} Mb) {}s = {} MB/s", 29 | bytes / 1024. / 1024., 30 | elapsed, 31 | bytes / 1024. / 1024. / elapsed 32 | ); 33 | } 34 | 35 | Ok(()) 36 | } 37 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(feature = "ringbuf", feature(get_mut_unchecked))] 2 | 3 | use bitflags::_core::str::Utf8Error; 4 | use std::io; 5 | use thiserror::Error; 6 | 7 | #[derive(Error, Debug)] 8 | pub enum Error { 9 | #[error("Error in USB communication")] 10 | RUSBError(#[from] rusb::Error), 11 | #[error("IO Error")] 12 | IOError(#[from] io::Error), 13 | #[error("UTF8 Error")] 14 | Utf8Error(#[from] Utf8Error), 15 | #[error("{0}")] 16 | GeneralError(String), 17 | } 18 | 19 | macro_rules! format_general_err { 20 | ($($arg:tt)*) => { $crate::Error::GeneralError(format!($($arg)*)) } 21 | } 22 | 23 | macro_rules! ensure { 24 | ($cond:expr) => { 25 | if !($cond) { 26 | return Err(format_general_err!("something went wrong")); 27 | } 28 | }; 29 | ($cond:expr, $e:expr) => { 30 | if !($cond) { 31 | return Err(format_general_err!($e)); 32 | } 33 | }; 34 | ($cond:expr, $fmt:expr, $($arg:tt)*) => { 35 | if !($cond) { 36 | return Err(format_general_err!($fmt, $($arg)*)); 37 | } 38 | }; 39 | } 40 | 41 | type Result = std::result::Result; 42 | 43 | pub mod ft60x; 44 | pub mod ft60x_config; 45 | #[cfg(feature = "ringbuf")] 46 | pub mod ringbuf; 47 | -------------------------------------------------------------------------------- /examples/perf_debug.rs: -------------------------------------------------------------------------------- 1 | // prints the received stream as 2 16 bit values. 2 | // useful for debugging performance 3 | 4 | use byteorder::{LittleEndian, ReadBytesExt}; 5 | use ft60x::ft60x::{FT60x, DEFAULT_PID, DEFAULT_VID}; 6 | use std::io::Cursor; 7 | use std::time::SystemTime; 8 | 9 | type Result = std::result::Result; 10 | 11 | fn main() -> Result<()> { 12 | let ft60x = FT60x::new(DEFAULT_VID, DEFAULT_PID)?; 13 | let mut consumer = ft60x.data_stream_ringbuf(1024 * 1024 * 128)?; 14 | 15 | let mut start = SystemTime::now(); 16 | let mut last_i = 0; 17 | while consumer 18 | .with_next_buffer(|buf| { 19 | let mut cursor = Cursor::new(&buf[..]); 20 | while let (Ok(i), Ok(j)) = ( 21 | cursor.read_u16::(), 22 | cursor.read_u16::(), 23 | ) { 24 | if j > 0 { 25 | eprintln!("{}, {}", last_i, j); 26 | } 27 | last_i = i; 28 | } 29 | 30 | let bytes = buf.len() as f64; 31 | let elapsed = start.elapsed().unwrap().as_secs_f64(); 32 | start = SystemTime::now(); 33 | eprintln!( 34 | "elapsed (for {} Mb) {}s = {} MB/s", 35 | bytes / 1024. / 1024., 36 | elapsed, 37 | bytes / 1024. / 1024. / elapsed 38 | ); 39 | }) 40 | .is_ok() 41 | {} 42 | 43 | Ok(()) 44 | } 45 | -------------------------------------------------------------------------------- /examples/stream_checker.rs: -------------------------------------------------------------------------------- 1 | // checks that the received stream forms a consecutive counter when interpreted as 32 bit unsigned values. 2 | // useful for ensuring that no data is lost 3 | 4 | use byteorder::{LittleEndian, ReadBytesExt}; 5 | use ft60x::ft60x::{FT60x, DEFAULT_PID, DEFAULT_VID}; 6 | use std::io::Cursor; 7 | use std::time::SystemTime; 8 | 9 | type Result = std::result::Result; 10 | 11 | fn main() -> Result<()> { 12 | let ft60x = FT60x::new(DEFAULT_VID, DEFAULT_PID)?; 13 | let mut consumer = ft60x.data_stream_ringbuf(1024 * 1024 * 128)?; 14 | 15 | let mut start = SystemTime::now(); 16 | let mut last = 0u32; 17 | while consumer 18 | .with_next_buffer(|buf| { 19 | let mut cursor = Cursor::new(&buf[..]); 20 | while let Ok(i) = cursor.read_u32::() { 21 | if last.overflowing_add(1).0 != i { 22 | eprintln!("miss! last: {}; next: {}", last, i); 23 | } 24 | 25 | last = i; 26 | } 27 | 28 | let bytes = buf.len() as f64; 29 | let elapsed = start.elapsed().unwrap().as_secs_f64(); 30 | start = SystemTime::now(); 31 | eprintln!( 32 | "elapsed (for {} Mb) {}s = {} MB/s", 33 | bytes / 1024. / 1024., 34 | elapsed, 35 | bytes / 1024. / 1024. / elapsed 36 | ); 37 | }) 38 | .is_ok() 39 | {} 40 | 41 | Ok(()) 42 | } 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `ft60x-rs` 2 | A rust interface library for the ftdi FT60x usb3.0 superspeed FIFO bridge chip series. 3 | 4 | ## Current State 5 | `ft60x-rs` can sucessfully stream data from the FT601 to the host in 245 fifo mode. 6 | Streaming in the other direction is not implemented yet. 7 | FT600 should work as well but is untested. 8 | 9 | ## Binaries / Utilities 10 | Shipped with `ft60x-rs` are some examples (found in [`examples/`](examples/)). 11 | 12 | * `datastreamer` streams the data it recieves to stdout while printing performance information to stderr. This can be used to record the datastream to disk or process it further using other tools. 13 | * `stream_checker` checks that the 32bit words recieved from the FT60x form a consecutive counter. If anything is missed, a warning is printed to stderr. This can be used to verify that no data is missed (and therefore to verify gateware). Example gateware that can be used in companion with this tool can be found [in the apertus nmigen-gateware repo](https://github.com/apertus-open-source-cinema/nmigen-gateware/blob/c75fffe/src/experiments/usb3_test.py) 14 | * `config` configures the ft601 to be used as a fifo in 254 mode. 15 | * `perf_debug` can help debugging performance issues. 16 | 17 | 18 | ## Performance 19 | Using the FT601 in 245 FIFO mode, we were able to read ~360Mbyte/s continiously. 20 | This is pretty exactly the same performance as we achieved using the proprietary 21 | D3XX library while this code uses less cpu time. 22 | 23 | Further performance optimization might be possible using the 600 FIFO mode. However 24 | this was not investigated further. 25 | -------------------------------------------------------------------------------- /src/notes.org: -------------------------------------------------------------------------------- 1 | 2 | 3 | // let mut buf = [0u8; 4]; 4 | // device.read_control(request_type(Direction::In, RequestType::Vendor, Recipient::Device), 5 | // 0xf1, 0, 0, &mut buf, Duration::new(60, 0))?; 6 | // println!("{:#x?}", buf.to_vec()); 7 | 8 | // device.read_control(request_type(Direction::In, RequestType::Vendor, Recipient::Device), 9 | // 0x03, 1, 0x8400, &mut buf, Duration::new(60, 0))?; 10 | // println!("{:#x?}", buf.to_vec()); 11 | 12 | // device.read_control(request_type(Direction::In, RequestType::Vendor, Recipient::Device), 13 | // 0x03, 1, 0x8000, &mut buf, Duration::new(60, 0))?; 14 | // println!("{:#x?}", buf.to_vec()); 15 | 16 | // let ctrlreq = [0x00, 0x00, 0x00, 0x00, 0x82, 0x03, 0x00, 0x00, 0x00, 0xce, 0x7b, 0x76, 0xd5, 0x17, 0x52, 0xfc, 0x00, 0x00, 0x00, 0x00]; 17 | // device.write_bulk(0x01, &ctrlreq, Duration::new(1, 0))?; 18 | // device.read_interrupt(0x81, &mut buf, Duration::new(1, 0))?; 19 | 20 | 21 | // This one is working for starting transfers :) 22 | // let ctrlreq = [0x00, 0x00, 0x00, 0x00, 0x82, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; 23 | // This one is not working for shutdown :) 24 | // let ctrlreq = [0x00, 0x00, 0x00, 0x00, 0x82, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; 25 | // device.write_bulk(0x01, &ctrlreq, Duration::new(1, 0))?; 26 | -------------------------------------------------------------------------------- /src/ringbuf.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicBool, Ordering}; 2 | use std::sync::mpsc::{Receiver, Sender}; 3 | use std::sync::Arc; 4 | 5 | pub struct RingBuf { 6 | buffer: Vec, 7 | capacity: usize, 8 | one_was_dropped: AtomicBool, 9 | } 10 | 11 | impl RingBuf { 12 | pub fn new(capacity: usize, default: T) -> Self { 13 | assert!(capacity != 1, "Use a RwLock for capacity 1"); 14 | 15 | RingBuf { 16 | buffer: vec![default; capacity], 17 | capacity, 18 | one_was_dropped: AtomicBool::new(false), 19 | } 20 | } 21 | 22 | pub fn create_channel_with_default_value( 23 | capacity: usize, 24 | default: T, 25 | ) -> (RingBufProducer, RingBufConsumer) { 26 | let ringbuf = Arc::new(RingBuf::new(capacity, default)); 27 | 28 | let (next_write_pos_sink, next_write_pos_receiver) = std::sync::mpsc::channel(); 29 | let (last_read_pos_sender, last_read_pos_receiver) = std::sync::mpsc::channel(); 30 | 31 | let producer = 32 | RingBufProducer::new(ringbuf.clone(), next_write_pos_sink, last_read_pos_receiver); 33 | let consumer = RingBufConsumer::new(ringbuf, next_write_pos_receiver, last_read_pos_sender); 34 | 35 | (producer, consumer) 36 | } 37 | 38 | pub fn create_channel(capacity: usize) -> (RingBufProducer, RingBufConsumer) { 39 | Self::create_channel_with_default_value(capacity, Default::default()) 40 | } 41 | } 42 | 43 | pub struct RingBufProducer { 44 | ringbuf: Arc>, 45 | next_write_pos_sink: Sender, 46 | last_read_pos: Receiver, 47 | next_write_pos: usize, 48 | lastknown_last_read_pos: usize, 49 | } 50 | 51 | impl RingBufProducer { 52 | fn new( 53 | ringbuf: Arc>, 54 | next_write_pos_sink: Sender, 55 | last_read_pos: Receiver, 56 | ) -> Self { 57 | Self { 58 | ringbuf, 59 | next_write_pos_sink, 60 | last_read_pos, 61 | next_write_pos: 0, 62 | lastknown_last_read_pos: 0, 63 | } 64 | } 65 | 66 | pub fn cancel(&mut self) { 67 | self.ringbuf.one_was_dropped.store(true, Ordering::Relaxed); 68 | self.next_write_pos_sink.send(self.next_write_pos).unwrap(); 69 | } 70 | 71 | pub fn with_next_buffer R, R>( 72 | &mut self, 73 | mut func: F, 74 | ) -> std::result::Result { 75 | for last_read_pos in 76 | std::iter::once(self.lastknown_last_read_pos).chain(self.last_read_pos.iter()) 77 | { 78 | if self.ringbuf.one_was_dropped.load(Ordering::Relaxed) { 79 | return Err(()); 80 | } 81 | 82 | if (self.next_write_pos - last_read_pos) < self.ringbuf.capacity { 83 | self.lastknown_last_read_pos = last_read_pos; 84 | break; 85 | } 86 | } 87 | 88 | let pos = self.next_write_pos % self.ringbuf.capacity; 89 | 90 | let ret = unsafe { func(&mut Arc::get_mut_unchecked(&mut self.ringbuf).buffer[pos]) }; 91 | 92 | self.next_write_pos += 1; 93 | self.next_write_pos_sink.send(self.next_write_pos).unwrap(); 94 | 95 | Ok(ret) 96 | } 97 | } 98 | 99 | impl Drop for RingBufProducer { 100 | fn drop(&mut self) { 101 | self.cancel() 102 | } 103 | } 104 | 105 | pub struct RingBufConsumer { 106 | ringbuf: Arc>, 107 | next_write_pos: Receiver, 108 | last_read_pos: Sender, 109 | next_read_pos: usize, 110 | lastknown_next_write_pos: usize, 111 | } 112 | 113 | impl RingBufConsumer { 114 | fn new( 115 | ringbuf: Arc>, 116 | next_write_pos: Receiver, 117 | last_read_pos: Sender, 118 | ) -> Self { 119 | Self { 120 | ringbuf, 121 | next_write_pos, 122 | last_read_pos, 123 | next_read_pos: 0, 124 | lastknown_next_write_pos: 0, 125 | } 126 | } 127 | 128 | pub fn cancel(&mut self) { 129 | self.ringbuf.one_was_dropped.store(true, Ordering::Relaxed); 130 | self.last_read_pos.send(self.next_read_pos - 1).unwrap(); 131 | } 132 | 133 | pub fn with_next_buffer R, R>( 134 | &mut self, 135 | mut func: F, 136 | ) -> std::result::Result { 137 | for next_write_pos in 138 | std::iter::once(self.lastknown_next_write_pos).chain(self.next_write_pos.iter()) 139 | { 140 | if self.ringbuf.one_was_dropped.load(Ordering::Relaxed) { 141 | return Err(()); 142 | } 143 | 144 | if next_write_pos > self.next_read_pos { 145 | self.lastknown_next_write_pos = next_write_pos; 146 | break; 147 | } 148 | } 149 | 150 | let pos = self.next_read_pos % self.ringbuf.capacity; 151 | let ret = func(&self.ringbuf.buffer[pos]); 152 | 153 | self.last_read_pos.send(self.next_read_pos).unwrap(); 154 | 155 | self.next_read_pos += 1; 156 | 157 | Ok(ret) 158 | } 159 | } 160 | 161 | impl Drop for RingBufConsumer { 162 | fn drop(&mut self) { 163 | self.cancel() 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "adler32" 5 | version = "1.2.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" 8 | 9 | [[package]] 10 | name = "bit-set" 11 | version = "0.5.2" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" 14 | dependencies = [ 15 | "bit-vec", 16 | ] 17 | 18 | [[package]] 19 | name = "bit-vec" 20 | version = "0.6.3" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" 23 | 24 | [[package]] 25 | name = "bitflags" 26 | version = "1.2.1" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 29 | 30 | [[package]] 31 | name = "byteorder" 32 | version = "1.3.4" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" 35 | 36 | [[package]] 37 | name = "cc" 38 | version = "1.0.66" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48" 41 | 42 | [[package]] 43 | name = "cfg-if" 44 | version = "1.0.0" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 47 | 48 | [[package]] 49 | name = "crc32fast" 50 | version = "1.2.1" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" 53 | dependencies = [ 54 | "cfg-if", 55 | ] 56 | 57 | [[package]] 58 | name = "filetime" 59 | version = "0.2.13" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "0c122a393ea57648015bf06fbd3d372378992e86b9ff5a7a497b076a28c79efe" 62 | dependencies = [ 63 | "cfg-if", 64 | "libc", 65 | "redox_syscall", 66 | "winapi", 67 | ] 68 | 69 | [[package]] 70 | name = "ft60x" 71 | version = "0.1.1" 72 | dependencies = [ 73 | "bitflags", 74 | "byteorder", 75 | "owning_ref", 76 | "rusb", 77 | "thiserror", 78 | ] 79 | 80 | [[package]] 81 | name = "libc" 82 | version = "0.2.81" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb" 85 | 86 | [[package]] 87 | name = "libflate" 88 | version = "0.1.27" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "d9135df43b1f5d0e333385cb6e7897ecd1a43d7d11b91ac003f4d2c2d2401fdd" 91 | dependencies = [ 92 | "adler32", 93 | "crc32fast", 94 | "rle-decode-fast", 95 | "take_mut", 96 | ] 97 | 98 | [[package]] 99 | name = "libusb1-sys" 100 | version = "0.3.7" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "71d9ddd446b6f233a79ef7e6f73de63a58f3a9047d60c46f15cda31452a8f86e" 103 | dependencies = [ 104 | "cc", 105 | "libc", 106 | "libflate", 107 | "pkg-config", 108 | "tar", 109 | "vcpkg", 110 | ] 111 | 112 | [[package]] 113 | name = "owning_ref" 114 | version = "0.4.1" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "6ff55baddef9e4ad00f88b6c743a2a8062d4c6ade126c2a528644b8e444d52ce" 117 | dependencies = [ 118 | "stable_deref_trait", 119 | ] 120 | 121 | [[package]] 122 | name = "pkg-config" 123 | version = "0.3.19" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" 126 | 127 | [[package]] 128 | name = "proc-macro2" 129 | version = "1.0.24" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" 132 | dependencies = [ 133 | "unicode-xid", 134 | ] 135 | 136 | [[package]] 137 | name = "quote" 138 | version = "1.0.7" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" 141 | dependencies = [ 142 | "proc-macro2", 143 | ] 144 | 145 | [[package]] 146 | name = "redox_syscall" 147 | version = "0.1.57" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" 150 | 151 | [[package]] 152 | name = "rle-decode-fast" 153 | version = "1.0.1" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "cabe4fa914dec5870285fa7f71f602645da47c486e68486d2b4ceb4a343e90ac" 156 | 157 | [[package]] 158 | name = "rusb" 159 | version = "0.4.0" 160 | source = "git+https://github.com/apertus-open-source-cinema/rusb?rev=async#874262677bd3b3cc9829ec1f03b60469dc7a65f7" 161 | dependencies = [ 162 | "bit-set", 163 | "libc", 164 | "libusb1-sys", 165 | ] 166 | 167 | [[package]] 168 | name = "stable_deref_trait" 169 | version = "1.2.0" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 172 | 173 | [[package]] 174 | name = "syn" 175 | version = "1.0.54" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "9a2af957a63d6bd42255c359c93d9bfdb97076bd3b820897ce55ffbfbf107f44" 178 | dependencies = [ 179 | "proc-macro2", 180 | "quote", 181 | "unicode-xid", 182 | ] 183 | 184 | [[package]] 185 | name = "take_mut" 186 | version = "0.2.2" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" 189 | 190 | [[package]] 191 | name = "tar" 192 | version = "0.4.30" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "489997b7557e9a43e192c527face4feacc78bfbe6eed67fd55c4c9e381cba290" 195 | dependencies = [ 196 | "filetime", 197 | "libc", 198 | "redox_syscall", 199 | "xattr", 200 | ] 201 | 202 | [[package]] 203 | name = "thiserror" 204 | version = "1.0.22" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "0e9ae34b84616eedaaf1e9dd6026dbe00dcafa92aa0c8077cb69df1fcfe5e53e" 207 | dependencies = [ 208 | "thiserror-impl", 209 | ] 210 | 211 | [[package]] 212 | name = "thiserror-impl" 213 | version = "1.0.22" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "9ba20f23e85b10754cd195504aebf6a27e2e6cbe28c17778a0c930724628dd56" 216 | dependencies = [ 217 | "proc-macro2", 218 | "quote", 219 | "syn", 220 | ] 221 | 222 | [[package]] 223 | name = "unicode-xid" 224 | version = "0.2.1" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 227 | 228 | [[package]] 229 | name = "vcpkg" 230 | version = "0.2.11" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb" 233 | 234 | [[package]] 235 | name = "winapi" 236 | version = "0.3.9" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 239 | dependencies = [ 240 | "winapi-i686-pc-windows-gnu", 241 | "winapi-x86_64-pc-windows-gnu", 242 | ] 243 | 244 | [[package]] 245 | name = "winapi-i686-pc-windows-gnu" 246 | version = "0.4.0" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 249 | 250 | [[package]] 251 | name = "winapi-x86_64-pc-windows-gnu" 252 | version = "0.4.0" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 255 | 256 | [[package]] 257 | name = "xattr" 258 | version = "0.2.2" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c" 261 | dependencies = [ 262 | "libc", 263 | ] 264 | -------------------------------------------------------------------------------- /src/ft60x.rs: -------------------------------------------------------------------------------- 1 | use rusb::{ 2 | request_type, AsyncGroup, Context, DeviceHandle, Direction, Recipient, RequestType, Transfer, 3 | }; 4 | use std::time::Duration; 5 | 6 | use crate::ft60x_config::FT60xConfig; 7 | #[cfg(feature = "ringbuf")] 8 | use crate::ringbuf::{RingBuf, RingBufConsumer}; 9 | use crate::Result; 10 | use bitflags::_core::ops::DerefMut; 11 | use owning_ref::OwningHandle; 12 | use std::iter::once; 13 | use std::sync::mpsc::{sync_channel, Receiver, SyncSender}; 14 | use std::sync::Arc; 15 | use std::thread; 16 | use std::thread::JoinHandle; 17 | 18 | pub const DEFAULT_PID: u16 = 0x601f; 19 | pub const DEFAULT_VID: u16 = 0x0403; 20 | 21 | pub struct FT60x { 22 | context: Arc, 23 | device: OwningHandle, Box>>, 24 | streaming_mode: bool, 25 | } 26 | 27 | impl FT60x { 28 | pub fn new(vid: u16, pid: u16) -> Result { 29 | let context = Arc::new(Context::new()?); 30 | let device: Result<_> = OwningHandle::try_new(context.clone(), |context| unsafe { 31 | Ok(Box::new( 32 | context 33 | .as_ref() 34 | .ok_or_else(|| format_general_err!("null pointer for context received"))? 35 | .open_device_with_vid_pid(vid, pid) 36 | .ok_or_else(|| { 37 | format_general_err!( 38 | "No device with VID {:#x} and PID {:#x} was found", 39 | vid, 40 | pid 41 | ) 42 | })?, 43 | )) 44 | }); 45 | Ok(FT60x { 46 | context, 47 | device: device?, 48 | streaming_mode: false, 49 | }) 50 | } 51 | 52 | pub fn get_config(&self) -> Result { 53 | let mut buf = [0; 152]; 54 | let read = self.device.read_control( 55 | request_type(Direction::In, RequestType::Vendor, Recipient::Device), 56 | 0xcf, 57 | 1, 58 | 0, 59 | &mut buf, 60 | Duration::new(1, 0), 61 | )?; 62 | 63 | ensure!(read == 152, "got wrong number of config bytes"); 64 | FT60xConfig::parse(buf) 65 | } 66 | 67 | pub fn set_config(&mut self, config: FT60xConfig) -> Result<()> { 68 | let buf = config.encode()?; 69 | let written = self.device.write_control( 70 | request_type(Direction::Out, RequestType::Vendor, Recipient::Device), 71 | 0xcf, 72 | 0, 73 | 0, 74 | &buf, 75 | Duration::new(1, 0), 76 | )?; 77 | 78 | ensure!(written == 152, "wrote wrong number of config bytes"); 79 | Ok(()) 80 | } 81 | 82 | fn set_streaming_mode(&mut self) -> Result<()> { 83 | if !self.streaming_mode { 84 | self.device.claim_interface(0)?; 85 | self.device.claim_interface(1)?; 86 | 87 | let ctrlreq = [ 88 | 0x00, 0x00, 0x00, 0x00, 0x82, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 89 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 90 | ]; 91 | 92 | self.device 93 | .write_bulk(0x01, &ctrlreq, Duration::new(1, 0))?; 94 | self.streaming_mode = true; 95 | } 96 | Ok(()) 97 | } 98 | 99 | /// it is recommended to read multiples of 32Kb 100 | pub fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> { 101 | self.set_streaming_mode()?; 102 | 103 | let blocksize: usize = 32 * 1024; // 32 Kb seems to be the sweet spot for the ft601 104 | let mut_chunks = buf.chunks_mut(blocksize); 105 | let mut_chunks_len = mut_chunks.len(); 106 | let mut collected = 0; 107 | 108 | let mut async_group = AsyncGroup::new(&self.context); 109 | for (i, chunk) in mut_chunks.enumerate() { 110 | // The FT60x doesn't seem to like too many outstanding requests 111 | if i > 500 { 112 | let mut transfer = async_group.wait_any()?; 113 | ensure!( 114 | transfer.buffer().len() == transfer.actual().len(), 115 | "FT60x did not return enough data. requested {} got {}", 116 | transfer.buffer().len(), 117 | transfer.actual().len() 118 | ); 119 | collected += 1; 120 | } 121 | 122 | async_group.submit(Transfer::bulk( 123 | &self.device, 124 | 0x82, 125 | chunk, 126 | Duration::new(1, 0), 127 | ))?; 128 | } 129 | while let Ok(mut transfer) = async_group.wait_any() { 130 | ensure!( 131 | transfer.buffer().len() == transfer.actual().len(), 132 | "FT60x did not return enough data. requested {} got {}", 133 | transfer.buffer().len(), 134 | transfer.actual().len() 135 | ); 136 | collected += 1; 137 | } 138 | ensure!( 139 | collected == mut_chunks_len, 140 | "FT60x did not answer all chunks within timeout. Requested {} got an answer for {}", 141 | mut_chunks_len, 142 | collected 143 | ); 144 | Ok(()) 145 | } 146 | 147 | // starts a thread with which you can send empty buffers and receive full buffers from 148 | // allows for interleaved data transfers (without loosing data) 149 | pub fn data_stream_mpsc( 150 | mut self, 151 | in_flight_buffers: usize, 152 | ) -> (SyncSender, Receiver>, JoinHandle<()>) 153 | where 154 | T: DerefMut + Send + Sync + 'static, 155 | { 156 | let (empty_buffer_tx, empty_buffer_rx) = sync_channel::(in_flight_buffers); 157 | let (full_buffer_tx, full_buffer_rx) = sync_channel::>(in_flight_buffers); 158 | let full_buffer_tx2 = full_buffer_tx.clone(); 159 | 160 | let mut thread_fn = move || { 161 | self.set_streaming_mode()?; 162 | 163 | let blocksize: usize = 32 * 1024; // 32 Kb seems to be the sweet spot for the ft601 164 | 165 | let mut async_group_buffer: Vec<(T, AsyncGroup)> = Vec::new(); 166 | 167 | let mut outstanding = 0; 168 | for mut current_buffer in empty_buffer_rx.iter() { 169 | let mut current_async_group = AsyncGroup::new(&self.context); 170 | for chunk in unsafe { 171 | // the rust compiler cant prove the lifetime here. 172 | // we are dropping the async group together with ending to write to the buffer 173 | // so for the relevant timeframe, the pointers to the chunks of that buffer are valid. 174 | std::mem::transmute::<&mut T, &'static mut T>(&mut current_buffer) 175 | } 176 | .chunks_mut(blocksize) 177 | { 178 | // The FT60x doesn't seem to like too many outstanding requests 179 | if outstanding > 500 { 180 | let mut to_ship = None; 181 | for (i, async_group) in async_group_buffer 182 | .iter_mut() 183 | .map(|v| &mut v.1) 184 | .chain(once(&mut current_async_group)) 185 | .enumerate() 186 | { 187 | match async_group.wait_any() { 188 | Ok(mut transfer) => { 189 | ensure!( 190 | transfer.buffer().len() == transfer.actual().len(), 191 | "FT60x did not return enough data. requested {} got {}", 192 | transfer.buffer().len(), 193 | transfer.actual().len() 194 | ); 195 | outstanding -= 1; 196 | break; 197 | } 198 | Err(rusb::Error::NotFound) => { 199 | assert_eq!(to_ship, None); 200 | assert_eq!(i, 0); 201 | to_ship = Some(i); 202 | } 203 | Err(e) => { 204 | return Err(format_general_err!( 205 | "error while waiting for a transfer to complete: {:?}", 206 | e 207 | )) 208 | } 209 | } 210 | } 211 | 212 | if let Some(i) = to_ship { 213 | if i < async_group_buffer.len() { 214 | full_buffer_tx 215 | .send(Ok(async_group_buffer.remove(i).0)) 216 | .map_err(|_| format_general_err!("mpsc send error"))?; 217 | } 218 | } 219 | } 220 | 221 | current_async_group.submit(Transfer::bulk( 222 | &self.device, 223 | 0x82, 224 | chunk, 225 | Duration::new(1, 0), 226 | ))?; 227 | outstanding += 1; 228 | } 229 | async_group_buffer.push((current_buffer, current_async_group)); 230 | } 231 | 232 | let mut to_ship = None; 233 | for (i, async_group) in async_group_buffer.iter_mut().map(|v| &mut v.1).enumerate() { 234 | match async_group.wait_any() { 235 | Ok(mut transfer) => { 236 | ensure!( 237 | transfer.buffer().len() == transfer.actual().len(), 238 | "FT60x did not return enough data. requested {} got {}", 239 | transfer.buffer().len(), 240 | transfer.actual().len() 241 | ); 242 | break; 243 | } 244 | Err(rusb::Error::NotFound) => { 245 | assert_eq!(to_ship, None); 246 | assert_eq!(i, 0); 247 | to_ship = Some(i); 248 | } 249 | Err(_) => panic!(), 250 | } 251 | } 252 | 253 | if let Some(i) = to_ship { 254 | if i < async_group_buffer.len() { 255 | full_buffer_tx 256 | .send(Ok(async_group_buffer.remove(i).0)) 257 | .map_err(|_| format_general_err!("mpsc send error"))?; 258 | } 259 | } 260 | 261 | Ok(()) 262 | }; 263 | 264 | let join_handle = thread::Builder::new() 265 | .name("ft60x-rx".to_string()) 266 | .spawn(move || { 267 | let result = thread_fn(); 268 | if let Err(e) = result { 269 | full_buffer_tx2.send(Err(e)).unwrap(); 270 | } 271 | }) 272 | .unwrap(); 273 | 274 | (empty_buffer_tx, full_buffer_rx, join_handle) 275 | } 276 | 277 | /// it is recommended to request multiples of 32Kb 278 | #[cfg(feature = "ringbuf")] 279 | pub fn data_stream_ringbuf(mut self, bufsize: usize) -> Result>> { 280 | let (mut producer, consumer) = 281 | RingBuf::>::create_channel_with_default_value(4, vec![0u8; bufsize]); 282 | 283 | std::thread::spawn(move || { 284 | while producer 285 | .with_next_buffer(|buf| self.read_exact(buf).unwrap()) 286 | .is_ok() 287 | {} 288 | }); 289 | 290 | Ok(consumer) 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /src/ft60x_config.rs: -------------------------------------------------------------------------------- 1 | use crate::Result; 2 | use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; 3 | use std::io::{Cursor, Read, Write}; 4 | 5 | #[derive(Debug)] 6 | pub struct FT60xConfig { 7 | vid: u16, 8 | pid: u16, 9 | manufacturer: String, 10 | product_description: String, 11 | serial_number: String, 12 | power_attributes: u8, 13 | power_consumption: u16, 14 | pub fifo_clock: FT60xFifoClock, 15 | pub fifo_mode: FT60xFifoMode, 16 | pub channel_config: FT60xChannelConfig, 17 | optional_features_support: u16, 18 | battery_charging_gpio_config: u8, 19 | flash_eeprom_detection: ft60x_flash_rom_detection::FT60xFlashRomDetection, 20 | msio_config: u32, 21 | gpio_config: u32, 22 | reserved1: u8, 23 | reserved2: u8, 24 | } 25 | 26 | impl FT60xConfig { 27 | pub fn parse(bytes: [u8; 152]) -> Result { 28 | let mut data = Cursor::new(&bytes[..]); 29 | 30 | let vid = data.read_u16::()?; 31 | let pid = data.read_u16::()?; 32 | 33 | let mut strings_buf = [0u8; 128]; 34 | ensure!(data.read(&mut strings_buf)? == 128); 35 | 36 | fn parse_string(bytes: &[u8]) -> Result<(String, u8)> { 37 | let offset = bytes[0]; 38 | let length = (offset - 2) >> 1; 39 | 40 | ensure!(bytes[1] == 0x3); 41 | 42 | let mut res = String::new(); 43 | 44 | for i in 0..(length as usize) { 45 | res += std::str::from_utf8(&[bytes[2 * i + 2]])?; 46 | ensure!(bytes[2 * i + 2 + 1] == 0); 47 | } 48 | 49 | Ok((res, offset)) 50 | } 51 | 52 | let mut offset = 0usize; 53 | let (manufacturer, new_offset) = parse_string(&strings_buf[offset..])?; 54 | offset += new_offset as usize; 55 | let (product_description, new_offset) = parse_string(&strings_buf[offset..])?; 56 | offset += new_offset as usize; 57 | let (serial_number, _) = parse_string(&strings_buf[offset..])?; 58 | 59 | let reserved1 = data.read_u8()?; 60 | 61 | let power_attributes = data.read_u8()?; 62 | let power_consumption = data.read_u16::()?; 63 | 64 | let reserved2 = data.read_u8()?; 65 | 66 | let fifo_clock = FT60xFifoClock::parse(data.read_u8()?)?; 67 | let fifo_mode = FT60xFifoMode::parse(data.read_u8()?)?; 68 | let channel_config = FT60xChannelConfig::parse(data.read_u8()?)?; 69 | 70 | let optional_features_support = data.read_u16::()?; 71 | let battery_charging_gpio_config = data.read_u8()?; 72 | let flash_eeprom_detection = data.read_u8()?; 73 | let flash_eeprom_detection = 74 | ft60x_flash_rom_detection::FT60xFlashRomDetection::parse(flash_eeprom_detection)?; 75 | 76 | let msio_config = data.read_u32::()?; 77 | let gpio_config = data.read_u32::()?; 78 | 79 | Ok(FT60xConfig { 80 | vid, 81 | pid, 82 | manufacturer, 83 | product_description, 84 | serial_number, 85 | power_attributes, 86 | power_consumption, 87 | fifo_clock, 88 | fifo_mode, 89 | channel_config, 90 | optional_features_support, 91 | battery_charging_gpio_config, 92 | flash_eeprom_detection, 93 | msio_config, 94 | gpio_config, 95 | reserved1, 96 | reserved2, 97 | }) 98 | } 99 | 100 | pub fn encode(&self) -> Result<[u8; 152]> { 101 | let mut buf = [0; 152]; 102 | let mut cursor = Cursor::new(&mut buf[..]); 103 | 104 | cursor.write_u16::(self.vid)?; 105 | cursor.write_u16::(self.pid)?; 106 | 107 | let mut strings_buf = [0u8; 128]; 108 | let mut strings_cursor = Cursor::new(&mut strings_buf[..]); 109 | 110 | fn encode_string(string: &str, cursor: &mut Cursor<&mut [u8]>) -> Result<()> { 111 | let length = string.len(); 112 | let offset = (length + 1) << 1; 113 | 114 | cursor.write_u8(offset as u8)?; 115 | cursor.write_u8(0x3)?; 116 | 117 | for i in 0..length { 118 | cursor.write_u8(string.as_bytes()[i] as u8)?; 119 | cursor.write_u8(0x0)?; 120 | } 121 | 122 | Ok(()) 123 | } 124 | 125 | encode_string(&self.manufacturer, &mut strings_cursor)?; 126 | encode_string(&self.product_description, &mut strings_cursor)?; 127 | encode_string(&self.serial_number, &mut strings_cursor)?; 128 | 129 | cursor.write_all(&strings_buf)?; 130 | 131 | cursor.write_u8(self.reserved1)?; 132 | 133 | cursor.write_u8(self.power_attributes)?; 134 | cursor.write_u16::(self.power_consumption)?; 135 | 136 | cursor.write_u8(self.reserved2)?; 137 | 138 | cursor.write_u8(self.fifo_clock.encode())?; 139 | cursor.write_u8(self.fifo_mode.encode())?; 140 | cursor.write_u8(self.channel_config.encode())?; 141 | cursor.write_u16::(self.optional_features_support)?; 142 | cursor.write_u8(self.battery_charging_gpio_config)?; 143 | cursor.write_u8(self.flash_eeprom_detection.encode())?; 144 | 145 | cursor.write_u32::(self.msio_config)?; 146 | cursor.write_u32::(self.gpio_config)?; 147 | 148 | Ok(buf) 149 | } 150 | } 151 | 152 | #[derive(Debug)] 153 | pub enum FT60xFifoMode { 154 | Mode245, 155 | Mode600, 156 | } 157 | 158 | impl FT60xFifoMode { 159 | fn parse(num: u8) -> Result { 160 | match num { 161 | 0 => Ok(Self::Mode245), 162 | 1 => Ok(Self::Mode600), 163 | _ => Err(format_general_err!( 164 | "Unknown fifo mode configuration {}", 165 | num 166 | )), 167 | } 168 | } 169 | 170 | fn encode(&self) -> u8 { 171 | match self { 172 | Self::Mode245 => 0, 173 | Self::Mode600 => 1, 174 | } 175 | } 176 | } 177 | 178 | #[derive(Debug)] 179 | pub enum FT60xFifoClock { 180 | Clock100MHz, 181 | Clock66MHz, 182 | Clock50MHz, 183 | Clock40MHz, 184 | } 185 | 186 | impl FT60xFifoClock { 187 | fn parse(num: u8) -> Result { 188 | match num { 189 | 0 => Ok(Self::Clock100MHz), 190 | 1 => Ok(Self::Clock66MHz), 191 | 2 => Ok(Self::Clock50MHz), 192 | 3 => Ok(Self::Clock40MHz), 193 | _ => Err(format_general_err!( 194 | "Unknown fifo clock configuration {}", 195 | num 196 | )), 197 | } 198 | } 199 | 200 | fn encode(&self) -> u8 { 201 | match self { 202 | Self::Clock100MHz => 0, 203 | Self::Clock66MHz => 1, 204 | Self::Clock50MHz => 2, 205 | Self::Clock40MHz => 3, 206 | } 207 | } 208 | } 209 | 210 | #[derive(Debug)] 211 | pub enum FT60xChannelConfig { 212 | FourChannels, 213 | TwoChannels, 214 | OneChannel, 215 | OneChannelOutPipe, 216 | OneChannelInPipe, 217 | } 218 | 219 | impl FT60xChannelConfig { 220 | fn parse(num: u8) -> Result { 221 | match num { 222 | 0 => Ok(Self::FourChannels), 223 | 1 => Ok(Self::TwoChannels), 224 | 2 => Ok(Self::OneChannel), 225 | 3 => Ok(Self::OneChannelOutPipe), 226 | 4 => Ok(Self::OneChannelInPipe), 227 | _ => Err(format_general_err!("Unknown channel configuration {}", num)), 228 | } 229 | } 230 | 231 | fn encode(&self) -> u8 { 232 | match self { 233 | Self::FourChannels => 0, 234 | Self::TwoChannels => 1, 235 | Self::OneChannel => 2, 236 | Self::OneChannelOutPipe => 3, 237 | Self::OneChannelInPipe => 4, 238 | } 239 | } 240 | } 241 | 242 | pub mod ft60x_flash_rom_detection { 243 | use crate::Result; 244 | 245 | #[derive(Debug)] 246 | pub enum MemoryType { 247 | Flash, 248 | ROM, 249 | } 250 | 251 | #[derive(Debug)] 252 | pub enum MemoryStatus { 253 | Exists, 254 | ExistsNot, 255 | } 256 | 257 | #[derive(Debug)] 258 | pub enum CustomConfigValidity { 259 | Valid, 260 | Invalid, 261 | } 262 | 263 | #[derive(Debug)] 264 | pub enum CustomConfigChecksum { 265 | Valid, 266 | Invalid, 267 | } 268 | 269 | #[derive(Debug)] 270 | pub enum GPIOInput { 271 | Ignore, 272 | Used, 273 | } 274 | 275 | #[derive(Debug)] 276 | pub enum ConfigUsed { 277 | Default, 278 | Custom, 279 | } 280 | 281 | #[derive(Debug)] 282 | pub enum GPIO0 { 283 | Low, 284 | High, 285 | } 286 | 287 | #[derive(Debug)] 288 | pub enum GPIO1 { 289 | Low, 290 | High, 291 | } 292 | 293 | #[derive(Debug)] 294 | pub struct FT60xFlashRomDetection { 295 | memory_type: MemoryType, 296 | memory_status: MemoryStatus, 297 | custom_config_validity: CustomConfigValidity, 298 | custom_config_checksum: CustomConfigChecksum, 299 | gpio_input: GPIOInput, 300 | config_used: ConfigUsed, 301 | gpio0: GPIO0, 302 | gpio1: GPIO1, 303 | } 304 | 305 | impl FT60xFlashRomDetection { 306 | pub fn parse(flags: u8) -> Result { 307 | let memory_type = match flags & (1 << 0) { 308 | 0 => MemoryType::Flash, 309 | _ => MemoryType::ROM, 310 | }; 311 | 312 | let memory_status = match flags & (1 << 1) { 313 | 0 => MemoryStatus::Exists, 314 | _ => MemoryStatus::ExistsNot, 315 | }; 316 | 317 | let custom_config_validity = match flags & (1 << 2) { 318 | 0 => CustomConfigValidity::Valid, 319 | _ => CustomConfigValidity::Invalid, 320 | }; 321 | 322 | let custom_config_checksum = match flags & (1 << 3) { 323 | 0 => CustomConfigChecksum::Valid, 324 | _ => CustomConfigChecksum::Invalid, 325 | }; 326 | 327 | let config_used = match flags & (1 << 4) { 328 | 0 => ConfigUsed::Default, 329 | _ => ConfigUsed::Custom, 330 | }; 331 | 332 | let gpio_input = match flags & (1 << 5) { 333 | 0 => GPIOInput::Ignore, 334 | _ => GPIOInput::Used, 335 | }; 336 | 337 | let gpio0 = match flags & (1 << 6) { 338 | 0 => GPIO0::Low, 339 | _ => GPIO0::High, 340 | }; 341 | 342 | let gpio1 = match flags & (1 << 7) { 343 | 0 => GPIO1::Low, 344 | _ => GPIO1::High, 345 | }; 346 | 347 | Ok(FT60xFlashRomDetection { 348 | memory_type, 349 | memory_status, 350 | custom_config_validity, 351 | custom_config_checksum, 352 | gpio_input, 353 | config_used, 354 | gpio0, 355 | gpio1, 356 | }) 357 | } 358 | 359 | pub fn encode(&self) -> u8 { 360 | let mut flags = 0; 361 | if let MemoryType::ROM = self.memory_type { 362 | flags |= 1 << 0 363 | } 364 | if let MemoryStatus::ExistsNot = self.memory_status { 365 | flags |= 1 << 1 366 | } 367 | if let CustomConfigValidity::Invalid = self.custom_config_validity { 368 | flags |= 1 << 2 369 | } 370 | if let CustomConfigChecksum::Invalid = self.custom_config_checksum { 371 | flags |= 1 << 3 372 | } 373 | if let ConfigUsed::Custom = self.config_used { 374 | flags |= 1 << 4 375 | } 376 | if let GPIOInput::Used = self.gpio_input { 377 | flags |= 1 << 5 378 | } 379 | if let GPIO0::High = self.gpio0 { 380 | flags |= 1 << 6 381 | } 382 | if let GPIO1::High = self.gpio1 { 383 | flags |= 1 << 7 384 | } 385 | 386 | flags 387 | } 388 | } 389 | } 390 | --------------------------------------------------------------------------------