bb_flasher_bcf/
cc1352p7.rs

1//! Library to provide flashing capabilities for [CC1352P7] in [BeagleConnect Freedom]. This is the
2//! main processor, and can be flashed just by connecting over USB.
3//!
4//! BSL command details can be found in [Technical Specification].
5//!
6//! [BeagleConnect Freedom]: https://www.beagleboard.org/boards/beagleconnect-freedom
7//! [CC1352P7]: https://www.ti.com/product/CC1352P7
8//! [Technical Specification]: https://www.ti.com/lit/ug/swcu192/swcu192.pdf?ts=1741089110661&ref_url=https%253A%252F%252Fwww.ti.com%252Fproduct%252FCC1352P7
9
10use std::{io, time::Duration};
11
12use futures::channel::mpsc;
13use serialport::SerialPort;
14use thiserror::Error;
15use tracing::{error, info, warn};
16
17use crate::{
18    Status,
19    helpers::{chan_send, parse_bin},
20};
21
22const ACK: u8 = 0xcc;
23const NACK: u8 = 0x33;
24
25const COMMAND_DOWNLOAD: u8 = 0x21;
26const COMMAND_GET_STATUS: u8 = 0x23;
27const COMMAND_SEND_DATA: u8 = 0x24;
28const COMMAND_RESET: u8 = 0x25;
29const COMMAND_CRC32: u8 = 0x27;
30const COMMAND_BANK_ERASE: u8 = 0x2c;
31
32const COMMAND_MAX_SIZE: u8 = u8::MAX - 3;
33
34const FIRMWARE_SIZE: u32 = 704 * 1024;
35
36type Result<T, E = Error> = std::result::Result<T, E>;
37
38#[derive(Error, Debug)]
39/// Errors for CC1352P7
40pub enum Error {
41    /// Status for failing flash erase or program operation
42    #[error("Status for failing flash erase or program operation")]
43    FlashFail,
44    /// Bootloader sent unexpected response
45    #[error("Bootloader sent unexpected response")]
46    UnknownResponse,
47    /// Bootloader Responded with Nack
48    #[error("Bootloader Responded with Nack")]
49    Nack,
50    /// Failed to start Bootloader
51    #[error("Failed to start Bootloader")]
52    FailedToStartBootloader,
53    /// Flashed image is not valid
54    #[error("Flashed image is not valid")]
55    InvalidImage,
56    /// Failed to open serial port
57    #[error("Failed to open serial port")]
58    FailedToOpenPort,
59    /// Aborted before completing
60    #[error("Aborted before completing")]
61    Aborted,
62    #[error("IO Error: {0}")]
63    IoError(io::Error),
64}
65
66impl From<io::Error> for Error {
67    fn from(value: io::Error) -> Self {
68        Self::IoError(value)
69    }
70}
71
72struct BeagleConnectFreedom<S: SerialPort> {
73    port: S,
74}
75
76impl<S> BeagleConnectFreedom<S>
77where
78    S: SerialPort,
79{
80    fn new(port: S) -> Result<Self> {
81        let mut bcf = BeagleConnectFreedom { port };
82
83        bcf.invoke_bootloader()?;
84        bcf.send_sync()?;
85
86        Ok(bcf)
87    }
88
89    fn wait_for_ack(&mut self) -> Result<()> {
90        let mut buf = [0u8; 1];
91
92        while buf[0] == 0x00 {
93            self.port.read_exact(&mut buf)?;
94        }
95
96        match buf[0] {
97            ACK => Ok(()),
98            NACK => Err(Error::Nack),
99            _ => Err(Error::UnknownResponse),
100        }
101    }
102
103    fn invoke_bootloader(&mut self) -> Result<()> {
104        info!("Invoke Bootloader");
105
106        self.port
107            .set_break()
108            .map_err(|_| Error::FailedToStartBootloader)?;
109        std::thread::sleep(Duration::from_secs(2));
110        self.port
111            .clear_break()
112            .map_err(|_| Error::FailedToStartBootloader)?;
113
114        std::thread::sleep(Duration::from_millis(500));
115        Ok(())
116    }
117
118    fn send_sync(&mut self) -> Result<()> {
119        info!("Send Sync");
120        const PKT: &[u8] = &[0x55, 0x55];
121
122        self.port.write_all(PKT)?;
123
124        self.wait_for_ack()
125    }
126
127    fn crc32(&mut self) -> Result<u32> {
128        let addr = 0u32.to_be_bytes();
129        let size = FIRMWARE_SIZE.to_be_bytes();
130        let read_repeat = 0u32.to_be_bytes();
131        let mut cmd = [0u8; 2];
132        let mut cmd_data = [0u8; 4];
133
134        let checksum: u8 = size
135            .iter()
136            .chain(&[COMMAND_CRC32])
137            .fold(0u8, |acc, t| acc.wrapping_add(*t));
138
139        self.port.write_all(&[15, checksum, COMMAND_CRC32])?;
140        self.port.write_all(&addr)?;
141        self.port.write_all(&size)?;
142        self.port.write_all(&read_repeat)?;
143
144        self.wait_for_ack()?;
145
146        self.port.read_exact(&mut cmd)?;
147        assert_eq!(cmd[0], 6);
148
149        let checksum = cmd[1];
150
151        self.port.read_exact(&mut cmd_data)?;
152        assert_eq!(
153            checksum,
154            cmd_data.iter().fold(0u8, |acc, x| acc.wrapping_add(*x))
155        );
156
157        self.send_ack()?;
158
159        Ok(u32::from_be_bytes(cmd_data))
160    }
161
162    fn send_ack(&mut self) -> Result<(), Error> {
163        const PKT: &[u8] = &[0x00, ACK];
164        self.port.write_all(PKT).map_err(Into::into)
165    }
166
167    fn send_bank_erase(&mut self) -> Result<(), Error> {
168        const CMD: &[u8] = &[3, COMMAND_BANK_ERASE, COMMAND_BANK_ERASE];
169
170        self.port.write_all(CMD)?;
171
172        self.wait_for_ack()?;
173        self.get_status()
174    }
175
176    fn get_status(&mut self) -> Result<(), Error> {
177        const CMD: &[u8] = &[3, COMMAND_GET_STATUS, COMMAND_GET_STATUS];
178        let mut resp = [0u8; 1];
179
180        self.port.write_all(CMD)?;
181
182        self.wait_for_ack()?;
183
184        while resp[0] == 0x00 {
185            self.port.read_exact(&mut resp)?;
186        }
187
188        self.port.read_exact(&mut resp)?;
189        self.port.read_exact(&mut resp)?;
190
191        self.send_ack()?;
192
193        match resp[0] {
194            0x40 => Ok(()),
195            0x41 => panic!("Unknown Command"),
196            0x42 => panic!("Invalid Command"),
197            0x43 => panic!("Invalid Address"),
198            0x44 => Err(Error::FlashFail),
199            _ => Err(Error::UnknownResponse),
200        }
201    }
202
203    fn send_download(&mut self, addr: u32, size: u32) -> Result<(), Error> {
204        let addr = addr.to_be_bytes();
205        let size = size.to_be_bytes();
206
207        let checksum: u8 = addr
208            .into_iter()
209            .chain(size)
210            .chain([COMMAND_DOWNLOAD])
211            .fold(0u8, |acc, t| acc.wrapping_add(t));
212
213        self.port.write_all(&[11, checksum, COMMAND_DOWNLOAD])?;
214        self.port.write_all(&addr)?;
215        self.port.write_all(&size)?;
216
217        self.wait_for_ack()?;
218        self.get_status()
219    }
220
221    fn send_data(&mut self, data: &[u8]) -> Result<usize> {
222        let bytes_to_write = std::cmp::min(data.len(), usize::from(COMMAND_MAX_SIZE));
223
224        let checksum = data[..bytes_to_write]
225            .iter()
226            .chain(&[COMMAND_SEND_DATA])
227            .fold(0u8, |acc, t| acc.wrapping_add(*t));
228
229        self.port
230            .write_all(&[(bytes_to_write + 3) as u8, checksum, COMMAND_SEND_DATA])?;
231        self.port.write_all(&data[..bytes_to_write])?;
232
233        self.wait_for_ack()?;
234        self.get_status()?;
235
236        Ok(bytes_to_write)
237    }
238
239    fn send_reset(&mut self) -> Result<(), Error> {
240        const CMD: &[u8] = &[3, COMMAND_RESET, COMMAND_RESET];
241
242        self.port.write_all(CMD)?;
243        self.wait_for_ack()
244    }
245
246    fn verify(&mut self, crc32: u32) -> Result<bool> {
247        self.crc32().map(|x| x == crc32)
248    }
249}
250
251impl<S> Drop for BeagleConnectFreedom<S>
252where
253    S: SerialPort,
254{
255    fn drop(&mut self) {
256        let _ = self.send_reset();
257    }
258}
259
260const fn progress(off: usize) -> f32 {
261    (off as f32) / (FIRMWARE_SIZE as f32)
262}
263
264fn check_arc(cancel: Option<&std::sync::Weak<()>>) -> Result<()> {
265    match cancel {
266        Some(x) if x.strong_count() == 0 => Err(Error::Aborted),
267        _ => Ok(()),
268    }
269}
270
271/// Flash BeagleConnect Freedom. Also provides optional progress and abort mechanism.
272///
273/// # Firmware
274///
275/// Firmware type is auto detected. Supported firmwares:
276///
277/// - Raw binary
278/// - Ti-TXT
279/// - Intel Hex
280///
281/// # Aborting
282///
283/// The process can be aborted by dropping all strong references to the [`Arc`] that owns the
284/// [`Weak`] passed as `cancel`.
285///
286/// [`Arc`]: std::sync::Arc
287/// [`Weak`]: std::sync::Weak
288pub fn flash(
289    firmware: &[u8],
290    port: &str,
291    verify: bool,
292    mut chan: Option<mpsc::Sender<Status>>,
293    cancel: Option<std::sync::Weak<()>>,
294) -> Result<()> {
295    let firmware_bin = parse_bin(firmware).map_err(|_| Error::InvalidImage)?;
296
297    chan_send(chan.as_mut(), Status::Preparing);
298
299    let port = serialport::new(port, 115200)
300        .timeout(Duration::from_millis(500))
301        .open_native()
302        .map_err(|_| Error::FailedToOpenPort)?;
303    let mut bcf = BeagleConnectFreedom::new(port)?;
304    info!("BeagleConnectFreedom Connected");
305
306    check_arc(cancel.as_ref())?;
307    chan_send(chan.as_mut(), Status::Flashing(0.0));
308
309    let img_crc32 = crc32fast::hash(
310        &firmware_bin
311            .to_bytes(0..(FIRMWARE_SIZE as usize), Some(0xff))
312            .unwrap(),
313    );
314    if bcf.verify(img_crc32)? {
315        warn!("Skipping flashing same image");
316        return Ok(());
317    }
318
319    check_arc(cancel.as_ref())?;
320    info!("Erase Flash");
321    bcf.send_bank_erase()?;
322
323    info!("Start Flashing");
324
325    check_arc(cancel.as_ref())?;
326    for (start_address, data) in firmware_bin.segments_list() {
327        let mut offset = 0;
328
329        bcf.send_download(
330            start_address.try_into().unwrap(),
331            data.len().try_into().unwrap(),
332        )?;
333        while offset < data.len() {
334            offset += bcf.send_data(&data[offset..])?;
335
336            chan_send(
337                chan.as_mut(),
338                Status::Flashing(progress(start_address + offset)),
339            );
340            check_arc(cancel.as_ref())?;
341        }
342    }
343
344    let res = if verify {
345        chan_send(chan.as_mut(), Status::Verifying);
346        if bcf.verify(img_crc32)? {
347            info!("Flashing Successful");
348            Ok(())
349        } else {
350            error!("Invalid CRC32 in Flash. The flashed image might be corrupted");
351            Err(Error::InvalidImage)
352        }
353    } else {
354        Ok(())
355    };
356
357    res
358}
359
360/// Returns all paths to ports having BeagleConnect Freedom.
361pub fn ports() -> std::collections::HashSet<String> {
362    serialport::available_ports()
363        .expect("Unsupported OS")
364        .into_iter()
365        .filter(|x| {
366            if cfg!(target_os = "linux") {
367                match &x.port_type {
368                    serialport::SerialPortType::UsbPort(y) => {
369                        y.manufacturer.as_deref() == Some("BeagleBoard.org")
370                            && y.product.as_deref() == Some("BeagleConnect")
371                    }
372                    _ => false,
373                }
374            } else {
375                true
376            }
377        })
378        .map(|x| x.port_name)
379        .collect()
380}