tudelft_arm_qemu_runner/
lib.rs1use std::{
2 fs::File,
3 io::{Read, Write},
4 net::{TcpListener, TcpStream},
5 path::Path,
6 process::{Child, Command},
7};
8use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
9use std::process::Stdio;
10use tracing::info;
11use error::{PrereqError, RunnerError};
12
13pub mod error;
14
15pub struct Runner {
23 pub qemu: Child,
24 #[allow(unused)]
25 pub listener: TcpListener,
26 pub stream: TcpStream,
27 pub testing: bool,
34}
35
36impl Drop for Runner {
37 fn drop(&mut self) {
38 if !self.testing {
39 info!("runner dropped: killing qemu");
40 self.qemu.kill().unwrap();
41 }
42 }
43}
44
45impl Read for Runner {
46 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
47 self.stream.read(buf)
48 }
49}
50
51impl Write for Runner {
52 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
53 self.stream.write(buf)
54 }
55
56 fn flush(&mut self) -> std::io::Result<()> {
57 self.stream.flush()
58 }
59}
60
61fn check_prereqs() -> Result<(), PrereqError> {
62 info!("checking for `rust-objcopy` in $PATH");
63 if Command::new("rust-objcopy").output().is_err() {
64 return Err(PrereqError::MissingRustObjcopy);
65 }
66
67 info!("checking for `qemu-system-arm` in $PATH");
68 if Command::new("qemu-system-arm").output().is_err() {
69 return Err(PrereqError::MissingQemu);
70 }
71
72 Ok(())
73}
74
75impl Runner {
76 pub fn new(executable_path: &str, wait_for_debugger: bool) -> Result<Self, RunnerError> {
82 check_prereqs()?;
83 let original_file = Path::new(&executable_path).to_path_buf();
84 let testing = {
85 let mut v = vec![];
86 File::open(&original_file)?.read_to_end(&mut v)?;
87 const TEST_RUNNER: &[u8] = b"test_runner";
88 v.windows(TEST_RUNNER.len()).any(|p| p == TEST_RUNNER)
89 };
90
91 let mut qemu_cmd = create_qemu(&executable_path);
92 const SERIAL_ADDRESS: SocketAddr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 4321));
93 qemu_cmd.args(["-serial", &format!("tcp:{SERIAL_ADDRESS}")]);
94
95 if wait_for_debugger {
96 qemu_cmd.args(["-s", "-S"]);
97 }
98
99 info!("starting qemu: {qemu_cmd:?}");
100 let qemu = qemu_cmd.spawn()?;
101 info!("qemu piping serial i/o to tcp:{SERIAL_ADDRESS}");
102
103 let socket_addr = SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), SERIAL_ADDRESS.port());
104 info!("binding tcp socket to {socket_addr}");
105
106 if wait_for_debugger {
107 info!("waiting for debugger to attach...");
108 }
109
110 let listener = TcpListener::bind(socket_addr).unwrap();
111 let stream = listener.incoming().next().unwrap()?;
112 Ok(Self {
113 qemu,
114 listener,
115 stream,
116 testing,
117 })
118 }
119
120 pub fn wait_for_tests(mut self) {
122 self.qemu.wait().unwrap();
123 }
124}
125
126fn create_qemu(bin_file: &str) -> Command {
127 let mut cmd = Command::new("qemu-system-arm");
128 cmd.stdout(Stdio::inherit());
129 cmd.args(["-machine", "lm3s6965evb"])
130 .args(["-cpu", "cortex-m3"])
131 .args(["-semihosting-config", "enable=on,target=native"])
132 .arg("-kernel")
133 .arg(bin_file)
134 .args(["-monitor", "none"]);
135 cmd
136}