probe_rs/probe/ftdi/
mod.rs

1//! FTDI-based debug probes.
2use crate::{
3    architecture::{
4        arm::{
5            ArmCommunicationInterface, ArmDebugInterface, ArmError,
6            communication_interface::DapProbe, sequences::ArmDebugSequence,
7        },
8        riscv::{
9            communication_interface::{RiscvError, RiscvInterfaceBuilder},
10            dtm::jtag_dtm::JtagDtmBuilder,
11        },
12        xtensa::communication_interface::{
13            XtensaCommunicationInterface, XtensaDebugInterfaceState, XtensaError,
14        },
15    },
16    probe::{
17        AutoImplementJtagAccess, DebugProbe, DebugProbeError, DebugProbeInfo, DebugProbeSelector,
18        IoSequenceItem, JtagAccess, JtagDriverState, ProbeCreationError, ProbeFactory,
19        ProbeStatistics, RawJtagIo, RawSwdIo, SwdSettings, WireProtocol,
20    },
21};
22use bitvec::prelude::*;
23use nusb::{DeviceInfo, MaybeFuture};
24use std::{
25    io::{Read, Write},
26    sync::Arc,
27    time::{Duration, Instant},
28};
29
30mod command_compacter;
31mod ftdaye;
32
33use command_compacter::Command;
34use ftdaye::{ChipType, error::FtdiError};
35
36#[derive(Debug)]
37struct JtagAdapter {
38    device: ftdaye::Device,
39    speed_khz: u32,
40
41    command: Command,
42    commands: Vec<u8>,
43    in_bit_counts: Vec<usize>,
44    in_bits: BitVec,
45    ftdi: FtdiProperties,
46}
47
48impl JtagAdapter {
49    fn open(ftdi: FtdiDevice, usb_device: DeviceInfo) -> Result<Self, DebugProbeError> {
50        let device = ftdaye::Builder::new()
51            .with_interface(ftdaye::Interface::A)
52            .with_read_timeout(Duration::from_secs(5))
53            .with_write_timeout(Duration::from_secs(5))
54            .usb_open(usb_device)?;
55
56        let ftdi = FtdiProperties::try_from((ftdi, device.chip_type()))?;
57
58        Ok(Self {
59            device,
60            speed_khz: 1000,
61            command: Command::default(),
62            commands: vec![],
63            in_bit_counts: vec![],
64            in_bits: BitVec::new(),
65            ftdi,
66        })
67    }
68
69    pub fn attach(&mut self) -> Result<(), FtdiError> {
70        self.device.usb_reset()?;
71        // 0x0B configures pins for JTAG
72        self.device.set_bitmode(0x0b, ftdaye::BitMode::Mpsse)?;
73        self.device.set_latency_timer(1)?;
74        self.device.usb_purge_buffers()?;
75
76        let mut junk = vec![];
77        let _ = self.device.read_to_end(&mut junk);
78
79        let (output, direction) = self.pin_layout();
80        self.device.set_pins(output, direction)?;
81
82        self.apply_clock_speed(self.speed_khz)?;
83
84        self.device.disable_loopback()?;
85
86        Ok(())
87    }
88
89    fn pin_layout(&self) -> (u16, u16) {
90        let (output, direction) = match (
91            self.device.vendor_id(),
92            self.device.product_id(),
93            self.device.product_string().unwrap_or(""),
94        ) {
95            // Digilent HS3
96            (0x0403, 0x6014, "Digilent USB Device") => (0x2088, 0x308b),
97            // Digilent HS2
98            (0x0403, 0x6014, "Digilent Adept USB Device") => (0x00e8, 0x60eb),
99            // Digilent HS1
100            (0x0403, 0x6010, "Digilent Adept USB Device") => (0x0088, 0x008b),
101            // Built-in Digilent HS1 (on-board)
102            (0x0403, 0x6010, "Digilent USB Device") => (0x0088, 0x008b),
103            // Other devices:
104            // TMS starts high
105            // TMS, TDO and TCK are outputs
106            _ => (0x0008, 0x000b),
107        };
108        (output, direction)
109    }
110
111    fn speed_khz(&self) -> u32 {
112        self.speed_khz
113    }
114
115    fn set_speed_khz(&mut self, speed_khz: u32) -> u32 {
116        self.speed_khz = speed_khz;
117        self.speed_khz
118    }
119
120    fn apply_clock_speed(&mut self, speed_khz: u32) -> Result<u32, FtdiError> {
121        // Disable divide-by-5 mode if available
122        if self.ftdi.has_divide_by_5 {
123            self.device.disable_divide_by_5()?;
124        } else {
125            // Force enable divide-by-5 mode if not available or unknown
126            self.device.enable_divide_by_5()?;
127        }
128
129        // If `speed_khz` is not a divisor of the maximum supported speed, we need to round up
130        let is_exact = self.ftdi.max_clock.is_multiple_of(speed_khz);
131
132        // If `speed_khz` is 0, use the maximum supported speed
133        let divisor =
134            (self.ftdi.max_clock.checked_div(speed_khz).unwrap_or(1) - is_exact as u32).min(0xFFFF);
135
136        let actual_speed = self.ftdi.max_clock / (divisor + 1);
137
138        tracing::info!(
139            "Setting speed to {} kHz (divisor: {}, actual speed: {} kHz)",
140            speed_khz,
141            divisor,
142            actual_speed
143        );
144
145        self.device.configure_clock_divider(divisor as u16)?;
146
147        self.speed_khz = actual_speed;
148        Ok(actual_speed)
149    }
150
151    fn read_response(&mut self) -> Result<(), DebugProbeError> {
152        if self.in_bit_counts.is_empty() {
153            return Ok(());
154        }
155
156        let mut t0 = Instant::now();
157        let timeout = Duration::from_millis(10);
158
159        let mut reply = Vec::with_capacity(self.in_bit_counts.len());
160        while reply.len() < self.in_bit_counts.len() {
161            let read = self
162                .device
163                .read_to_end(&mut reply)
164                .map_err(FtdiError::from)?;
165
166            if read > 0 {
167                t0 = Instant::now();
168            }
169
170            if t0.elapsed() > timeout {
171                tracing::warn!(
172                    "Read {} bytes, expected {}",
173                    reply.len(),
174                    self.in_bit_counts.len()
175                );
176                return Err(DebugProbeError::Timeout);
177            }
178        }
179
180        if reply.len() != self.in_bit_counts.len() {
181            return Err(DebugProbeError::Other(format!(
182                "Read more data than expected. Expected {} bytes, got {} bytes",
183                self.in_bit_counts.len(),
184                reply.len()
185            )));
186        }
187
188        for (byte, count) in reply.into_iter().zip(self.in_bit_counts.drain(..)) {
189            let bits = byte >> (8 - count);
190            self.in_bits
191                .extend_from_bitslice(&bits.view_bits::<Lsb0>()[..count]);
192        }
193
194        Ok(())
195    }
196
197    fn flush(&mut self) -> Result<(), DebugProbeError> {
198        self.finalize_command()?;
199        self.send_buffer()?;
200        self.read_response()?;
201
202        Ok(())
203    }
204
205    fn append_command(&mut self, command: Command) -> Result<(), DebugProbeError> {
206        tracing::trace!("Appending {:?}", command);
207        // 1 byte is reserved for the send immediate command
208        if self.commands.len() + command.len() + 1 >= self.ftdi.buffer_size {
209            self.send_buffer()?;
210            self.read_response()?;
211        }
212
213        command.add_captured_bits(&mut self.in_bit_counts);
214        command.encode(&mut self.commands);
215
216        Ok(())
217    }
218
219    fn finalize_command(&mut self) -> Result<(), DebugProbeError> {
220        if let Some(command) = self.command.take() {
221            self.append_command(command)?;
222        }
223
224        Ok(())
225    }
226
227    fn shift_bit(&mut self, tms: bool, tdi: bool, capture: bool) -> Result<(), DebugProbeError> {
228        if let Some(command) = self.command.append_jtag_bit(tms, tdi, capture) {
229            self.append_command(command)?;
230        }
231
232        Ok(())
233    }
234
235    fn send_buffer(&mut self) -> Result<(), DebugProbeError> {
236        if self.commands.is_empty() {
237            return Ok(());
238        }
239
240        // Send Immediate: This will make the FTDI chip flush its buffer back to the PC.
241        // See https://www.ftdichip.com/Support/Documents/AppNotes/AN_108_Command_Processor_for_MPSSE_and_MCU_Host_Bus_Emulation_Modes.pdf
242        // section 5.1
243        self.commands.push(0x87);
244
245        tracing::trace!("Sending buffer: {:X?}", self.commands);
246
247        self.device
248            .write_all(&self.commands)
249            .map_err(FtdiError::from)?;
250
251        self.commands.clear();
252
253        Ok(())
254    }
255
256    fn read_captured_bits(&mut self) -> Result<BitVec, DebugProbeError> {
257        self.flush()?;
258
259        Ok(std::mem::take(&mut self.in_bits))
260    }
261}
262
263/// A factory for creating [`FtdiProbe`] instances.
264#[derive(Debug)]
265pub struct FtdiProbeFactory;
266
267impl std::fmt::Display for FtdiProbeFactory {
268    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
269        f.write_str("FTDI")
270    }
271}
272
273impl ProbeFactory for FtdiProbeFactory {
274    fn open(&self, selector: &DebugProbeSelector) -> Result<Box<dyn DebugProbe>, DebugProbeError> {
275        // Only open FTDI-compatible probes
276        let Some(ftdi) = FTDI_COMPAT_DEVICES
277            .iter()
278            .find(|ftdi| ftdi.id == (selector.vendor_id, selector.product_id))
279            .copied()
280        else {
281            return Err(DebugProbeError::ProbeCouldNotBeCreated(
282                ProbeCreationError::NotFound,
283            ));
284        };
285
286        let devices = nusb::list_devices()
287            .wait()
288            .map_err(|e| DebugProbeError::from(FtdiError::Usb(e.into())))?;
289
290        let mut probes = devices
291            .filter(|usb_info| selector.matches(usb_info))
292            .collect::<Vec<_>>();
293
294        if probes.is_empty() {
295            return Err(DebugProbeError::ProbeCouldNotBeCreated(
296                ProbeCreationError::NotFound,
297            ));
298        } else if probes.len() > 1 {
299            tracing::warn!("More than one matching FTDI probe was found. Opening the first one.");
300        }
301
302        let probe = FtdiProbe {
303            adapter: JtagAdapter::open(ftdi, probes.pop().unwrap())?,
304            jtag_state: JtagDriverState::default(),
305            swd_settings: SwdSettings::default(),
306            probe_statistics: ProbeStatistics::default(),
307        };
308        tracing::debug!("opened probe: {:?}", probe);
309        Ok(Box::new(probe))
310    }
311
312    fn list_probes(&self) -> Vec<DebugProbeInfo> {
313        list_ftdi_devices()
314    }
315}
316
317/// An FTDI-based debug probe.
318#[derive(Debug)]
319pub struct FtdiProbe {
320    adapter: JtagAdapter,
321    jtag_state: JtagDriverState,
322    probe_statistics: ProbeStatistics,
323    swd_settings: SwdSettings,
324}
325
326impl DebugProbe for FtdiProbe {
327    fn get_name(&self) -> &str {
328        "FTDI"
329    }
330
331    fn speed_khz(&self) -> u32 {
332        self.adapter.speed_khz()
333    }
334
335    fn set_speed(&mut self, speed_khz: u32) -> Result<u32, DebugProbeError> {
336        Ok(self.adapter.set_speed_khz(speed_khz))
337    }
338
339    fn attach(&mut self) -> Result<(), DebugProbeError> {
340        tracing::debug!("Attaching...");
341
342        self.adapter.attach()?;
343        self.select_target(0)
344    }
345
346    fn detach(&mut self) -> Result<(), crate::Error> {
347        Ok(())
348    }
349
350    fn target_reset(&mut self) -> Result<(), DebugProbeError> {
351        // TODO we could add this by using a GPIO. However, different probes may connect
352        // different pins (if any) to the reset line, so we would need to make this configurable.
353        Err(DebugProbeError::NotImplemented {
354            function_name: "target_reset",
355        })
356    }
357
358    fn target_reset_assert(&mut self) -> Result<(), DebugProbeError> {
359        Err(DebugProbeError::NotImplemented {
360            function_name: "target_reset_assert",
361        })
362    }
363
364    fn target_reset_deassert(&mut self) -> Result<(), DebugProbeError> {
365        Err(DebugProbeError::NotImplemented {
366            function_name: "target_reset_deassert",
367        })
368    }
369
370    fn select_protocol(&mut self, protocol: WireProtocol) -> Result<(), DebugProbeError> {
371        if protocol != WireProtocol::Jtag {
372            Err(DebugProbeError::UnsupportedProtocol(protocol))
373        } else {
374            Ok(())
375        }
376    }
377
378    fn active_protocol(&self) -> Option<WireProtocol> {
379        // Only supports JTAG
380        Some(WireProtocol::Jtag)
381    }
382
383    fn try_as_jtag_probe(&mut self) -> Option<&mut dyn JtagAccess> {
384        Some(self)
385    }
386
387    fn try_get_riscv_interface_builder<'probe>(
388        &'probe mut self,
389    ) -> Result<Box<dyn RiscvInterfaceBuilder<'probe> + 'probe>, RiscvError> {
390        Ok(Box::new(JtagDtmBuilder::new(self)))
391    }
392
393    fn has_riscv_interface(&self) -> bool {
394        true
395    }
396
397    fn into_probe(self: Box<Self>) -> Box<dyn DebugProbe> {
398        self
399    }
400
401    fn try_get_arm_debug_interface<'probe>(
402        self: Box<Self>,
403        sequence: Arc<dyn ArmDebugSequence>,
404    ) -> Result<Box<dyn ArmDebugInterface + 'probe>, (Box<dyn DebugProbe>, ArmError)> {
405        Ok(ArmCommunicationInterface::create(self, sequence, true))
406    }
407
408    fn has_arm_interface(&self) -> bool {
409        true
410    }
411
412    fn try_get_xtensa_interface<'probe>(
413        &'probe mut self,
414        state: &'probe mut XtensaDebugInterfaceState,
415    ) -> Result<XtensaCommunicationInterface<'probe>, XtensaError> {
416        Ok(XtensaCommunicationInterface::new(self, state))
417    }
418
419    fn has_xtensa_interface(&self) -> bool {
420        true
421    }
422}
423
424impl AutoImplementJtagAccess for FtdiProbe {}
425impl DapProbe for FtdiProbe {}
426
427impl RawSwdIo for FtdiProbe {
428    fn swd_io<S>(&mut self, _swdio: S) -> Result<Vec<bool>, DebugProbeError>
429    where
430        S: IntoIterator<Item = IoSequenceItem>,
431    {
432        Err(DebugProbeError::NotImplemented {
433            function_name: "swd_io",
434        })
435    }
436
437    fn swj_pins(
438        &mut self,
439        _pin_out: u32,
440        _pin_select: u32,
441        _pin_wait: u32,
442    ) -> Result<u32, DebugProbeError> {
443        Err(DebugProbeError::CommandNotSupportedByProbe {
444            command_name: "swj_pins",
445        })
446    }
447
448    fn swd_settings(&self) -> &SwdSettings {
449        &self.swd_settings
450    }
451
452    fn probe_statistics(&mut self) -> &mut ProbeStatistics {
453        &mut self.probe_statistics
454    }
455}
456
457impl RawJtagIo for FtdiProbe {
458    fn shift_bit(
459        &mut self,
460        tms: bool,
461        tdi: bool,
462        capture_tdo: bool,
463    ) -> Result<(), DebugProbeError> {
464        self.jtag_state.state.update(tms);
465        self.adapter.shift_bit(tms, tdi, capture_tdo)?;
466        Ok(())
467    }
468
469    fn read_captured_bits(&mut self) -> Result<BitVec, DebugProbeError> {
470        self.adapter.read_captured_bits()
471    }
472
473    fn state_mut(&mut self) -> &mut JtagDriverState {
474        &mut self.jtag_state
475    }
476
477    fn state(&self) -> &JtagDriverState {
478        &self.jtag_state
479    }
480}
481
482/// Known properties associated to particular FTDI chip types.
483#[derive(Debug)]
484struct FtdiProperties {
485    /// The size of the device's RX buffer.
486    ///
487    /// We can push down this many bytes to the device in one batch.
488    buffer_size: usize,
489
490    /// The maximum TCK clock speed supported by the device, in kHz.
491    max_clock: u32,
492
493    /// Whether the device supports the divide-by-5 clock mode for "FT2232D compatibility".
494    ///
495    /// Newer devices have 60MHz internal clocks, instead of 12MHz, however, they still
496    /// fall back to 12MHz by default. This flag indicates whether we can disable the clock divider.
497    has_divide_by_5: bool,
498}
499
500impl TryFrom<(FtdiDevice, Option<ChipType>)> for FtdiProperties {
501    type Error = FtdiError;
502
503    fn try_from((ftdi, chip_type): (FtdiDevice, Option<ChipType>)) -> Result<Self, Self::Error> {
504        let chip_type = match chip_type {
505            Some(ty) => ty,
506            None => {
507                tracing::warn!("Unknown FTDI chip. Assuming {:?}", ftdi.fallback_chip_type);
508                ftdi.fallback_chip_type
509            }
510        };
511
512        let properties = match chip_type {
513            ChipType::FT2232H | ChipType::FT4232H => Self {
514                buffer_size: 4096,
515                max_clock: 30_000,
516                has_divide_by_5: true,
517            },
518            ChipType::FT232H => Self {
519                buffer_size: 1024,
520                max_clock: 30_000,
521                has_divide_by_5: true,
522            },
523            ChipType::FT2232C => Self {
524                buffer_size: 128,
525                max_clock: 6_000,
526                has_divide_by_5: false,
527            },
528            not_mpsse => {
529                tracing::warn!("Unsupported FTDI chip: {:?}", not_mpsse);
530                return Err(FtdiError::UnsupportedChipType(not_mpsse));
531            }
532        };
533
534        Ok(properties)
535    }
536}
537
538#[derive(Debug, Clone, Copy)]
539struct FtdiDevice {
540    /// The (VID, PID) pair of this device.
541    id: (u16, u16),
542
543    /// FTDI chip type to use if the device is not recognized.
544    ///
545    /// "FTDI compatible" devices may use the same VID/PID pair as an FTDI device, but
546    /// they may be implemented by a completely third party solution. In this case,
547    /// we still try the same `bcdDevice` based detection, but if it fails, we fall back
548    /// to this chip type.
549    fallback_chip_type: ChipType,
550}
551
552impl FtdiDevice {
553    fn matches(&self, device: &DeviceInfo) -> bool {
554        self.id == (device.vendor_id(), device.product_id())
555    }
556}
557
558/// Known FTDI device variants.
559static FTDI_COMPAT_DEVICES: &[FtdiDevice] = &[
560    //
561    // --- FTDI VID/PID pairs ---
562    //
563    // FTDI Ltd. FT2232C/D/H Dual UART/FIFO IC
564    FtdiDevice {
565        id: (0x0403, 0x6010),
566        fallback_chip_type: ChipType::FT2232C,
567    },
568    // FTDI Ltd. FT4232H Quad HS USB-UART/FIFO IC
569    FtdiDevice {
570        id: (0x0403, 0x6011),
571        fallback_chip_type: ChipType::FT4232H,
572    },
573    // FTDI Ltd. FT232H Single HS USB-UART/FIFO IC
574    FtdiDevice {
575        id: (0x0403, 0x6014),
576        fallback_chip_type: ChipType::FT232H,
577    },
578    //
579    // --- Third-party VID/PID pairs ---
580    //
581    // Olimex Ltd. ARM-USB-OCD
582    FtdiDevice {
583        id: (0x15ba, 0x0003),
584        fallback_chip_type: ChipType::FT2232C,
585    },
586    // Olimex Ltd. ARM-USB-TINY
587    FtdiDevice {
588        id: (0x15ba, 0x0004),
589        fallback_chip_type: ChipType::FT2232C,
590    },
591    // Olimex Ltd. ARM-USB-TINY-H
592    FtdiDevice {
593        id: (0x15ba, 0x002a),
594        fallback_chip_type: ChipType::FT2232H,
595    },
596    // Olimex Ltd. ARM-USB-OCD-H
597    FtdiDevice {
598        id: (0x15ba, 0x002b),
599        fallback_chip_type: ChipType::FT2232H,
600    },
601];
602
603fn get_device_info(device: &DeviceInfo) -> Option<DebugProbeInfo> {
604    FTDI_COMPAT_DEVICES.iter().find_map(|ftdi| {
605        ftdi.matches(device).then(|| DebugProbeInfo {
606            identifier: device.product_string().unwrap_or("FTDI").to_string(),
607            vendor_id: device.vendor_id(),
608            product_id: device.product_id(),
609            serial_number: device.serial_number().map(|s| s.to_string()),
610            probe_factory: &FtdiProbeFactory,
611            is_hid_interface: false,
612            interface: None,
613        })
614    })
615}
616
617#[tracing::instrument(skip_all)]
618fn list_ftdi_devices() -> Vec<DebugProbeInfo> {
619    match nusb::list_devices().wait() {
620        Ok(devices) => devices
621            .filter_map(|device| get_device_info(&device))
622            .collect(),
623        Err(e) => {
624            tracing::warn!("error listing FTDI devices: {e}");
625            vec![]
626        }
627    }
628}