opentitanlib/transport/verilator/
subprocess.rs1use anyhow::{Context, Result, anyhow, ensure};
6use regex::Regex;
7use std::io::ErrorKind;
8use std::process::{Child, Command, Stdio};
9use std::sync::{Arc, Mutex};
10use std::time::{Duration, Instant};
11
12use crate::util::printer;
13
14pub struct Options {
16 pub executable: String,
18 pub rom_images: Vec<String>,
23 pub flash_images: Vec<String>,
28 pub ctn_ram_image: String,
30 pub otp_image: String,
32 pub extra_args: Vec<String>,
34 pub timeout: Duration,
36}
37
38pub struct Subprocess {
39 child: Child,
40 accumulated_output: Arc<Mutex<String>>,
41}
42
43impl Subprocess {
44 pub fn from_options(options: Options) -> Result<Self> {
46 let mut command = Command::new(&options.executable);
47 let mut args = Vec::new();
48
49 if !options.rom_images.is_empty() {
50 let re = Regex::new(r"^(?P<fname>.*?)(:(?P<slot>\d+))?$").unwrap();
52 let images = &options.rom_images;
53 for image in images {
54 let groups = re.captures(image).unwrap();
55 let image_file = groups.name("fname").unwrap().as_str();
56 let slot = match groups.name("slot") {
57 Some(x) => x
58 .as_str()
59 .parse::<u8>()
60 .with_context(|| format!("Invalid ROM slot '{}'", x.as_str()))?,
61 None => 0,
62 };
63 args.push(format!("--meminit=rom{},{}", slot, image_file));
64 }
65 }
66 if !options.flash_images.is_empty() {
67 let re = Regex::new(r"^(?P<fname>.*?)(:(?P<slot>\d+))?$").unwrap();
69 let images = &options.flash_images;
70 for image in images {
71 let groups = re.captures(image).unwrap();
72 let image_file = groups.name("fname").unwrap().as_str();
73 let slot = match groups.name("slot") {
74 Some(x) => x
75 .as_str()
76 .parse::<u8>()
77 .with_context(|| format!("Invalid FLASH slot '{}'", x.as_str()))?,
78 None => 0,
79 };
80 args.push(format!("--meminit=flash{},{}", slot, image_file));
81 }
82 }
83 if !options.ctn_ram_image.is_empty() {
84 args.push(format!("--meminit=ctn_ram,{}", options.ctn_ram_image));
85 }
86 if !options.otp_image.is_empty() {
87 args.push(format!("--meminit=otp,{}", options.otp_image));
88 }
89 args.extend_from_slice(&options.extra_args);
90 command.args(&args[..]);
91
92 log::info!("CWD: {:?}", std::env::current_dir());
93 log::info!(
94 "Spawning verilator: {:?} {:?}",
95 options.executable,
96 args.join(" ")
97 );
98
99 let mut child = command
100 .stdin(Stdio::null())
101 .stdout(Stdio::piped())
102 .stderr(Stdio::inherit())
103 .spawn()?;
104 let accumulator: Arc<Mutex<String>> = Default::default();
105 let stdout = child.stdout.take().unwrap();
106 let a = Arc::clone(&accumulator);
107 std::thread::spawn(move || {
108 printer::accumulate(stdout, concat!(module_path!(), "::stdout"), a)
109 });
110
111 Ok(Subprocess {
112 child,
113 accumulated_output: accumulator,
114 })
115 }
116
117 pub fn find(&self, re: &Regex, deadline: Instant) -> Result<String> {
120 let len = re.captures_len();
125 ensure!(
126 len <= 2,
127 "Expected zero or one capture groups, found {}",
128 len
129 );
130 while deadline > Instant::now() {
131 {
132 let a = self.accumulated_output.lock().unwrap();
133 if let Some(captures) = re.captures(a.as_str()) {
134 let val = captures.get(len - 1).expect("expected a capture");
135 return Ok(val.as_str().to_owned());
136 }
137 }
138 std::thread::sleep(Duration::from_millis(50));
139 }
140 Err(anyhow!("Timed out"))
141 }
142
143 pub fn kill(&mut self) -> Result<()> {
145 match self.child.kill() {
146 Err(error) if error.kind() != ErrorKind::InvalidInput => Err(error.into()),
147 _ => Ok(()),
148 }
149 }
150}
151
152#[cfg(test)]
153mod test {
154 use super::*;
155
156 fn echo_subprocess() -> Result<Subprocess> {
157 let options = Options {
158 executable: "/bin/echo".to_owned(),
159 rom_images: vec!["/dev/null:1".to_owned()],
160 flash_images: vec!["/dev/null:1".to_owned()],
161 otp_image: "".to_owned(),
162 ctn_ram_image: "".to_owned(),
163 extra_args: vec!["abc 123 def 456".to_owned()],
164 timeout: Duration::from_secs(5),
165 };
166 Subprocess::from_options(options)
167 }
168
169 #[test]
170 fn test_find_regex() -> Result<()> {
171 let subprocess = echo_subprocess()?;
172 let regex = Regex::new("abc (.*) def")?;
173 let deadline = Instant::now() + Duration::from_secs(5);
174 let found = subprocess.find(®ex, deadline)?;
175 assert_eq!(found, "123");
176 Ok(())
177 }
178
179 #[test]
180 fn test_kill() -> Result<()> {
181 let mut subprocess = echo_subprocess()?;
182 subprocess.kill()?;
183 Ok(())
184 }
185}