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 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}