uboot_shell/
lib.rs

1use std::{
2    fs::File,
3    io::*,
4    path::PathBuf,
5    sync::Mutex,
6    time::{Duration, Instant},
7};
8
9use colored::Colorize;
10
11mod crc;
12mod ymodem;
13
14macro_rules! trace {
15    ($($arg:tt)*) => {{
16        println!("\r\n{}", &std::fmt::format(format_args!($($arg)*)).bright_black());
17    }};
18}
19
20pub struct UbootShell {
21    pub tx: Option<Box<dyn Write + Send>>,
22    pub rx: Option<Box<dyn Read + Send>>,
23    perfix: String,
24}
25
26impl UbootShell {
27    /// Create a new UbootShell instance, block wait for uboot shell.
28    pub fn new(tx: impl Write + Send + 'static, rx: impl Read + Send + 'static) -> Result<Self> {
29        let mut s = Self {
30            tx: Some(Box::new(tx)),
31            rx: Some(Box::new(rx)),
32            perfix: "".to_string(),
33        };
34        s.wait_for_shell()?;
35        trace!("shell ready, perfix: `{}`", s.perfix);
36        Ok(s)
37    }
38
39    fn rx(&mut self) -> &mut Box<dyn Read + Send> {
40        self.rx.as_mut().unwrap()
41    }
42
43    fn tx(&mut self) -> &mut Box<dyn Write + Send> {
44        self.tx.as_mut().unwrap()
45    }
46
47    fn wait_for_shell(&mut self) -> Result<()> {
48        let mut history: Vec<u8> = Vec::new();
49        const CTRL_C: u8 = 0x03;
50
51        let mut last = Instant::now();
52        let mut is_interrupt_found = false;
53        let mut is_shell_ok = false;
54        const INT_STR: &str = "<INTERRUPT>";
55        const INT: &[u8] = INT_STR.as_bytes();
56
57        trace!("wait for `{INT_STR}`");
58        loop {
59            match self.read_byte() {
60                Ok(ch) => {
61                    if ch == b'\n' && history.last() != Some(&b'\r') {
62                        print_raw(b"\r");
63                        history.push(b'\r');
64                    }
65                    history.push(ch);
66
67                    print_raw(&[ch]);
68
69                    if history.ends_with(INT) && !is_interrupt_found {
70                        let line = history.split(|n| *n == b'\n').next_back().unwrap();
71                        let s = String::from_utf8_lossy(line);
72                        self.perfix = s.trim().replace(INT_STR, "").trim().to_string();
73                        is_interrupt_found = true;
74                        trace!("clear");
75                        let _ = self.rx().read_to_end(&mut vec![]);
76                        trace!("test shell");
77                        let ret = self.tx().write_all("testshell\r\n".as_bytes());
78                        trace!("test shell ret {:?}", ret);
79                    }
80
81                    if is_interrupt_found && history.ends_with("\'help\'".as_bytes()) {
82                        is_shell_ok = true;
83                    }
84
85                    if is_shell_ok && history.ends_with(self.perfix.as_bytes()) {
86                        return Ok(());
87                    }
88
89                    if last.elapsed() > Duration::from_millis(20) && !is_interrupt_found {
90                        let _ = self.tx().write_all(&[CTRL_C]);
91                        last = Instant::now();
92                    }
93                }
94
95                Err(ref e) if e.kind() == ErrorKind::TimedOut => {
96                    continue;
97                }
98                Err(e) => {
99                    return Err(e);
100                }
101            }
102        }
103    }
104
105    fn read_byte(&mut self) -> Result<u8> {
106        let mut buff = [0u8; 1];
107        let time_out = Duration::from_secs(5);
108        let start = Instant::now();
109
110        loop {
111            match self.rx().read_exact(&mut buff) {
112                Ok(_) => return Ok(buff[0]),
113                Err(e) => {
114                    if e.kind() == ErrorKind::TimedOut {
115                        if start.elapsed() > time_out {
116                            return Err(std::io::Error::new(
117                                std::io::ErrorKind::TimedOut,
118                                "Timeout",
119                            ));
120                        }
121                    } else {
122                        return Err(e);
123                    }
124                }
125            }
126        }
127    }
128
129    pub fn wait_for_reply(&mut self, val: &str) -> Result<String> {
130        let mut reply = Vec::new();
131
132        trace!("wait for `{}`", val);
133        loop {
134            let byte = self.read_byte()?;
135            reply.push(byte);
136            print_raw(&[byte]);
137
138            if reply.ends_with(val.as_bytes()) {
139                break;
140            }
141        }
142        Ok(String::from_utf8_lossy(&reply)
143            .trim()
144            .trim_end_matches(&self.perfix)
145            .to_string())
146    }
147
148    pub fn cmd_without_reply(&mut self, cmd: &str) -> Result<()> {
149        self.tx().write_all(cmd.as_bytes())?;
150        self.tx().write_all("\r\n".as_bytes())?;
151        self.tx().flush()?;
152        self.wait_for_reply(cmd)?;
153        trace!("cmd ok");
154        Ok(())
155    }
156
157    pub fn cmd(&mut self, cmd: &str) -> Result<String> {
158        self.cmd_without_reply(cmd)?;
159        let perfix = self.perfix.clone();
160        self.wait_for_reply(&perfix)
161    }
162
163    pub fn set_env(&mut self, name: impl Into<String>, value: impl Into<String>) -> Result<()> {
164        self.cmd(&format!("setenv {} {}", name.into(), value.into()))?;
165        Ok(())
166    }
167
168    pub fn env(&mut self, name: impl Into<String>) -> Result<String> {
169        let name = name.into();
170        let s = self.cmd(&format!("echo ${}", name))?;
171        if s.is_empty() {
172            return Err(Error::new(
173                ErrorKind::NotFound,
174                format!("env {} not found", name),
175            ));
176        }
177        Ok(s)
178    }
179
180    pub fn env_int(&mut self, name: impl Into<String>) -> Result<usize> {
181        let name = name.into();
182        let line = self.env(&name)?;
183        parse_int(&line).ok_or(Error::new(
184            ErrorKind::InvalidData,
185            format!("env {name} is not a number"),
186        ))
187    }
188
189    pub fn loady(
190        &mut self,
191        addr: usize,
192        file: impl Into<PathBuf>,
193        on_progress: impl Fn(usize, usize),
194    ) -> Result<String> {
195        self.cmd_without_reply(&format!("loady {:#x}", addr,))?;
196        let crc = self.wait_for_load_crc()?;
197        let mut p = ymodem::Ymodem::new(crc);
198
199        let file = file.into();
200        let name = file.file_name().unwrap().to_str().unwrap();
201
202        let mut file = File::open(&file).unwrap();
203
204        let size = file.metadata().unwrap().len() as usize;
205
206        p.send(self, &mut file, name, size, |p| {
207            on_progress(p, size);
208        })?;
209        let perfix = self.perfix.clone();
210        self.wait_for_reply(&perfix)
211    }
212
213    fn wait_for_load_crc(&mut self) -> Result<bool> {
214        let mut reply = Vec::new();
215        loop {
216            let byte = self.read_byte()?;
217            reply.push(byte);
218            print_raw(&[byte]);
219
220            if reply.ends_with(b"C") {
221                return Ok(true);
222            }
223            let res = String::from_utf8_lossy(&reply);
224            if res.contains("try 'help'") {
225                panic!("{}", res);
226            }
227        }
228    }
229}
230
231impl Read for UbootShell {
232    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
233        self.rx().read(buf)
234    }
235}
236
237impl Write for UbootShell {
238    fn write(&mut self, buf: &[u8]) -> Result<usize> {
239        self.tx().write(buf)
240    }
241
242    fn flush(&mut self) -> Result<()> {
243        self.tx().flush()
244    }
245}
246
247fn parse_int(line: &str) -> Option<usize> {
248    let mut line = line.trim();
249    let mut radix = 10;
250    if line.starts_with("0x") {
251        line = &line[2..];
252        radix = 16;
253    }
254    u64::from_str_radix(line, radix).ok().map(|o| o as _)
255}
256
257fn print_raw(buff: &[u8]) {
258    #[cfg(target_os = "windows")]
259    print_raw_win(buff);
260    #[cfg(not(target_os = "windows"))]
261    stdout().write_all(buff).unwrap();
262}
263
264fn print_raw_win(buff: &[u8]) {
265    static PRINT_BUFF: Mutex<Vec<u8>> = Mutex::new(Vec::new());
266
267    let mut g = PRINT_BUFF.lock().unwrap();
268
269    g.extend_from_slice(buff);
270
271    if g.ends_with(b"\n") {
272        let s = String::from_utf8_lossy(&g[..]);
273        println!("{}", s.trim());
274        g.clear();
275    }
276}