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}
54
55pub trait DownloadStub {
57 fn download_stub(&mut self) -> Result<()>;
58}
59
60pub struct CommandConfig {
62 pub compat_mode: bool,
63 pub chunk_size: usize,
64 pub chunk_delay_ms: u64,
65}
66
67impl Default for CommandConfig {
68 fn default() -> Self {
69 Self {
70 compat_mode: false,
71 chunk_size: 256,
72 chunk_delay_ms: 10,
73 }
74 }
75}
76
77pub struct RamOps;
79
80impl RamOps {
81 const DEFAULT_TIMEOUT_MS: u128 = 4000;
82 const ERASE_ALL_TIMEOUT_MS: u128 = 30 * 1000;
83
84 pub fn send_command_and_wait_response(
86 port: &mut Box<dyn SerialPort>,
87 cmd: Command,
88 memory_type: &str,
89 ) -> Result<Response> {
90 tracing::debug!("command: {:?}", cmd);
91
92 port.write_all(cmd.to_string().as_bytes())?;
94 port.flush()?;
95 let timeout = match cmd {
101 Command::EraseAll { .. } => Self::ERASE_ALL_TIMEOUT_MS,
102 _ => Self::DEFAULT_TIMEOUT_MS,
103 };
104 let timeout = if memory_type == "sd" {
105 timeout * 3
106 } else {
107 timeout
108 };
109
110 match cmd {
112 Command::SetBaud { .. } | Command::Read { .. } | Command::Erase { .. } => {
113 return Ok(Response::Ok);
114 }
115 _ => (),
116 }
117
118 Self::wait_for_response(port, timeout)
119 }
120
121 pub fn send_data_and_wait_response(
123 port: &mut Box<dyn SerialPort>,
124 data: &[u8],
125 config: &CommandConfig,
126 ) -> Result<Response> {
127 if !config.compat_mode {
129 port.write_all(data)?;
130 port.flush()?;
131 } else {
132 for chunk in data.chunks(config.chunk_size) {
134 port.write_all(chunk)?;
135 port.flush()?;
136 std::thread::sleep(std::time::Duration::from_millis(config.chunk_delay_ms));
137 }
138 }
139
140 Self::wait_for_response(port, Self::DEFAULT_TIMEOUT_MS)
141 }
142
143 fn wait_for_response(port: &mut Box<dyn SerialPort>, timeout_ms: u128) -> Result<Response> {
145 let mut buffer = Vec::new();
146 let now = std::time::SystemTime::now();
147
148 loop {
149 let elapsed = now.elapsed().unwrap().as_millis();
150 if elapsed > timeout_ms {
151 tracing::debug!("Response buffer: {:?}", String::from_utf8_lossy(&buffer));
152 return Err(Error::timeout("waiting for RAM command response"));
153 }
154
155 let mut byte = [0];
156 let ret = port.read_exact(&mut byte);
157 if ret.is_err() {
158 continue;
159 }
160 buffer.push(byte[0]);
161
162 for response_str in RESPONSE_STR_TABLE.iter() {
164 let response_bytes = response_str.as_bytes();
165 let exists = buffer
166 .windows(response_bytes.len())
167 .any(|window| window == response_bytes);
168 if exists {
169 tracing::debug!("Response buffer: {:?}", String::from_utf8_lossy(&buffer));
170 return Response::from_str(response_str)
171 .map_err(|e| Error::invalid_input(e.to_string()));
172 }
173 }
174 }
175 }
176
177 pub fn wait_for_shell_prompt(
179 port: &mut Box<dyn SerialPort>,
180 prompt: &[u8],
181 retry_interval_ms: u64,
182 max_retries: u32,
183 ) -> Result<()> {
184 let mut buffer = Vec::new();
185 let mut now = std::time::SystemTime::now();
186 let mut retry_count = 0;
187
188 port.write_all(b"\r\n")?;
190 port.flush()?;
191
192 loop {
193 let elapsed = now.elapsed().unwrap().as_millis();
194 if elapsed > retry_interval_ms as u128 {
195 tracing::warn!(
196 "Wait for shell Failed, retry. buffer: {:?}",
197 String::from_utf8_lossy(&buffer)
198 );
199 port.clear(serialport::ClearBuffer::All)?;
200 tracing::debug!("Retrying to find shell prompt...");
201 std::thread::sleep(std::time::Duration::from_millis(100));
202 retry_count += 1;
203 now = std::time::SystemTime::now();
204 port.write_all(b"\r\n")?;
205 port.flush()?;
206 buffer.clear();
207 }
208
209 if retry_count > max_retries {
210 return Err(Error::timeout("waiting for shell prompt"));
211 }
212
213 let mut byte = [0];
214 let ret = port.read_exact(&mut byte);
215 if ret.is_err() {
216 continue;
217 }
218 buffer.push(byte[0]);
219
220 if buffer.windows(prompt.len()).any(|window| window == prompt) {
222 break;
223 }
224 }
225
226 Ok(())
227 }
228}