Skip to main content

sftool_lib/common/
read_flash.rs

1use crate::common::ram_command::{Command, RamCommand};
2use crate::common::serial_io::{SerialIo, for_tool};
3use crate::progress::{ProgressHandle, ProgressOperation, ProgressStatus};
4use crate::utils::Utils;
5use crate::{Error, Result, SifliToolTrait};
6use crc::{Algorithm, Crc};
7use std::fs::File;
8use std::io::{Seek, Write};
9use std::time::Duration;
10use tempfile::tempfile;
11
12/// 通用的Flash读取文件结构
13#[derive(Debug)]
14pub struct ReadFlashFile {
15    pub file_path: String,
16    pub address: u32,
17    pub size: u32,
18}
19
20/// 通用的Flash读取操作实现
21pub struct FlashReader;
22
23impl FlashReader {
24    const START_TRANS_MARKER: &'static [u8] = b"start_trans\r\n";
25    const READ_TIMEOUT_MS: u128 = 10_000;
26    const READ_CHUNK_SIZE: usize = 16 * 1024;
27    const CRC_32_ALGO: Algorithm<u32> = Algorithm {
28        width: 32,
29        poly: 0x04C11DB7,
30        init: 0,
31        refin: true,
32        refout: true,
33        xorout: 0,
34        check: 0,
35        residue: 0,
36    };
37
38    /// 解析读取文件信息 (filename@address:size格式)
39    pub fn parse_file_info(file_spec: &str) -> Result<ReadFlashFile> {
40        let Some((file_path, addr_size)) = file_spec.split_once('@') else {
41            return Err(Error::invalid_input(format!(
42                "Invalid format: {}. Expected: filename@address:size",
43                file_spec
44            )));
45        };
46        let Some((addr, size)) = addr_size.split_once(':') else {
47            return Err(Error::invalid_input(format!(
48                "Invalid format: {}. Expected: filename@address:size",
49                file_spec
50            )));
51        };
52
53        let address = Utils::str_to_u32(addr)
54            .map_err(|e| Error::invalid_input(format!("Invalid address '{}': {}", addr, e)))?;
55        let size = Utils::str_to_u32(size)
56            .map_err(|e| Error::invalid_input(format!("Invalid size '{}': {}", size, e)))?;
57
58        Ok(ReadFlashFile {
59            file_path: file_path.to_string(),
60            address,
61            size,
62        })
63    }
64
65    /// 从Flash读取数据的通用实现
66    pub fn read_flash_data<T>(
67        tool: &mut T,
68        address: u32,
69        size: u32,
70        output_path: &str,
71    ) -> Result<()>
72    where
73        T: SifliToolTrait + RamCommand,
74    {
75        tool.check_cancelled()?;
76        let progress = tool.progress();
77        let progress_bar =
78            progress.create_bar(size as u64, ProgressOperation::ReadFlash { address, size });
79
80        let mut temp_file = tempfile()?;
81
82        // 读取一次即可,由设备负责连续发送数据
83        tool.command(Command::Read { address, len: size })?;
84
85        let (expected_crc, actual_crc) = {
86            let mut io = for_tool(tool);
87
88            Self::wait_for_marker(&mut io, Self::START_TRANS_MARKER, "start_trans marker")?;
89
90            let actual_crc =
91                Self::receive_payload(&mut io, size, &mut temp_file, &progress_bar, address)?;
92
93            let expected_crc = Self::read_crc_value(&mut io)?;
94            Self::expect_ok(&mut io)?;
95
96            (expected_crc, actual_crc)
97        };
98
99        if actual_crc != expected_crc {
100            return Err(Error::CrcMismatch {
101                expected: expected_crc,
102                actual: actual_crc,
103            });
104        }
105
106        progress_bar.finish(ProgressStatus::Success);
107
108        temp_file.seek(std::io::SeekFrom::Start(0))?;
109        let mut output_file = File::create(output_path)?;
110        std::io::copy(&mut temp_file, &mut output_file)?;
111
112        Ok(())
113    }
114
115    fn wait_for_marker(io: &mut SerialIo<'_>, marker: &[u8], context: &str) -> Result<()> {
116        io.wait_for_pattern(
117            marker,
118            Duration::from_millis(Self::READ_TIMEOUT_MS as u64),
119            context,
120        )?;
121        Ok(())
122    }
123
124    fn receive_payload(
125        io: &mut SerialIo<'_>,
126        size: u32,
127        temp_file: &mut File,
128        progress_bar: &ProgressHandle,
129        address: u32,
130    ) -> Result<u32> {
131        let mut remaining = size as usize;
132        let buffer_len = remaining.clamp(1usize, Self::READ_CHUNK_SIZE);
133        let mut buffer = vec![0u8; buffer_len];
134
135        let crc = Crc::<u32>::new(&Self::CRC_32_ALGO);
136        let mut digest = crc.digest();
137        let mut processed = 0usize;
138
139        while remaining > 0 {
140            io.check_cancelled()?;
141            let chunk_len = std::cmp::min(buffer.len(), remaining);
142            let chunk = &mut buffer[..chunk_len];
143            let current_address = address.saturating_add(processed as u32);
144            io.read_exact_with_timeout(
145                chunk,
146                Duration::from_millis(Self::READ_TIMEOUT_MS as u64),
147                &format!("reading flash at 0x{:08X}", current_address),
148            )?;
149
150            temp_file.write_all(chunk)?;
151            digest.update(chunk);
152
153            remaining -= chunk_len;
154            processed += chunk_len;
155            progress_bar.inc(chunk_len as u64);
156        }
157
158        Ok(digest.finalize())
159    }
160
161    fn read_crc_value(io: &mut SerialIo<'_>) -> Result<u32> {
162        let line = Self::read_non_empty_line(io, "CRC response")?;
163        let lower = line.to_ascii_lowercase();
164        let prefix = "crc:0x";
165
166        if !lower.starts_with(prefix) {
167            return Err(Error::protocol(format!("unexpected CRC line: {}", line)));
168        }
169
170        let hex_part = &line[prefix.len()..];
171        u32::from_str_radix(hex_part, 16)
172            .map_err(|e| Error::protocol(format!("invalid CRC '{}': {}", line, e)))
173    }
174
175    fn expect_ok(io: &mut SerialIo<'_>) -> Result<()> {
176        let line = Self::read_non_empty_line(io, "OK response")?;
177        if line != "OK" {
178            return Err(Error::protocol(format!("unexpected response: {}", line)));
179        }
180        Ok(())
181    }
182
183    fn read_non_empty_line(io: &mut SerialIo<'_>, context: &str) -> Result<String> {
184        loop {
185            let line = Self::read_line(io, context)?;
186            let trimmed = line.trim().to_string();
187            if trimmed.is_empty() {
188                continue;
189            }
190            return Ok(trimmed);
191        }
192    }
193
194    fn read_line(io: &mut SerialIo<'_>, context: &str) -> Result<String> {
195        io.read_line_with_timeout(Duration::from_millis(Self::READ_TIMEOUT_MS as u64), context)
196    }
197}