1#[macro_use]
2extern crate log;
3
4use std::{
5 fs::File,
6 io::*,
7 path::PathBuf,
8 sync::{
9 Arc,
10 atomic::{AtomicBool, Ordering},
11 },
12 thread,
13 time::{Duration, Instant},
14};
15
16mod crc;
17mod ymodem;
18
19macro_rules! dbg {
20 ($($arg:tt)*) => {{
21 debug!("$ {}", &std::fmt::format(format_args!($($arg)*)));
22 }};
23}
24
25const CTRL_C: u8 = 0x03;
26const INT_STR: &str = "<INTERRUPT>";
27const INT: &[u8] = INT_STR.as_bytes();
28
29pub struct UbootShell {
30 pub tx: Option<Box<dyn Write + Send>>,
31 pub rx: Option<Box<dyn Read + Send>>,
32 perfix: String,
33}
34
35impl UbootShell {
36 pub fn new(tx: impl Write + Send + 'static, rx: impl Read + Send + 'static) -> Result<Self> {
38 let mut s = Self {
39 tx: Some(Box::new(tx)),
40 rx: Some(Box::new(rx)),
41 perfix: "".to_string(),
42 };
43 s.wait_for_shell()?;
44 debug!("shell ready, perfix: `{}`", s.perfix);
45 Ok(s)
46 }
47
48 fn rx(&mut self) -> &mut Box<dyn Read + Send> {
49 self.rx.as_mut().unwrap()
50 }
51
52 fn tx(&mut self) -> &mut Box<dyn Write + Send> {
53 self.tx.as_mut().unwrap()
54 }
55
56 fn wait_for_interrupt(&mut self) -> Result<Vec<u8>> {
57 let mut tx = self.tx.take().unwrap();
58
59 let ok = Arc::new(AtomicBool::new(false));
60
61 let tx_handle = thread::spawn({
62 let ok = ok.clone();
63 move || {
64 while !ok.load(Ordering::Acquire) {
65 let _ = tx.write_all(&[CTRL_C]);
66 thread::sleep(Duration::from_millis(20));
67 }
68 tx
69 }
70 });
71 let mut history: Vec<u8> = Vec::new();
72 let mut interrupt_line: Vec<u8> = Vec::new();
73 debug!("wait for interrupt");
74 loop {
75 match self.read_byte() {
76 Ok(ch) => {
77 history.push(ch);
78
79 if history.last() == Some(&b'\n') {
80 let line = history.trim_ascii_end();
81 dbg!("{}", String::from_utf8_lossy(line));
82 let it = line.ends_with(INT);
83 if it {
84 interrupt_line.extend_from_slice(line);
85 }
86 history.clear();
87 if it {
88 ok.store(true, Ordering::Release);
89 break;
90 }
91 }
92 }
93
94 Err(ref e) if e.kind() == ErrorKind::TimedOut => {
95 continue;
96 }
97 Err(e) => {
98 return Err(e);
99 }
100 }
101 }
102
103 self.tx = Some(tx_handle.join().unwrap());
104
105 Ok(interrupt_line)
106 }
107
108 fn clear_shell(&mut self) -> Result<()> {
109 let _ = self.read_to_end(&mut vec![]);
110 Ok(())
111 }
112
113 fn wait_for_shell(&mut self) -> Result<()> {
114 let mut line = self.wait_for_interrupt()?;
115 debug!("got {}", String::from_utf8_lossy(&line));
116 line.resize(line.len() - INT.len(), 0);
117 self.perfix = String::from_utf8_lossy(&line).to_string();
118 self.clear_shell()?;
119 Ok(())
120 }
121
122 fn read_byte(&mut self) -> Result<u8> {
123 let mut buff = [0u8; 1];
124 let time_out = Duration::from_secs(5);
125 let start = Instant::now();
126
127 loop {
128 match self.rx().read_exact(&mut buff) {
129 Ok(_) => return Ok(buff[0]),
130 Err(e) => {
131 if e.kind() == ErrorKind::TimedOut {
132 if start.elapsed() > time_out {
133 return Err(std::io::Error::new(
134 std::io::ErrorKind::TimedOut,
135 "Timeout",
136 ));
137 }
138 } else {
139 return Err(e);
140 }
141 }
142 }
143 }
144 }
145
146 pub fn wait_for_reply(&mut self, val: &str) -> Result<String> {
147 let mut reply = Vec::new();
148 let mut display = Vec::new();
149 debug!("wait for `{}`", val);
150 loop {
151 let byte = self.read_byte()?;
152 reply.push(byte);
153 display.push(byte);
154 if byte == b'\n' {
155 dbg!("{}", String::from_utf8_lossy(&display).trim_end());
156 display.clear();
157 }
158
159 if reply.ends_with(val.as_bytes()) {
160 dbg!("{}", String::from_utf8_lossy(&display).trim_end());
161 break;
162 }
163 }
164 Ok(String::from_utf8_lossy(&reply)
165 .trim()
166 .trim_end_matches(&self.perfix)
167 .to_string())
168 }
169
170 pub fn cmd_without_reply(&mut self, cmd: &str) -> Result<()> {
171 self.tx().write_all(cmd.as_bytes())?;
172 self.tx().write_all("\n".as_bytes())?;
173 Ok(())
177 }
178
179 fn _cmd(&mut self, cmd: &str) -> Result<String> {
180 let _ = self.read_to_end(&mut vec![]);
181 let ok_str = "cmd-ok";
182 let cmd_with_id = format!("{cmd}&& echo {ok_str}");
183 self.cmd_without_reply(&cmd_with_id)?;
184 let perfix = self.perfix.clone();
185 let res = self
186 .wait_for_reply(&perfix)?
187 .trim_end()
188 .trim_end_matches(self.perfix.as_str().trim())
189 .trim_end()
190 .to_string();
191 if res.ends_with(ok_str) {
192 let res = res
193 .trim()
194 .trim_end_matches(ok_str)
195 .trim_end()
196 .trim_start_matches(&cmd_with_id)
197 .trim()
198 .to_string();
199 Ok(res)
200 } else {
201 Err(Error::other(format!(
202 "command `{cmd}` failed, response: {res}",
203 )))
204 }
205 }
206
207 pub fn cmd(&mut self, cmd: &str) -> Result<String> {
208 info!("cmd: {cmd}");
209 let mut retry = 3;
210 while retry > 0 {
211 match self._cmd(cmd) {
212 Ok(res) => return Ok(res),
213 Err(e) => {
214 warn!("cmd `{}` failed: {}, retrying...", cmd, e);
215 retry -= 1;
216 thread::sleep(Duration::from_millis(100));
217 }
218 }
219 }
220 Err(Error::other(format!(
221 "command `{cmd}` failed after retries",
222 )))
223 }
224
225 pub fn set_env(&mut self, name: impl Into<String>, value: impl Into<String>) -> Result<()> {
226 self.cmd(&format!("setenv {} {}", name.into(), value.into()))?;
227 Ok(())
228 }
229
230 pub fn env(&mut self, name: impl Into<String>) -> Result<String> {
231 let name = name.into();
232 let s = self.cmd(&format!("echo ${}", name))?;
233 let sp = s
234 .split("\n")
235 .filter(|s| !s.trim().is_empty())
236 .collect::<Vec<_>>();
237 let s = sp
238 .last()
239 .ok_or(Error::new(
240 ErrorKind::NotFound,
241 format!("env {} not found", name),
242 ))?
243 .to_string();
244 Ok(s)
245 }
246
247 pub fn env_int(&mut self, name: impl Into<String>) -> Result<usize> {
248 let name = name.into();
249 let line = self.env(&name)?;
250 debug!("env {name} = {line}");
251
252 parse_int(&line).ok_or(Error::new(
253 ErrorKind::InvalidData,
254 format!("env {name} is not a number"),
255 ))
256 }
257
258 pub fn loady(
259 &mut self,
260 addr: usize,
261 file: impl Into<PathBuf>,
262 on_progress: impl Fn(usize, usize),
263 ) -> Result<String> {
264 self.cmd_without_reply(&format!("loady {:#x}", addr,))?;
265 let crc = self.wait_for_load_crc()?;
266 let mut p = ymodem::Ymodem::new(crc);
267
268 let file = file.into();
269 let name = file.file_name().unwrap().to_str().unwrap();
270
271 let mut file = File::open(&file).unwrap();
272
273 let size = file.metadata().unwrap().len() as usize;
274
275 p.send(self, &mut file, name, size, |p| {
276 on_progress(p, size);
277 })?;
278 let perfix = self.perfix.clone();
279 self.wait_for_reply(&perfix)
280 }
281
282 fn wait_for_load_crc(&mut self) -> Result<bool> {
283 let mut reply = Vec::new();
284 loop {
285 let byte = self.read_byte()?;
286 reply.push(byte);
287 print_raw(&[byte]);
288
289 if reply.ends_with(b"C") {
290 return Ok(true);
291 }
292 let res = String::from_utf8_lossy(&reply);
293 if res.contains("try 'help'") {
294 panic!("{}", res);
295 }
296 }
297 }
298}
299
300impl Read for UbootShell {
301 fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
302 self.rx().read(buf)
303 }
304}
305
306impl Write for UbootShell {
307 fn write(&mut self, buf: &[u8]) -> Result<usize> {
308 self.tx().write(buf)
309 }
310
311 fn flush(&mut self) -> Result<()> {
312 self.tx().flush()
313 }
314}
315
316fn parse_int(line: &str) -> Option<usize> {
317 let mut line = line.trim();
318 let mut radix = 10;
319 if line.starts_with("0x") {
320 line = &line[2..];
321 radix = 16;
322 }
323 u64::from_str_radix(line, radix).ok().map(|o| o as _)
324}
325
326fn print_raw(buff: &[u8]) {
327 #[cfg(target_os = "windows")]
328 print_raw_win(buff);
329 #[cfg(not(target_os = "windows"))]
330 stdout().write_all(buff).unwrap();
331}
332
333#[cfg(target_os = "windows")]
334fn print_raw_win(buff: &[u8]) {
335 use std::sync::Mutex;
336 static PRINT_BUFF: Mutex<Vec<u8>> = Mutex::new(Vec::new());
337
338 let mut g = PRINT_BUFF.lock().unwrap();
339
340 g.extend_from_slice(buff);
341
342 if g.ends_with(b"\n") {
343 let s = String::from_utf8_lossy(&g[..]);
344 println!("{}", s.trim());
345 g.clear();
346 }
347}