usbd_blaster/
blaster.rs

1use hal::digital::v2::{InputPin, OutputPin};
2use usb_device::{class_prelude::*, control::RequestType};
3
4use crate::class::{BlasterClass, FTDI_MODEM_STA_DUMMY};
5use crate::port::Port;
6
7/// Depending on the underlying USB library (libusb or similar) the OS may send/receive more bytes than declared in the USB endpoint
8/// If this happens to you, please open an issue for this crate on GitHub.
9const BLASTER_WRITE_SIZE: usize = 64;
10const BLASTER_READ_SIZE: usize = 32;
11
12/// Blaster device class
13pub struct Blaster<
14    'a,
15    B: UsbBus,
16    E,
17    TDI: OutputPin<Error = E>,
18    TCK: OutputPin<Error = E>,
19    TMS: OutputPin<Error = E>,
20    TDO: InputPin<Error = E>,
21> {
22    class: BlasterClass<'a, B>,
23    port: Port<E, TDI, TCK, TMS, TDO>,
24    send_buffer: [u8; BLASTER_WRITE_SIZE],
25    send_len: usize,
26    recv_buffer: [u8; BLASTER_READ_SIZE],
27    recv_len: usize,
28}
29
30impl<
31        'a,
32        B: UsbBus,
33        E,
34        TDI: OutputPin<Error = E>,
35        TCK: OutputPin<Error = E>,
36        TMS: OutputPin<Error = E>,
37        TDO: InputPin<Error = E>,
38    > Blaster<'a, B, E, TDI, TCK, TMS, TDO>
39{
40    /// Allocate a Blaster on the USB bus. Takes control of the four JTAG pins.
41    /// The JTAG pins can be any pins you want, just make sure you assign them correctly.
42    pub fn new(
43        alloc: &'a UsbBusAllocator<B>,
44        tdi: TDI,
45        tck: TCK,
46        tms: TMS,
47        tdo: TDO,
48    ) -> Blaster<'a, B, E, TDI, TCK, TMS, TDO> {
49        let mut blaster = Blaster {
50            class: BlasterClass::new(alloc, BLASTER_WRITE_SIZE as u16, BLASTER_READ_SIZE as u16),
51            port: Port::new(tdi, tck, tms, tdo),
52            send_buffer: [0u8; BLASTER_WRITE_SIZE],
53            send_len: 0,
54            recv_buffer: [0u8; BLASTER_READ_SIZE],
55            recv_len: 0,
56        };
57        blaster.send_buffer[0] = FTDI_MODEM_STA_DUMMY[0];
58        blaster.send_buffer[1] = FTDI_MODEM_STA_DUMMY[1];
59        blaster
60    }
61
62    /// Read data from the host output endpoint into the Blaster's internal read buffer.
63    pub fn read(&mut self) -> usb_device::Result<usize> {
64        if self.recv_len == self.recv_buffer.len() {
65            return Err(UsbError::WouldBlock);
66        }
67        let amount = self.class.read(&mut self.recv_buffer[self.recv_len..])?;
68        self.recv_len += amount;
69        Ok(amount)
70    }
71
72    /// Write data to the host input endpoint from the Blaster's internal write buffer.
73    /// The heartbeat parameter must be true at least once every 10 milliseconds, so that the blaster can output the modem status. See [libftdi ftdi.c](https://github.com/lipro/libftdi/blob/master/src/ftdi.c#L2053) for more on this.
74    /// Otherwise, [a BSOD could occur on Windows](https://github.com/mithro/ixo-usb-jtag/blob/master/usbjtag.c#L212)
75    /// A safe default for the heartbeat seems to be true all the time. This will output the modem status whenever the host reads the device.
76    pub fn write(&mut self, heartbeat: bool) -> usb_device::Result<usize> {
77        if self.send_len == 0 && !heartbeat {
78            return Err(UsbError::WouldBlock);
79        }
80        let res = self.class.write(&self.send_buffer[..self.send_len + 2]);
81        if res.is_ok() {
82            let amount = *res.as_ref().unwrap();
83            if amount <= 2 {
84                if amount == 1 {
85                    // TODO: how to handle a half-sent STA?
86                    panic!("Cannot recover from half-sent status");
87                }
88            } else {
89                self.send_buffer
90                    .copy_within((amount)..(self.send_len + 2), 2);
91                let actual_amount = amount - 2;
92                self.send_len -= actual_amount;
93            }
94        }
95        res
96    }
97
98    /// Runs all pending operations from the internal read buffer until either no operations are left or the internal write buffer is full.
99    /// If a GPIO error occurs, the JTAG state machine will enter an undefined state requiring a forced USB bus reset.
100    pub fn handle(&mut self) -> Result<(), E> {
101        self.port.handle(
102            &mut self.recv_buffer,
103            &mut self.recv_len,
104            &mut self.send_buffer[2..],
105            &mut self.send_len,
106        )
107    }
108}
109
110impl<
111        B,
112        E,
113        TDI: OutputPin<Error = E>,
114        TCK: OutputPin<Error = E>,
115        TMS: OutputPin<Error = E>,
116        TDO: InputPin<Error = E>,
117    > UsbClass<B> for Blaster<'_, B, E, TDI, TCK, TMS, TDO>
118where
119    B: UsbBus,
120    E: core::fmt::Debug,
121{
122    fn get_configuration_descriptors(
123        &self,
124        writer: &mut DescriptorWriter,
125    ) -> usb_device::Result<()> {
126        self.class.get_configuration_descriptors(writer)
127    }
128
129    fn reset(&mut self) {
130        self.class.reset();
131        // TODO: if this fails, there are bigger, device-level problems.
132        self.port.reset().expect("unable to reset port");
133        self.send_len = 0;
134        self.recv_len = 0;
135    }
136
137    fn control_in(&mut self, xfer: ControlIn<B>) {
138        self.class.control_in(xfer);
139    }
140
141    fn control_out(&mut self, xfer: ControlOut<B>) {
142        /// See [Linux kernel ftdi_sio.h](https://github.com/torvalds/linux/blob/master/drivers/usb/serial/ftdi_sio.h#L74)
143        const FTDI_VEN_REQ_RESET: u8 = 0x00;
144        /// [Set chip baud rate](https://github.com/torvalds/linux/blob/master/drivers/usb/serial/ftdi_sio.h#L104)
145        const _FTDI_VEN_REQ_SET_BAUDRATE: u8 = 0x01;
146        /// [Set RS232 line characteristics](https://github.com/torvalds/linux/blob/master/drivers/usb/serial/ftdi_sio.h#L198)
147        const _FTDI_VEN_REQ_SET_DATA_CHAR: u8 = 0x02;
148        /// [Set chip flow control](https://github.com/torvalds/linux/blob/master/drivers/usb/serial/ftdi_sio.h#L277)
149        const _FTDI_VEN_REQ_SET_FLOW_CTRL: u8 = 0x03;
150        /// [Set modem ctrl](https://github.com/torvalds/linux/blob/master/drivers/usb/serial/ftdi_sio.h#L232)
151        const _FTDI_VEN_REQ_SET_MODEM_CTRL: u8 = 0x04;
152        /// [Set special event character](https://github.com/torvalds/linux/blob/master/drivers/usb/serial/ftdi_sio.h#L365)
153        const _FTDI_VEN_REQ_SET_EVENT_CHAR: u8 = 0x06;
154        /// [Set parity error replacement character](https://github.com/torvalds/linux/blob/master/drivers/usb/serial/ftdi_sio.h#L382)
155        const _FTDI_VEN_REQ_SET_ERR_CHAR: u8 = 0x07;
156        /// [Set latency timer](https://github.com/torvalds/linux/blob/master/drivers/usb/serial/ftdi_sio.h#L324)
157        const _FTDI_VEN_REQ_SET_LAT_TIMER: u8 = 0x09;
158        /// [Set bitmode](https://github.com/lipro/libftdi/blob/master/src/ftdi.c#L1921)
159        const _FTDI_VEN_REQ_SET_BITMODE: u8 = 0x0B;
160        /// See [libftdi ftdi.h](https://github.com/lipro/libftdi/blob/master/src/ftdi.h#L169)
161        /// This request is rejected -- EEPROM is read-only.
162        const FTDI_VEN_REQ_WR_EEPROM: u8 = 0x91;
163        /// This request is rejected -- EEPROM is read-only.
164        const FTDI_VEN_REQ_ES_EEPROM: u8 = 0x92;
165
166        let req = xfer.request();
167        if req.request_type == RequestType::Vendor {
168            match req.request {
169                FTDI_VEN_REQ_RESET => {
170                    const RESET_SIO: u16 = 0x0000;
171                    const RESET_PURGE_RX: u16 = 0x0001;
172                    const RESET_PURGE_TX: u16 = 0x0002;
173                    match req.value {
174                        RESET_SIO => {
175                            self.reset();
176                            xfer.accept().unwrap();
177                        }
178                        RESET_PURGE_RX => {
179                            self.recv_len = 0;
180                            xfer.accept().unwrap();
181                        }
182                        RESET_PURGE_TX => {
183                            self.send_len = 0;
184                            xfer.accept().unwrap();
185                        }
186                        _ => {
187                            xfer.reject().unwrap();
188                        }
189                    }
190                }
191                FTDI_VEN_REQ_WR_EEPROM => {
192                    xfer.reject().unwrap();
193                }
194                FTDI_VEN_REQ_ES_EEPROM => {
195                    xfer.reject().unwrap();
196                }
197                _ => {
198                    xfer.accept().unwrap();
199                }
200            }
201        }
202    }
203}