sftool_lib/common/
read_flash.rs1use 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#[derive(Debug)]
14pub struct ReadFlashFile {
15 pub file_path: String,
16 pub address: u32,
17 pub size: u32,
18}
19
20pub 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 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 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 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}