uboot_shell/
lib.rs

1use std::{
2    fs::File,
3    io::*,
4    os::unix::fs::MetadataExt,
5    path::PathBuf,
6    time::{Duration, Instant},
7};
8
9mod crc;
10mod ymodem;
11
12pub struct UbootShell {
13    pub tx: Option<Box<dyn Write + Send>>,
14    pub rx: Option<Box<dyn Read + Send>>,
15}
16
17impl UbootShell {
18    /// Create a new UbootShell instance, block wait for uboot shell.
19    pub fn new(tx: impl Write + Send + 'static, rx: impl Read + Send + 'static) -> Result<Self> {
20        let mut s = Self {
21            tx: Some(Box::new(tx)),
22            rx: Some(Box::new(rx)),
23        };
24        s.wait_for_shell()?;
25        Ok(s)
26    }
27
28    fn rx(&mut self) -> &mut Box<dyn Read + Send> {
29        self.rx.as_mut().unwrap()
30    }
31
32    fn tx(&mut self) -> &mut Box<dyn Write + Send> {
33        self.tx.as_mut().unwrap()
34    }
35
36    fn wait_for_shell(&mut self) -> Result<()> {
37        let mut buf = [0u8; 1];
38        let mut history: Vec<u8> = Vec::new();
39        const CTRL_C: u8 = 0x03;
40
41        let mut last = Instant::now();
42
43        loop {
44            match self.rx().read(&mut buf) {
45                Ok(n) => {
46                    if n == 1 {
47                        let ch = buf[0];
48                        if ch == b'\n' && history.last() != Some(&b'\r') {
49                            stdout().write_all(b"\r").unwrap();
50                            history.push(b'\r');
51                        }
52                        history.push(ch);
53
54                        stdout().write_all(&buf).unwrap();
55
56                        if history.ends_with(c"<INTERRUPT>".to_bytes()) {
57                            return Ok(());
58                        }
59
60                        if last.elapsed() > Duration::from_millis(20) {
61                            let _ = self.tx().write_all(&[CTRL_C]);
62                            last = Instant::now();
63                        }
64                    }
65                }
66
67                Err(ref e) if e.kind() == ErrorKind::TimedOut => {
68                    continue;
69                }
70                Err(e) => {
71                    return Err(e);
72                }
73            }
74        }
75    }
76
77    fn read_line(&mut self) -> Result<String> {
78        let mut line_raw = Vec::new();
79        let mut byte = [0; 1];
80
81        loop {
82            let n = self.rx().read(&mut byte)?;
83            if n == 0 {
84                break;
85            }
86
87            if byte[0] == b'\r' {
88                continue;
89            }
90
91            if byte[0] == b'\n' {
92                break;
93            }
94
95            line_raw.push(byte[0]);
96        }
97
98        if line_raw.is_empty() {
99            return Ok(String::new());
100        }
101
102        let line = String::from_utf8_lossy(&line_raw);
103        Ok(line.trim().to_string())
104    }
105
106    pub fn wait_for_reply(&mut self, val: &str) -> Result<String> {
107        let mut reply = Vec::new();
108        let mut buff = [0u8; 1];
109        loop {
110            self.rx().read_exact(&mut buff)?;
111            reply.push(buff[0]);
112            let _ = stdout().write_all(&buff);
113
114            if reply.ends_with(val.as_bytes()) {
115                break;
116            }
117        }
118        Ok(String::from_utf8_lossy(&reply).trim().to_string())
119    }
120
121    pub fn cmd_without_reply(&mut self, cmd: &str) -> Result<()> {
122        self.tx().write_all(cmd.as_bytes())?;
123        self.tx().write_all("\r\n".as_bytes())?;
124        self.tx().flush()?;
125        Ok(())
126    }
127
128    pub fn cmd(&mut self, cmd: &str) -> Result<String> {
129        self.cmd_without_reply(cmd)?;
130        let shell_start;
131        loop {
132            let line = self.read_line()?;
133            println!("{line}");
134            if line.contains(cmd) {
135                shell_start = line.trim().trim_end_matches(cmd).trim().to_string();
136                break;
137            }
138        }
139        Ok(self
140            .wait_for_reply(&shell_start)?
141            .trim_end_matches(&shell_start)
142            .to_string())
143    }
144
145    pub fn set_env(&mut self, name: impl Into<String>, value: impl Into<String>) -> Result<()> {
146        self.cmd(&format!("setenv {} {}", name.into(), value.into()))?;
147        Ok(())
148    }
149
150    pub fn env(&mut self, name: impl Into<String>) -> Result<String> {
151        let name = name.into();
152        let s = self.cmd(&format!("echo ${}", name))?;
153        if s.is_empty() {
154            return Err(Error::new(
155                ErrorKind::NotFound,
156                format!("env {} not found", name),
157            ));
158        }
159        Ok(s)
160    }
161
162    pub fn env_int(&mut self, name: impl Into<String>) -> Result<usize> {
163        let name = name.into();
164        let line = self.env(&name)?;
165        parse_int(&line).ok_or(Error::new(
166            ErrorKind::InvalidData,
167            format!("env {name} is not a number"),
168        ))
169    }
170
171    pub fn loady(
172        &mut self,
173        addr: usize,
174        file: impl Into<String>,
175        on_progress: impl Fn(usize, usize),
176    ) -> Result<()> {
177        self.cmd_without_reply(&format!("loady {:#x}", addr))?;
178
179        let mut p = ymodem::Ymodem::new();
180
181        let file = PathBuf::from(file.into());
182        let name = file.file_name().unwrap().to_str().unwrap();
183
184        let mut file = File::open(&file).unwrap();
185
186        let size = file.metadata().unwrap().size() as usize;
187
188        p.send(self, &mut file, name, size, |p| {
189            on_progress(p, size);
190        })?;
191
192        Ok(())
193    }
194}
195
196impl Read for UbootShell {
197    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
198        self.rx().read(buf)
199    }
200}
201
202impl Write for UbootShell {
203    fn write(&mut self, buf: &[u8]) -> Result<usize> {
204        self.tx().write(buf)
205    }
206
207    fn flush(&mut self) -> Result<()> {
208        self.tx().flush()
209    }
210}
211
212fn parse_int(line: &str) -> Option<usize> {
213    let mut line = line.trim();
214    let mut radix = 10;
215    if line.starts_with("0x") {
216        line = &line[2..];
217        radix = 16;
218    }
219    u64::from_str_radix(line, radix).ok().map(|o| o as _)
220}