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