bb_flasher_bcf/
msp430.rs

1//! Library to provide flashing capabilities for [MSP430F5503] in [BeagleConnect Freedom]. This is
2//! the co-processor that acts as USB-to-UART.
3//!
4//! To flash MSP430, we need to boot into BSL. This can be done by holding BOOT button while
5//! connecting the USB to [BeagleConnect Freedom].
6//!
7//! BSL command details can be found in [MSP430™ Flash Devices Bootloader (BSL)].
8//!
9//! [BeagleConnect Freedom]: https://www.beagleboard.org/boards/beagleconnect-freedom
10//! [MSP430F5503]: https://www.ti.com/product/MSP430F5503
11//! [MSP430™ Flash Devices Bootloader (BSL)]: https://www.ti.com/lit/ug/slau319af/slau319af.pdf?ts=1741178254884
12
13use std::{ffi::CString, time::Duration};
14
15use futures::channel::mpsc;
16use thiserror::Error;
17
18use crate::{
19    Status,
20    helpers::{chan_send, parse_bin},
21};
22
23const VID: u16 = 0x2047;
24const PID: u16 = 0x0200;
25
26const USB_MSG_HEADER: u8 = 0x3f;
27
28const COMMAND_MAX_SIZE: usize = 48;
29
30const BSL: &str = include_str!("../assets/MSP430_BSL.00.06.05.34.txt");
31const BSL_VERSION: [u8; 4] = [0, 0x06, 0x05, 0x34];
32const BSL_START_ADDR: [u8; 3] = three_bytes(0x2504);
33
34const CMD_RX_DATA_BLOCK_FAST: u8 = 0x1b;
35const CMD_RX_PASSWORD: u8 = 0x11;
36const CMD_LOAD_PC: u8 = 0x17;
37const CMD_TX_BSL_VERSION: u8 = 0x19;
38
39const fn three_bytes(x: usize) -> [u8; 3] {
40    let temp = x.to_le_bytes();
41    [temp[0], temp[1], temp[2]]
42}
43
44type Result<T, E = Error> = std::result::Result<T, E>;
45
46#[derive(Error, Debug)]
47/// Errors for MSP430F5503
48pub enum Error {
49    #[error("Failed to Write: {0}")]
50    FailedToWrite(String),
51    #[error("Failed to Read: {0}")]
52    FailedToRead(String),
53    #[error("Failed to open MSP430: {0}")]
54    FailedToOpenDestination(String),
55    #[error("Firmware is not valid")]
56    InvalidFirmware,
57}
58
59struct MSP430(hidapi::HidDevice);
60
61impl MSP430 {
62    fn request(cmd: u8, data: &[u8]) -> Vec<u8> {
63        [USB_MSG_HEADER, (data.len() + 1) as u8, cmd]
64            .into_iter()
65            .chain(data.iter().cloned())
66            .collect()
67    }
68
69    fn cmd_no_resp(&self, cmd: u8, data: &[u8]) -> Result<()> {
70        let req = Self::request(cmd, data);
71
72        self.0
73            .write(&req)
74            .map(|_| ())
75            .map_err(|e| Error::FailedToWrite(e.to_string()))
76    }
77
78    fn cmd(&self, cmd: u8, data: &[u8]) -> Result<Vec<u8>> {
79        let mut ans = [0u8; 256];
80
81        let req = Self::request(cmd, data);
82        self.0
83            .write(&req)
84            .map_err(|e| Error::FailedToWrite(e.to_string()))?;
85
86        let _ = self
87            .0
88            .read(&mut ans)
89            .map_err(|e| Error::FailedToRead(e.to_string()))?;
90
91        assert_eq!(ans[0], USB_MSG_HEADER);
92        let length = ans[1];
93
94        Ok(ans[2..(2 + length as usize)].to_vec())
95    }
96
97    fn mass_erase(&self) -> Result<()> {
98        let ans = self.cmd(
99            CMD_RX_PASSWORD,
100            &[
101                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
102                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
103                0xff, 0xff, 0, 0,
104            ],
105        )?;
106
107        assert_eq!(ans.len(), 2);
108        assert_ne!(ans[1], 0);
109
110        Ok(())
111    }
112
113    fn unlock(&self) -> Result<()> {
114        let ans = self.cmd(CMD_RX_PASSWORD, &[0xffu8; 32])?;
115
116        assert_eq!(ans.len(), 2);
117        assert_eq!(ans[1], 0);
118
119        Ok(())
120    }
121
122    fn load_pc(&self) -> Result<()> {
123        self.cmd_no_resp(CMD_LOAD_PC, &BSL_START_ADDR)
124    }
125
126    fn bsl_version(&self) -> Result<()> {
127        let resp = self.cmd(CMD_TX_BSL_VERSION, &[])?;
128
129        assert_eq!(resp[0], 0x3a);
130        assert_eq!(resp[1..], BSL_VERSION);
131
132        Ok(())
133    }
134
135    fn rx_data_block_fast(&self, addr: usize, block: &[u8]) -> Result<usize> {
136        let bytes_to_write = std::cmp::min(block.len(), COMMAND_MAX_SIZE);
137
138        let addr = three_bytes(addr);
139        let data: Vec<u8> = addr
140            .into_iter()
141            .chain(block[..bytes_to_write].iter().cloned())
142            .collect();
143
144        self.cmd_no_resp(CMD_RX_DATA_BLOCK_FAST, &data)?;
145
146        Ok(bytes_to_write)
147    }
148
149    fn load_binfile(&self, bin: &bin_file::BinFile) -> Result<()> {
150        for (start_address, data) in bin.segments_list() {
151            tracing::debug!(
152                "Start Address: {}, Data: {:?}, Data Len: {}",
153                start_address,
154                data,
155                data.len()
156            );
157            let mut offset = 0;
158            // assert!(data.len() % 2 == 0);
159            while offset < data.len() {
160                offset += self.rx_data_block_fast(start_address + offset, &data[offset..])?;
161            }
162        }
163
164        Ok(())
165    }
166}
167
168fn load_bsl(dst: &std::ffi::CStr) -> Result<()> {
169    let msp430 = MSP430(open_hidraw(dst)?);
170
171    tracing::info!("Mass Erase");
172    msp430.mass_erase()?;
173
174    std::thread::sleep(Duration::from_secs(1));
175
176    tracing::info!("Unlock");
177    msp430.unlock()?;
178
179    let bin = BSL.parse().expect("Failed to parse MSP430 BSL");
180    tracing::info!("BSL: {}", bin);
181
182    tracing::info!("Load BSL");
183    msp430.load_binfile(&bin)?;
184
185    tracing::info!("Load PC");
186    msp430.load_pc()?;
187
188    Ok(())
189}
190
191/// Flash MSP430 in BeagleConnect Freedom. Provides optional progress.
192///
193/// # Firmware
194///
195/// Firmware type is auto detected. Supported firmwares:
196///
197/// - Raw binary
198/// - Ti-TXT
199/// - Intel Hex
200///
201/// No abort mechanism is provided here since the time taken to flash is ~2 secs. So aborting is
202/// not much useful other than stress tests.
203pub fn flash(
204    firmware: &[u8],
205    dst: &std::ffi::CStr,
206    mut chan: Option<mpsc::Sender<Status>>,
207) -> Result<()> {
208    let firmware_bin = parse_bin(firmware).map_err(|_| Error::InvalidFirmware)?;
209
210    chan_send(chan.as_mut(), Status::Preparing);
211
212    load_bsl(dst)?;
213
214    std::thread::sleep(Duration::from_secs(1));
215
216    chan_send(chan.as_mut(), Status::Flashing(0.5));
217
218    let msp430 = MSP430(open_hidraw(dst)?);
219
220    tracing::info!("Get BSL Version");
221    msp430.bsl_version()?;
222    tracing::info!("Flashing");
223    msp430.load_binfile(&firmware_bin)?;
224
225    Ok(())
226}
227
228/// Returns all paths to ports having BeagleConnect Freedom.
229pub fn devices() -> std::collections::HashSet<CString> {
230    hidapi::HidApi::new()
231        .expect("Failed to create hidapi context")
232        .device_list()
233        .filter(|x| x.vendor_id() == VID && x.product_id() == PID)
234        .map(|x| x.path().to_owned())
235        .collect()
236}
237
238fn open_hidraw(dst: &std::ffi::CStr) -> Result<hidapi::HidDevice> {
239    hidapi::HidApi::new()
240        .map_err(|e| Error::FailedToOpenDestination(e.to_string()))?
241        .open_path(dst)
242        .map_err(|e| Error::FailedToOpenDestination(e.to_string()))
243}