1use 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)]
47pub 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 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
191pub 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
228pub 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}