1use std::fmt::LowerHex;
6use std::io::{BufRead, BufReader, Write};
7use std::mem::size_of;
8use std::net::TcpStream;
9use std::os::unix::process::CommandExt;
10use std::path::Path;
11use std::process::{Child, Command, Stdio};
12use std::sync::LazyLock;
13use std::time::{Duration, Instant};
14
15use anyhow::{Context, Result, bail, ensure};
16use regex::Regex;
17use serde::{Deserialize, Serialize};
18use thiserror::Error;
19
20use crate::dif::lc_ctrl::LcCtrlReg;
21use crate::impl_serializable_error;
22use crate::io::jtag::{Jtag, JtagChain, JtagError, JtagParams, JtagTap, RiscvReg};
23use crate::util::parse_int::ParseInt;
24use crate::util::printer;
25
26pub struct OpenOcd {
28 server_process: Child,
30 reader: BufReader<TcpStream>,
32 writer: TcpStream,
34}
35
36impl Drop for OpenOcd {
37 fn drop(&mut self) {
38 let _ = self.server_process.kill();
39 }
40}
41
42impl OpenOcd {
43 const OPENOCD_TCL_READY_TMO: Duration = Duration::from_secs(30);
45
46 fn wait_until_regex_match<'a>(
48 stderr: &mut impl BufRead,
49 regex: &Regex,
50 timeout: Duration,
51 log_stdio: bool,
52 s: &'a mut String,
53 ) -> Result<regex::Captures<'a>> {
54 let start = Instant::now();
55 loop {
56 let n = stderr.read_line(s)?;
59 if n == 0 {
60 bail!("OpenOCD stopped before being ready?");
61 }
62 if log_stdio {
63 log::info!(target: concat!(module_path!(), "::stderr"), "{}", s);
64 }
65 if regex.is_match(s) {
66 return Ok(regex.captures(s).unwrap());
69 }
70 s.clear();
71
72 if start.elapsed() >= timeout {
73 bail!("OpenOCD did not become ready to accept a TCL connection");
74 }
75 }
76 }
77
78 pub fn spawn(path: &Path, log_stdio: bool) -> Result<Self> {
80 let mut cmd = Command::new(path);
81
82 cmd.arg("-c")
88 .arg("tcl_port 0; telnet_port disabled; gdb_port disabled; noinit;");
89
90 log::info!("Spawning OpenOCD: {cmd:?}");
91
92 cmd.stdin(Stdio::null())
93 .stdout(Stdio::piped())
94 .stderr(Stdio::piped());
95
96 unsafe {
98 cmd.pre_exec(|| {
99 rustix::process::set_parent_process_death_signal(Some(
102 rustix::process::Signal::HUP,
103 ))?;
104 Ok(())
105 });
106 }
107
108 let mut child = cmd
109 .spawn()
110 .with_context(|| format!("failed to spawn openocd: {cmd:?}",))?;
111 let stdout = child.stdout.take().unwrap();
112 let mut stderr = BufReader::new(child.stderr.take().unwrap());
113 if log_stdio {
116 log::info!("Waiting for OpenOCD to be ready to accept a TCL connection...");
117 }
118 static READY_REGEX: LazyLock<Regex> = LazyLock::new(|| {
119 Regex::new("Info : Listening on port ([0-9]+) for tcl connections").unwrap()
120 });
121 let mut buf = String::new();
122 let regex_captures = Self::wait_until_regex_match(
123 &mut stderr,
124 &READY_REGEX,
125 Self::OPENOCD_TCL_READY_TMO,
126 log_stdio,
127 &mut buf,
128 )
129 .context("OpenOCD was not ready in time to accept a connection")?;
130 let openocd_port: u16 = regex_captures.get(1).unwrap().as_str().parse()?;
131 if log_stdio {
133 std::thread::spawn(move || {
134 printer::accumulate(
135 stdout,
136 concat!(module_path!(), "::stdout"),
137 Default::default(),
138 )
139 });
140 std::thread::spawn(move || {
141 printer::accumulate(
142 stderr,
143 concat!(module_path!(), "::stderr"),
144 Default::default(),
145 )
146 });
147 }
148
149 let kill_guard = scopeguard::guard(child, |mut child| {
150 let _ = child.kill();
151 });
152
153 log::info!("Connecting to OpenOCD tcl interface...");
154
155 let stream = TcpStream::connect(("localhost", openocd_port))
156 .context("failed to connect to OpenOCD socket")?;
157
158 let mut connection = Self {
159 server_process: scopeguard::ScopeGuard::into_inner(kill_guard),
160 reader: BufReader::new(stream.try_clone()?),
161 writer: stream,
162 };
163
164 let version = connection.execute("version")?;
166 log::info!("OpenOCD version: {version}");
167
168 Ok(connection)
169 }
170
171 fn send(&mut self, cmd: &str) -> Result<()> {
173 if cmd.contains('\x1A') {
178 bail!("TCL command string should be contained inside the text to send");
179 }
180
181 self.writer
182 .write_all(cmd.as_bytes())
183 .context("failed to send a command to OpenOCD server")?;
184 self.writer
185 .write_all(&[0x1a])
186 .context("failed to send the command terminator to OpenOCD server")?;
187 self.writer.flush().context("failed to flush stream")?;
188 Ok(())
189 }
190
191 fn recv(&mut self) -> Result<String> {
192 let mut buf = Vec::new();
193 self.reader.read_until(0x1A, &mut buf)?;
194 if !buf.ends_with(b"\x1A") {
195 bail!(OpenOcdError::PrematureExit);
196 }
197 buf.pop();
198 String::from_utf8(buf).context("failed to parse OpenOCD response as UTF-8")
199 }
200
201 pub fn shutdown(mut self) -> Result<()> {
202 self.execute("shutdown")?;
203 self.server_process
205 .wait()
206 .context("failed to wait for OpenOCD server to exit")?;
207 Ok(())
208 }
209
210 pub fn execute(&mut self, cmd: &str) -> Result<String> {
212 self.send(cmd)?;
213 self.recv()
214 }
215
216 pub fn irscan(&mut self, tap: &str, ir: u32) -> Result<()> {
218 let cmd = format!("irscan {} {:#x}", tap, ir);
219 let result = self.execute(&cmd)?;
220 ensure!(result.is_empty(), "unexpected response: '{result}'");
221 Ok(())
222 }
223
224 pub fn drscan<T: ParseInt + LowerHex>(
226 &mut self,
227 tap: &str,
228 numbits: u32,
229 data: T,
230 ) -> Result<T> {
231 let cmd = format!("drscan {} {} {:#x}", tap, numbits, data);
232 let result = self.execute(&cmd)?;
233 Ok(T::from_str_radix(&result, 16).map_err(|x| x.into())?)
234 }
235}
236
237pub struct OpenOcdJtagChain {
239 openocd: OpenOcd,
241}
242
243#[derive(Error, Debug, Deserialize, Serialize)]
245pub enum OpenOcdError {
246 #[error("OpenOCD initialization failed: {0}")]
247 InitializeFailure(String),
248 #[error("OpenOCD server exists prematurely")]
249 PrematureExit,
250 #[error("Generic error {0}")]
251 Generic(String),
252}
253impl_serializable_error!(OpenOcdError);
254
255impl OpenOcdJtagChain {
256 pub fn new(adapter_command: &str, opts: &JtagParams) -> Result<OpenOcdJtagChain> {
258 let mut openocd = OpenOcd::spawn(&opts.openocd, opts.log_stdio)?;
259
260 openocd.execute(adapter_command)?;
261 openocd.execute(&format!("adapter speed {}", opts.adapter_speed_khz))?;
262 openocd.execute("transport select jtag")?;
263 openocd.execute("scan_chain")?;
264
265 Ok(OpenOcdJtagChain { openocd })
266 }
267}
268
269impl JtagChain for OpenOcdJtagChain {
270 fn connect(mut self: Box<Self>, tap: JtagTap) -> Result<Box<dyn Jtag>> {
271 let target = match tap {
273 JtagTap::RiscvTap => include_str!(env!("openocd_riscv_target_cfg")),
274 JtagTap::LcTap => include_str!(env!("openocd_lc_target_cfg")),
275 };
276 self.openocd.execute(target)?;
277
278 let resp = self.openocd.execute("capture init")?;
280 if resp.contains("JTAG scan chain interrogation failed") {
281 bail!(OpenOcdError::InitializeFailure(resp));
282 }
283
284 Ok(Box::new(OpenOcdJtagTap {
285 openocd: self.openocd,
286 jtag_tap: tap,
287 }))
288 }
289
290 fn into_raw(self: Box<Self>) -> Result<OpenOcd> {
291 Ok(self.openocd)
292 }
293}
294
295pub struct OpenOcdJtagTap {
297 openocd: OpenOcd,
299 jtag_tap: JtagTap,
301}
302
303impl OpenOcdJtagTap {
304 fn send_tcl_cmd(&mut self, cmd: &str) -> Result<String> {
306 self.openocd.execute(cmd)
307 }
308
309 fn read_memory_impl<T: ParseInt>(&mut self, addr: u32, buf: &mut [T]) -> Result<usize> {
310 let cmd = format!(
313 "read_memory 0x{addr:x} {width} {count} phys",
314 width = 8 * size_of::<T>(),
315 count = buf.len()
316 );
317 let response = self.send_tcl_cmd(cmd.as_str())?;
318 response.trim().split(' ').try_fold(0, |idx, val| {
319 if idx < buf.len() {
320 buf[idx] = T::from_str(val).context(format!(
321 "expected response to be an hexadecimal byte, got '{response}'"
322 ))?;
323 Ok(idx + 1)
324 } else {
325 bail!("OpenOCD returned too much data on read".to_string())
326 }
327 })
328 }
329
330 fn write_memory_impl<T: ToString>(&mut self, addr: u32, bigbuf: &[T]) -> Result<()> {
331 const CHUNK_SIZE: usize = 1024;
332 for (idx, buf) in bigbuf.chunks(CHUNK_SIZE).enumerate() {
333 let data: Vec<_> = buf.iter().map(ToString::to_string).collect();
335 let data_str = &data[..].join(" ");
336 let cmd = format!(
338 "write_memory 0x{chunk_addr:x} {width} {{ {data_str} }} phys",
339 chunk_addr = addr + (idx * CHUNK_SIZE * size_of::<T>()) as u32,
340 width = 8 * size_of::<T>()
341 );
342 let response = self.send_tcl_cmd(cmd.as_str())?;
343 if !response.is_empty() {
344 bail!("unexpected response: '{response}'");
345 }
346 }
347
348 Ok(())
349 }
350
351 fn read_register<T: ParseInt>(&mut self, reg_name: &str, force: bool) -> Result<T> {
355 let cmd = format!(
356 "get_reg {} {{ {} }}",
357 if force { "-force" } else { "" },
358 reg_name,
359 );
360 let response = self.send_tcl_cmd(cmd.as_str())?;
361 let (out_reg_name, value) = response.trim().split_once(' ').with_context(|| {
363 format!("expected response of the form 'reg value', got '{response}'")
364 })?;
365 ensure!(
366 out_reg_name == reg_name,
367 "OpenOCD returned the value for register '{out_reg_name}' instead of '{reg_name}"
368 );
369 T::from_str(value).context(format!(
370 "expected value to be an hexadecimal string, got '{value}'"
371 ))
372 }
373
374 fn write_register<T: ToString>(&mut self, reg_name: &str, value: T) -> Result<()> {
375 let cmd = format!("set_reg {{ {reg_name} {} }}", T::to_string(&value));
376 let response = self.send_tcl_cmd(cmd.as_str())?;
377 if !response.is_empty() {
378 bail!("unexpected response: '{response}'");
379 }
380
381 Ok(())
382 }
383}
384
385impl Jtag for OpenOcdJtagTap {
386 fn into_raw(self: Box<Self>) -> Result<OpenOcd> {
387 Ok(self.openocd)
388 }
389
390 fn as_raw(&mut self) -> Result<&mut OpenOcd> {
391 Ok(&mut self.openocd)
392 }
393
394 fn disconnect(self: Box<Self>) -> Result<()> {
395 self.openocd.shutdown()
396 }
397
398 fn tap(&self) -> JtagTap {
399 self.jtag_tap
400 }
401
402 fn read_lc_ctrl_reg(&mut self, reg: &LcCtrlReg) -> Result<u32> {
403 ensure!(
404 matches!(self.jtag_tap, JtagTap::LcTap),
405 JtagError::Tap(self.jtag_tap)
406 );
407 let reg_offset = reg.word_offset();
408 let cmd = format!("riscv dmi_read 0x{reg_offset:x}");
409 let response = self.send_tcl_cmd(cmd.as_str())?;
410
411 let value = u32::from_str(response.trim()).context(format!(
412 "expected response to be hexadecimal word, got '{response}'"
413 ))?;
414
415 Ok(value)
416 }
417
418 fn write_lc_ctrl_reg(&mut self, reg: &LcCtrlReg, value: u32) -> Result<()> {
419 ensure!(
420 matches!(self.jtag_tap, JtagTap::LcTap),
421 JtagError::Tap(self.jtag_tap)
422 );
423 let reg_offset = reg.word_offset();
424 let cmd = format!("riscv dmi_write 0x{reg_offset:x} 0x{value:x}");
425 let response = self.send_tcl_cmd(cmd.as_str())?;
426
427 if !response.is_empty() {
428 bail!("unexpected response: '{response}'");
429 }
430
431 Ok(())
432 }
433
434 fn read_memory(&mut self, addr: u32, buf: &mut [u8]) -> Result<usize> {
435 ensure!(
436 matches!(self.jtag_tap, JtagTap::RiscvTap),
437 JtagError::Tap(self.jtag_tap)
438 );
439 self.read_memory_impl(addr, buf)
440 }
441
442 fn read_memory32(&mut self, addr: u32, buf: &mut [u32]) -> Result<usize> {
443 ensure!(
444 matches!(self.jtag_tap, JtagTap::RiscvTap),
445 JtagError::Tap(self.jtag_tap)
446 );
447 self.read_memory_impl(addr, buf)
448 }
449
450 fn write_memory(&mut self, addr: u32, buf: &[u8]) -> Result<()> {
451 ensure!(
452 matches!(self.jtag_tap, JtagTap::RiscvTap),
453 JtagError::Tap(self.jtag_tap)
454 );
455 self.write_memory_impl(addr, buf)
456 }
457
458 fn write_memory32(&mut self, addr: u32, buf: &[u32]) -> Result<()> {
459 ensure!(
460 matches!(self.jtag_tap, JtagTap::RiscvTap),
461 JtagError::Tap(self.jtag_tap)
462 );
463 self.write_memory_impl(addr, buf)
464 }
465
466 fn halt(&mut self) -> Result<()> {
467 ensure!(
468 matches!(self.jtag_tap, JtagTap::RiscvTap),
469 JtagError::Tap(self.jtag_tap)
470 );
471 let response = self.send_tcl_cmd("halt")?;
472 if !response.is_empty() {
473 bail!("unexpected response: '{response}'");
474 }
475
476 Ok(())
477 }
478
479 fn wait_halt(&mut self, timeout: Duration) -> Result<()> {
480 ensure!(
481 matches!(self.jtag_tap, JtagTap::RiscvTap),
482 JtagError::Tap(self.jtag_tap)
483 );
484 let cmd = format!("wait_halt {}", timeout.as_millis());
485 let response = self.send_tcl_cmd(cmd.as_str())?;
486 if !response.is_empty() {
487 bail!("unexpected response: '{response}'");
488 }
489 Ok(())
490 }
491
492 fn resume(&mut self) -> Result<()> {
493 ensure!(
494 matches!(self.jtag_tap, JtagTap::RiscvTap),
495 JtagError::Tap(self.jtag_tap)
496 );
497 let response = self.send_tcl_cmd("resume")?;
498 if !response.is_empty() {
499 bail!("unexpected response: '{response}'");
500 }
501
502 Ok(())
503 }
504
505 fn resume_at(&mut self, addr: u32) -> Result<()> {
506 ensure!(
507 matches!(self.jtag_tap, JtagTap::RiscvTap),
508 JtagError::Tap(self.jtag_tap)
509 );
510 let cmd = format!("resume 0x{:x}", addr);
511 let response = self.send_tcl_cmd(&cmd)?;
512 if !response.is_empty() {
513 bail!("unexpected response: '{response}'");
514 }
515
516 Ok(())
517 }
518
519 fn reset(&mut self, run: bool) -> Result<()> {
520 ensure!(
521 matches!(self.jtag_tap, JtagTap::RiscvTap),
522 JtagError::Tap(self.jtag_tap)
523 );
524 let cmd = format!("reset {}", if run { "run" } else { "halt" });
525 let response = self.send_tcl_cmd(&cmd)?;
526 if !response.is_empty() {
527 bail!("unexpected response: '{response}'");
528 }
529
530 Ok(())
531 }
532
533 fn step(&mut self) -> Result<()> {
534 ensure!(
535 matches!(self.jtag_tap, JtagTap::RiscvTap),
536 JtagError::Tap(self.jtag_tap)
537 );
538 let response = self.send_tcl_cmd("step")?;
539 if !response.is_empty() {
540 bail!("unexpected response: '{response}'");
541 }
542
543 Ok(())
544 }
545
546 fn step_at(&mut self, addr: u32) -> Result<()> {
547 ensure!(
548 matches!(self.jtag_tap, JtagTap::RiscvTap),
549 JtagError::Tap(self.jtag_tap)
550 );
551 let cmd = format!("step 0x{:x}", addr);
552 let response = self.send_tcl_cmd(&cmd)?;
553 if !response.is_empty() {
554 bail!("unexpected response: '{response}'");
555 }
556
557 Ok(())
558 }
559
560 fn read_riscv_reg(&mut self, reg: &RiscvReg) -> Result<u32> {
561 ensure!(
562 matches!(self.jtag_tap, JtagTap::RiscvTap),
563 JtagError::Tap(self.jtag_tap)
564 );
565 self.read_register::<u32>(reg.name(), true)
566 }
567
568 fn write_riscv_reg(&mut self, reg: &RiscvReg, val: u32) -> Result<()> {
569 ensure!(
570 matches!(self.jtag_tap, JtagTap::RiscvTap),
571 JtagError::Tap(self.jtag_tap)
572 );
573 self.write_register(reg.name(), val)
574 }
575
576 fn set_breakpoint(&mut self, address: u32, hw: bool) -> Result<()> {
577 let cmd = format!("bp {:#x} 2{}", address, if hw { " hw" } else { "" });
578 let response = self.send_tcl_cmd(&cmd)?;
579 if !response.starts_with("breakpoint set at ") {
580 bail!("unexpected response: '{response}'");
581 }
582 Ok(())
583 }
584
585 fn remove_breakpoint(&mut self, addr: u32) -> Result<()> {
586 let cmd = format!("rbp {:#x}", addr);
587 let response = self.send_tcl_cmd(&cmd)?;
588 if !response.is_empty() {
589 bail!("unexpected response: '{response}'");
590 }
591 Ok(())
592 }
593
594 fn remove_all_breakpoints(&mut self) -> Result<()> {
595 let response = self.send_tcl_cmd("rbp all")?;
596 if !response.is_empty() {
597 bail!("unexpected response: '{response}'");
598 }
599 Ok(())
600 }
601}