sftool_lib/common/
ram_command.rs1use crate::{Error, Result};
2use serialport::SerialPort;
3use std::io::{Read, Write};
4use std::str::FromStr;
5use strum::{Display, EnumString};
6
7#[derive(EnumString, Display, Debug, Clone, PartialEq, Eq)]
9pub enum Command {
10 #[strum(to_string = "burn_erase_all 0x{address:08x}\r")]
11 EraseAll { address: u32 },
12
13 #[strum(to_string = "burn_verify 0x{address:08x} 0x{len:08x} 0x{crc:08x}\r")]
14 Verify { address: u32, len: u32, crc: u32 },
15
16 #[strum(to_string = "burn_erase 0x{address:08x} 0x{len:08x}\r")]
17 Erase { address: u32, len: u32 },
18
19 #[strum(to_string = "burn_erase_write 0x{address:08x} 0x{len:08x}\r")]
20 WriteAndErase { address: u32, len: u32 },
21
22 #[strum(to_string = "burn_write 0x{address:08x} 0x{len:08x}\r")]
23 Write { address: u32, len: u32 },
24
25 #[strum(to_string = "burn_read 0x{address:08x} 0x{len:08x}\r")]
26 Read { address: u32, len: u32 },
27
28 #[strum(to_string = "burn_reset\r")]
29 SoftReset,
30
31 #[strum(to_string = "burn_speed {baud} {delay}\r")]
32 SetBaud { baud: u32, delay: u32 },
33}
34
35#[derive(EnumString, Display, Debug, Clone, PartialEq, Eq)]
37pub enum Response {
38 #[strum(serialize = "OK")]
39 Ok,
40 #[strum(serialize = "Fail")]
41 Fail,
42 #[strum(serialize = "RX_WAIT")]
43 RxWait,
44}
45
46pub const RESPONSE_STR_TABLE: [&str; 3] = ["OK", "Fail", "RX_WAIT"];
48
49pub trait RamCommand {
51 fn command(&mut self, cmd: Command) -> Result<Response>;
52 fn send_data(&mut self, data: &[u8]) -> Result<Response>;
53 fn format_command(&self, cmd: &Command) -> String {
54 cmd.to_string()
55 }
56}
57
58pub trait DownloadStub {
60 fn download_stub(&mut self) -> Result<()>;
61}
62
63pub struct CommandConfig {
65 pub compat_mode: bool,
66 pub chunk_size: usize,
67 pub chunk_delay_ms: u64,
68}
69
70impl Default for CommandConfig {
71 fn default() -> Self {
72 Self {
73 compat_mode: false,
74 chunk_size: 256,
75 chunk_delay_ms: 10,
76 }
77 }
78}
79
80pub struct RamOps;
82
83impl RamOps {
84 const DEFAULT_TIMEOUT_MS: u128 = 4000;
85 const ERASE_ALL_TIMEOUT_MS: u128 = 30 * 1000;
86
87 pub fn send_command_and_wait_response(
89 port: &mut Box<dyn SerialPort>,
90 cmd: Command,
91 command_str: &str,
92 memory_type: &str,
93 ) -> Result<Response> {
94 tracing::debug!("command: {:?}", cmd);
95
96 port.write_all(command_str.as_bytes())?;
98 port.flush()?;
99 let timeout = match cmd {
105 Command::EraseAll { .. } => Self::ERASE_ALL_TIMEOUT_MS,
106 _ => Self::DEFAULT_TIMEOUT_MS,
107 };
108 let timeout = if memory_type == "sd" {
109 timeout * 3
110 } else {
111 timeout
112 };
113
114 match cmd {
116 Command::SetBaud { .. } | Command::Read { .. } | Command::Erase { .. } => {
117 return Ok(Response::Ok);
118 }
119 _ => (),
120 }
121
122 Self::wait_for_response(port, timeout)
123 }
124
125 pub fn send_data_and_wait_response(
127 port: &mut Box<dyn SerialPort>,
128 data: &[u8],
129 config: &CommandConfig,
130 ) -> Result<Response> {
131 if !config.compat_mode {
133 port.write_all(data)?;
134 port.flush()?;
135 } else {
136 for chunk in data.chunks(config.chunk_size) {
138 port.write_all(chunk)?;
139 port.flush()?;
140 std::thread::sleep(std::time::Duration::from_millis(config.chunk_delay_ms));
141 }
142 }
143
144 Self::wait_for_response(port, Self::DEFAULT_TIMEOUT_MS)
145 }
146
147 fn wait_for_response(port: &mut Box<dyn SerialPort>, timeout_ms: u128) -> Result<Response> {
149 let mut buffer = Vec::new();
150 let now = std::time::SystemTime::now();
151
152 loop {
153 let elapsed = now.elapsed().unwrap().as_millis();
154 if elapsed > timeout_ms {
155 tracing::debug!("Response buffer: {:?}", String::from_utf8_lossy(&buffer));
156 return Err(Error::timeout("waiting for RAM command response"));
157 }
158
159 let mut byte = [0];
160 let ret = port.read_exact(&mut byte);
161 if ret.is_err() {
162 continue;
163 }
164 buffer.push(byte[0]);
165
166 for response_str in RESPONSE_STR_TABLE.iter() {
168 let response_bytes = response_str.as_bytes();
169 let exists = buffer
170 .windows(response_bytes.len())
171 .any(|window| window == response_bytes);
172 if exists {
173 tracing::debug!("Response buffer: {:?}", String::from_utf8_lossy(&buffer));
174 return Response::from_str(response_str)
175 .map_err(|e| Error::invalid_input(e.to_string()));
176 }
177 }
178 }
179 }
180
181 pub fn wait_for_shell_prompt(
183 port: &mut Box<dyn SerialPort>,
184 prompt: &[u8],
185 retry_interval_ms: u64,
186 max_retries: u32,
187 ) -> Result<()> {
188 let mut buffer = Vec::new();
189 let mut now = std::time::SystemTime::now();
190 let mut retry_count = 0;
191
192 port.write_all(b"\r\n")?;
194 port.flush()?;
195
196 loop {
197 let elapsed = now.elapsed().unwrap().as_millis();
198 if elapsed > retry_interval_ms as u128 {
199 tracing::warn!(
200 "Wait for shell Failed, retry. buffer: {:?}",
201 String::from_utf8_lossy(&buffer)
202 );
203 port.clear(serialport::ClearBuffer::All)?;
204 tracing::debug!("Retrying to find shell prompt...");
205 std::thread::sleep(std::time::Duration::from_millis(100));
206 retry_count += 1;
207 now = std::time::SystemTime::now();
208 port.write_all(b"\r\n")?;
209 port.flush()?;
210 buffer.clear();
211 }
212
213 if retry_count > max_retries {
214 return Err(Error::timeout("waiting for shell prompt"));
215 }
216
217 let mut byte = [0];
218 let ret = port.read_exact(&mut byte);
219 if ret.is_err() {
220 continue;
221 }
222 buffer.push(byte[0]);
223
224 if buffer.windows(prompt.len()).any(|window| window == prompt) {
226 break;
227 }
228 }
229
230 Ok(())
231 }
232}