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