sftool_lib/common/
ram_command.rs

1use serialport::SerialPort;
2use std::io::{Read, Write};
3use std::str::FromStr;
4use strum::{Display, EnumString};
5
6/// 通用的RAM命令枚举,可在不同芯片间复用
7#[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/// 通用的命令响应枚举
35#[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
45/// 响应字符串查找表
46pub const RESPONSE_STR_TABLE: [&str; 3] = ["OK", "Fail", "RX_WAIT"];
47
48/// RAM命令处理trait,定义了发送命令和数据的接口
49pub 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
54/// Stub下载trait,定义了下载stub的接口
55pub trait DownloadStub {
56    fn download_stub(&mut self) -> Result<(), std::io::Error>;
57}
58
59/// 命令处理的配置参数
60pub 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
76/// 通用的RAM操作处理器,包含可复用的逻辑
77pub struct RamOps;
78
79impl RamOps {
80    const DEFAULT_TIMEOUT_MS: u128 = 4000;
81    const ERASE_ALL_TIMEOUT_MS: u128 = 30 * 1000;
82
83    /// 发送命令并等待响应的通用实现
84    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        // 发送命令
92        port.write_all(cmd.to_string().as_bytes())?;
93        port.flush()?;
94        // 在macOS上,FTDI的驱动似乎不高兴我们清除输入缓冲区,这可能会导致后续要发送的内容被截断
95        // 因此这个地方我们不再需要清理缓冲区,应该在后续的操作中滤除掉额外的信息
96        // port.clear(serialport::ClearBuffer::All)?;
97
98        // 确定超时时间
99        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        // 某些命令直接返回成功,不等待响应
110        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    /// 发送数据并等待响应的通用实现
121    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        // 根据配置发送数据
127        if !config.compat_mode {
128            port.write_all(data)?;
129            port.flush()?;
130        } else {
131            // 兼容模式:分块发送
132            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    /// 等待响应的通用实现
143    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            // 检查是否收到预期的响应
165            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    /// 等待shell提示符的通用实现
181    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        // 发送初始的回车换行
192        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            // 检查是否收到shell提示符
224            if buffer.windows(prompt.len()).any(|window| window == prompt) {
225                break;
226            }
227        }
228
229        Ok(())
230    }
231}