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