1use std::collections::VecDeque;
6use std::fmt::LowerHex;
7use std::io::{BufRead, BufReader, Write};
8use std::mem::size_of;
9use std::net::TcpStream;
10use std::os::unix::process::CommandExt;
11use std::path::Path;
12use std::process::{Child, Command, Stdio};
13use std::sync::{Arc, Mutex};
14use std::thread::JoinHandle;
15use std::time::{Duration, Instant};
16
17use anyhow::{Context, Result, bail, ensure};
18use regex::Regex;
19use serde::{Deserialize, Serialize};
20use thiserror::Error;
21
22use ot_hal::dif::lc_ctrl::LcCtrlReg;
23
24use crate::impl_serializable_error;
25use crate::io::jtag::{Jtag, JtagChain, JtagError, JtagParams, JtagTap, RiscvReg};
26use crate::regex;
27use crate::util::parse_int::ParseInt;
28use crate::util::printer;
29
30pub struct OpenOcd {
32 server_process: Child,
34 reader: BufReader<TcpStream>,
36 writer: TcpStream,
38 stderr_tail: Arc<Mutex<VecDeque<String>>>,
40 stderr_thread: Option<JoinHandle<()>>,
42}
43
44impl Drop for OpenOcd {
45 fn drop(&mut self) {
46 let _ = self.server_process.kill();
47 }
48}
49
50impl OpenOcd {
51 const OPENOCD_TCL_READY_TMO: Duration = Duration::from_secs(30);
53 const STDERR_TAIL_LINES: usize = 20;
55 const EXIT_STATUS_TMO: Duration = Duration::from_secs(1);
57
58 fn wait_until_regex_match<'a>(
60 stderr: &mut impl BufRead,
61 regex: &Regex,
62 timeout: Duration,
63 log_stdio: bool,
64 s: &'a mut String,
65 ) -> Result<regex::Captures<'a>> {
66 let start = Instant::now();
67 loop {
68 let n = stderr.read_line(s)?;
71 if n == 0 {
72 bail!("OpenOCD stopped before being ready?");
73 }
74 if log_stdio {
75 log::info!(target: concat!(module_path!(), "::stderr"), "{}", s);
76 }
77 if regex.is_match(s) {
78 return Ok(regex.captures(s).unwrap());
81 }
82 s.clear();
83
84 if start.elapsed() >= timeout {
85 bail!("OpenOCD did not become ready to accept a TCL connection");
86 }
87 }
88 }
89
90 pub fn spawn(path: &Path, log_stdio: bool) -> Result<Self> {
92 let mut cmd = Command::new(path);
93
94 cmd.arg("-c")
100 .arg("tcl_port 0; telnet_port disabled; gdb_port disabled; noinit;");
101
102 log::info!("Spawning OpenOCD: {cmd:?}");
103
104 cmd.stdin(Stdio::null())
105 .stdout(Stdio::piped())
106 .stderr(Stdio::piped());
107
108 unsafe {
110 cmd.pre_exec(|| {
111 rustix::process::set_parent_process_death_signal(Some(
114 rustix::process::Signal::HUP,
115 ))?;
116 Ok(())
117 });
118 }
119
120 let mut child = cmd
121 .spawn()
122 .with_context(|| format!("failed to spawn openocd: {cmd:?}",))?;
123 let stdout = child.stdout.take().unwrap();
124 let mut stderr = BufReader::new(child.stderr.take().unwrap());
125 if log_stdio {
128 log::info!("Waiting for OpenOCD to be ready to accept a TCL connection...");
129 }
130 let mut buf = String::new();
131 let regex_captures = Self::wait_until_regex_match(
132 &mut stderr,
133 regex!("Info : Listening on port ([0-9]+) for tcl connections"),
134 Self::OPENOCD_TCL_READY_TMO,
135 log_stdio,
136 &mut buf,
137 )
138 .context("OpenOCD was not ready in time to accept a connection")?;
139 let openocd_port: u16 = regex_captures.get(1).unwrap().as_str().parse()?;
140 if log_stdio {
142 std::thread::spawn(move || {
143 printer::accumulate(
144 stdout,
145 concat!(module_path!(), "::stdout"),
146 Default::default(),
147 )
148 });
149 }
150 let stderr_tail = Arc::new(Mutex::new(VecDeque::with_capacity(Self::STDERR_TAIL_LINES)));
153 let tail = Arc::clone(&stderr_tail);
154 let stderr_thread = std::thread::spawn(move || {
155 let mut buf = Vec::new();
157 while let Ok(n) = stderr.read_until(b'\n', &mut buf)
158 && n > 0
159 {
160 let line = String::from_utf8_lossy(&buf)
161 .trim_end_matches(['\r', '\n'])
162 .to_string();
163 buf.clear();
164 if log_stdio {
165 log::info!(target: concat!(module_path!(), "::stderr"), "{line}");
166 }
167 let mut tail = tail.lock().unwrap();
168 if tail.len() == Self::STDERR_TAIL_LINES {
169 tail.pop_front();
170 }
171 tail.push_back(line);
172 }
173 });
174
175 let kill_guard = scopeguard::guard(child, |mut child| {
176 let _ = child.kill();
177 });
178
179 log::info!("Connecting to OpenOCD tcl interface...");
180
181 let stream = TcpStream::connect(("localhost", openocd_port))
182 .context("failed to connect to OpenOCD socket")?;
183
184 stream
188 .set_nodelay(true)
189 .context("failed to disable TCP socket delay")?;
190
191 let mut connection = Self {
192 server_process: scopeguard::ScopeGuard::into_inner(kill_guard),
193 reader: BufReader::new(stream.try_clone()?),
194 writer: stream,
195 stderr_tail,
196 stderr_thread: Some(stderr_thread),
197 };
198
199 let version = connection.execute("version")?;
201 log::info!("OpenOCD version: {version}");
202
203 Ok(connection)
204 }
205
206 fn send(&mut self, cmd: &str) -> Result<()> {
208 if cmd.contains('\x1A') {
213 bail!("TCL command string should be contained inside the text to send");
214 }
215
216 self.writer
217 .write_all(cmd.as_bytes())
218 .context("failed to send a command to OpenOCD server")?;
219 self.writer
220 .write_all(&[0x1a])
221 .context("failed to send the command terminator to OpenOCD server")?;
222 self.writer.flush().context("failed to flush stream")?;
223 Ok(())
224 }
225
226 fn recv(&mut self) -> Result<String> {
227 let mut buf = Vec::new();
228 self.reader.read_until(0x1A, &mut buf)?;
229 if !buf.ends_with(b"\x1A") {
230 let partial = String::from_utf8_lossy(&buf).to_string();
231 let report = self.exit_report();
232 bail!(OpenOcdError::PrematureExit(if partial.is_empty() {
233 report
234 } else {
235 format!("{partial}; {report}")
236 }));
237 }
238 buf.pop();
239 String::from_utf8(buf).context("failed to parse OpenOCD response as UTF-8")
240 }
241
242 fn exit_report(&mut self) -> String {
244 let deadline = Instant::now() + Self::EXIT_STATUS_TMO;
246 let status = loop {
247 match self.server_process.try_wait() {
248 Ok(None) if Instant::now() < deadline => {
249 std::thread::sleep(Duration::from_millis(10))
250 }
251 Ok(status) => break status,
252 Err(_) => break None,
253 }
254 };
255 if status.is_some()
257 && let Some(thread) = self.stderr_thread.take()
258 {
259 let _ = thread.join();
260 }
261 let tail = self.stderr_tail.lock().unwrap();
262 let mut report = match status {
263 Some(status) => format!("process ended with {status}"),
264 None => "process still running".to_string(),
265 };
266 if !tail.is_empty() {
267 report.push_str("\nLast OpenOCD output:");
268 for line in tail.iter() {
269 report.push_str("\n ");
270 report.push_str(line);
271 }
272 }
273 report
274 }
275
276 pub fn shutdown(mut self) -> Result<()> {
277 self.execute("shutdown")?;
278 self.server_process
280 .wait()
281 .context("failed to wait for OpenOCD server to exit")?;
282 Ok(())
283 }
284
285 pub fn execute(&mut self, cmd: &str) -> Result<String> {
287 self.send(cmd)?;
288 self.recv()
289 }
290
291 pub fn irscan(&mut self, tap: &str, ir: u32) -> Result<()> {
293 let cmd = format!("irscan {} {:#x}", tap, ir);
294 let result = self.execute(&cmd)?;
295 ensure!(result.is_empty(), "unexpected response: '{result}'");
296 Ok(())
297 }
298
299 pub fn drscan_cmd<T: ParseInt + LowerHex>(&self, tap: &str, numbits: u32, data: T) -> String {
301 format!("drscan {} {} {:#x}", tap, numbits, data)
302 }
303
304 pub fn drscan<T: ParseInt + LowerHex>(
306 &mut self,
307 tap: &str,
308 numbits: u32,
309 data: T,
310 ) -> Result<T> {
311 let cmd = self.drscan_cmd(tap, numbits, data);
312 let result = self.execute(&cmd)?;
313 Ok(T::from_str_radix(&result, 16).map_err(|x| x.into())?)
314 }
315}
316
317pub struct OpenOcdJtagChain {
319 openocd: OpenOcd,
321}
322
323#[derive(Error, Debug, Deserialize, Serialize)]
325pub enum OpenOcdError {
326 #[error("OpenOCD initialization failed: {0}")]
327 InitializeFailure(String),
328 #[error("OpenOCD server exited prematurely: {0}")]
329 PrematureExit(String),
330 #[error("Generic error {0}")]
331 Generic(String),
332}
333impl_serializable_error!(OpenOcdError);
334
335impl OpenOcdJtagChain {
336 pub fn new(adapter_command: &str, opts: &JtagParams) -> Result<OpenOcdJtagChain> {
338 let mut openocd = OpenOcd::spawn(&opts.openocd, opts.log_stdio)?;
339
340 openocd.execute(adapter_command)?;
341 openocd.execute(&format!("adapter speed {}", opts.adapter_speed_khz))?;
342 openocd.execute("transport select jtag")?;
343 openocd.execute("scan_chain")?;
344
345 Ok(OpenOcdJtagChain { openocd })
346 }
347}
348
349impl JtagChain for OpenOcdJtagChain {
350 fn connect(mut self: Box<Self>, tap: JtagTap) -> Result<Box<dyn Jtag>> {
351 let target = match tap {
353 JtagTap::RiscvTap => include_str!(env!("openocd_riscv_target_cfg")),
354 JtagTap::LcTap => include_str!(env!("openocd_lc_target_cfg")),
355 JtagTap::BackdoorTap => include_str!(env!("openocd_fpga_backdoor_target_cfg")),
356 };
357 self.openocd.execute(target)?;
358
359 let resp = self.openocd.execute("capture init")?;
361 if resp.contains("JTAG scan chain interrogation failed") {
362 bail!(OpenOcdError::InitializeFailure(resp));
363 }
364 if tap == JtagTap::RiscvTap && resp.contains("examination failed") {
368 log::warn!("OpenOCD could not examine the target during init:\n{resp}");
369 }
370
371 Ok(Box::new(OpenOcdJtagTap {
372 openocd: self.openocd,
373 jtag_tap: tap,
374 }))
375 }
376
377 fn into_raw(self: Box<Self>) -> Result<OpenOcd> {
378 Ok(self.openocd)
379 }
380}
381
382pub struct OpenOcdJtagTap {
384 openocd: OpenOcd,
386 jtag_tap: JtagTap,
388}
389
390impl OpenOcdJtagTap {
391 fn send_tcl_cmd(&mut self, cmd: &str) -> Result<String> {
393 self.openocd.execute(cmd)
394 }
395
396 fn read_memory_impl<T: ParseInt>(&mut self, addr: u32, buf: &mut [T]) -> Result<usize> {
397 let cmd = format!(
400 "read_memory 0x{addr:x} {width} {count} phys",
401 width = 8 * size_of::<T>(),
402 count = buf.len()
403 );
404 let response = self.send_tcl_cmd(cmd.as_str())?;
405 response.trim().split(' ').try_fold(0, |idx, val| {
406 if idx < buf.len() {
407 buf[idx] = T::from_str(val).context(format!(
408 "expected response to be an hexadecimal byte, got '{response}'"
409 ))?;
410 Ok(idx + 1)
411 } else {
412 bail!("OpenOCD returned too much data on read".to_string())
413 }
414 })
415 }
416
417 fn write_memory_impl<T: ToString>(&mut self, addr: u32, bigbuf: &[T]) -> Result<()> {
418 const CHUNK_SIZE: usize = 1024;
419 for (idx, buf) in bigbuf.chunks(CHUNK_SIZE).enumerate() {
420 let data: Vec<_> = buf.iter().map(ToString::to_string).collect();
422 let data_str = &data[..].join(" ");
423 let cmd = format!(
425 "write_memory 0x{chunk_addr:x} {width} {{ {data_str} }} phys",
426 chunk_addr = addr + (idx * CHUNK_SIZE * size_of::<T>()) as u32,
427 width = 8 * size_of::<T>()
428 );
429 let response = self.send_tcl_cmd(cmd.as_str())?;
430 if !response.is_empty() {
431 bail!("unexpected response: '{response}'");
432 }
433 }
434
435 Ok(())
436 }
437
438 fn read_register<T: ParseInt>(&mut self, reg_name: &str, force: bool) -> Result<T> {
442 let cmd = format!(
443 "get_reg {} {{ {} }}",
444 if force { "-force" } else { "" },
445 reg_name,
446 );
447 let response = self.send_tcl_cmd(cmd.as_str())?;
448 let (out_reg_name, value) = response.trim().split_once(' ').with_context(|| {
450 format!("expected response of the form 'reg value', got '{response}'")
451 })?;
452 ensure!(
453 out_reg_name == reg_name,
454 "OpenOCD returned the value for register '{out_reg_name}' instead of '{reg_name}"
455 );
456 T::from_str(value).context(format!(
457 "expected value to be an hexadecimal string, got '{value}'"
458 ))
459 }
460
461 fn write_register<T: ToString>(&mut self, reg_name: &str, value: T) -> Result<()> {
462 let cmd = format!("set_reg {{ {reg_name} {} }}", T::to_string(&value));
463 let response = self.send_tcl_cmd(cmd.as_str())?;
464 if !response.is_empty() {
465 bail!("unexpected response: '{response}'");
466 }
467
468 Ok(())
469 }
470}
471
472impl Jtag for OpenOcdJtagTap {
473 fn into_raw(self: Box<Self>) -> Result<OpenOcd> {
474 Ok(self.openocd)
475 }
476
477 fn as_raw(&mut self) -> Result<&mut OpenOcd> {
478 Ok(&mut self.openocd)
479 }
480
481 fn disconnect(self: Box<Self>) -> Result<()> {
482 self.openocd.shutdown()
483 }
484
485 fn tap(&self) -> JtagTap {
486 self.jtag_tap
487 }
488
489 fn read_lc_ctrl_reg(&mut self, reg: &LcCtrlReg) -> Result<u32> {
490 ensure!(
491 matches!(self.jtag_tap, JtagTap::LcTap),
492 JtagError::Tap(self.jtag_tap)
493 );
494 let reg_offset = reg.word_offset();
495 let cmd = format!("riscv dmi_read 0x{reg_offset:x}");
496 let response = self.send_tcl_cmd(cmd.as_str())?;
497
498 let value = u32::from_str(response.trim()).context(format!(
499 "expected response to be hexadecimal word, got '{response}'"
500 ))?;
501
502 Ok(value)
503 }
504
505 fn write_lc_ctrl_reg(&mut self, reg: &LcCtrlReg, value: u32) -> Result<()> {
506 ensure!(
507 matches!(self.jtag_tap, JtagTap::LcTap),
508 JtagError::Tap(self.jtag_tap)
509 );
510 let reg_offset = reg.word_offset();
511 let cmd = format!("riscv dmi_write 0x{reg_offset:x} 0x{value:x}");
512 let response = self.send_tcl_cmd(cmd.as_str())?;
513
514 if !response.is_empty() {
515 bail!("unexpected response: '{response}'");
516 }
517
518 Ok(())
519 }
520
521 fn read_memory(&mut self, addr: u32, buf: &mut [u8]) -> Result<usize> {
522 ensure!(
523 matches!(self.jtag_tap, JtagTap::RiscvTap),
524 JtagError::Tap(self.jtag_tap)
525 );
526 self.read_memory_impl(addr, buf)
527 }
528
529 fn read_memory32(&mut self, addr: u32, buf: &mut [u32]) -> Result<usize> {
530 ensure!(
531 matches!(self.jtag_tap, JtagTap::RiscvTap),
532 JtagError::Tap(self.jtag_tap)
533 );
534 self.read_memory_impl(addr, buf)
535 }
536
537 fn write_memory(&mut self, addr: u32, buf: &[u8]) -> Result<()> {
538 ensure!(
539 matches!(self.jtag_tap, JtagTap::RiscvTap),
540 JtagError::Tap(self.jtag_tap)
541 );
542 self.write_memory_impl(addr, buf)
543 }
544
545 fn write_memory32(&mut self, addr: u32, buf: &[u32]) -> Result<()> {
546 ensure!(
547 matches!(self.jtag_tap, JtagTap::RiscvTap),
548 JtagError::Tap(self.jtag_tap)
549 );
550 self.write_memory_impl(addr, buf)
551 }
552
553 fn halt(&mut self) -> Result<()> {
554 ensure!(
555 matches!(self.jtag_tap, JtagTap::RiscvTap),
556 JtagError::Tap(self.jtag_tap)
557 );
558 let response = self.send_tcl_cmd("halt")?;
559 if !response.is_empty() {
560 bail!("unexpected response: '{response}'");
561 }
562
563 Ok(())
564 }
565
566 fn wait_halt(&mut self, timeout: Duration) -> Result<()> {
567 ensure!(
568 matches!(self.jtag_tap, JtagTap::RiscvTap),
569 JtagError::Tap(self.jtag_tap)
570 );
571 let cmd = format!("wait_halt {}", timeout.as_millis());
572 let response = self.send_tcl_cmd(cmd.as_str())?;
573 if !response.is_empty() {
574 bail!("unexpected response: '{response}'");
575 }
576 Ok(())
577 }
578
579 fn resume(&mut self) -> Result<()> {
580 ensure!(
581 matches!(self.jtag_tap, JtagTap::RiscvTap),
582 JtagError::Tap(self.jtag_tap)
583 );
584 let response = self.send_tcl_cmd("resume")?;
585 if !response.is_empty() {
586 bail!("unexpected response: '{response}'");
587 }
588
589 Ok(())
590 }
591
592 fn resume_at(&mut self, addr: u32) -> Result<()> {
593 ensure!(
594 matches!(self.jtag_tap, JtagTap::RiscvTap),
595 JtagError::Tap(self.jtag_tap)
596 );
597 let cmd = format!("resume 0x{:x}", addr);
598 let response = self.send_tcl_cmd(&cmd)?;
599 if !response.is_empty() {
600 bail!("unexpected response: '{response}'");
601 }
602
603 Ok(())
604 }
605
606 fn reset(&mut self, run: bool) -> Result<()> {
607 ensure!(
608 matches!(self.jtag_tap, JtagTap::RiscvTap),
609 JtagError::Tap(self.jtag_tap)
610 );
611 let cmd = format!("reset {}", if run { "run" } else { "halt" });
612 let response = self.send_tcl_cmd(&cmd)?;
613 if !response.is_empty() {
614 bail!("unexpected response: '{response}'");
615 }
616
617 Ok(())
618 }
619
620 fn step(&mut self) -> Result<()> {
621 ensure!(
622 matches!(self.jtag_tap, JtagTap::RiscvTap),
623 JtagError::Tap(self.jtag_tap)
624 );
625 let response = self.send_tcl_cmd("step")?;
626 if !response.is_empty() {
627 bail!("unexpected response: '{response}'");
628 }
629
630 Ok(())
631 }
632
633 fn step_at(&mut self, addr: u32) -> Result<()> {
634 ensure!(
635 matches!(self.jtag_tap, JtagTap::RiscvTap),
636 JtagError::Tap(self.jtag_tap)
637 );
638 let cmd = format!("step 0x{:x}", addr);
639 let response = self.send_tcl_cmd(&cmd)?;
640 if !response.is_empty() {
641 bail!("unexpected response: '{response}'");
642 }
643
644 Ok(())
645 }
646
647 fn read_riscv_reg(&mut self, reg: &RiscvReg) -> Result<u32> {
648 ensure!(
649 matches!(self.jtag_tap, JtagTap::RiscvTap),
650 JtagError::Tap(self.jtag_tap)
651 );
652 self.read_register::<u32>(reg.name(), true)
653 }
654
655 fn write_riscv_reg(&mut self, reg: &RiscvReg, val: u32) -> Result<()> {
656 ensure!(
657 matches!(self.jtag_tap, JtagTap::RiscvTap),
658 JtagError::Tap(self.jtag_tap)
659 );
660 self.write_register(reg.name(), val)
661 }
662
663 fn set_breakpoint(&mut self, address: u32, hw: bool) -> Result<()> {
664 let cmd = format!("bp {:#x} 2{}", address, if hw { " hw" } else { "" });
665 let response = self.send_tcl_cmd(&cmd)?;
666 if !response.starts_with("breakpoint set at ") {
667 bail!("unexpected response: '{response}'");
668 }
669 Ok(())
670 }
671
672 fn remove_breakpoint(&mut self, addr: u32) -> Result<()> {
673 let cmd = format!("rbp {:#x}", addr);
674 let response = self.send_tcl_cmd(&cmd)?;
675 if !response.is_empty() {
676 bail!("unexpected response: '{response}'");
677 }
678 Ok(())
679 }
680
681 fn remove_all_breakpoints(&mut self) -> Result<()> {
682 let response = self.send_tcl_cmd("rbp all")?;
683 if !response.is_empty() {
684 bail!("unexpected response: '{response}'");
685 }
686 Ok(())
687 }
688}