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::time::{Duration, Instant};
13
14use anyhow::{Context, Result, bail, ensure};
15use regex::Regex;
16use serde::{Deserialize, Serialize};
17use thiserror::Error;
18
19use ot_hal::dif::lc_ctrl::LcCtrlReg;
20
21use crate::impl_serializable_error;
22use crate::io::jtag::{Jtag, JtagChain, JtagError, JtagParams, JtagTap, RiscvReg};
23use crate::regex;
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 let mut buf = String::new();
120 let regex_captures = Self::wait_until_regex_match(
121 &mut stderr,
122 regex!("Info : Listening on port ([0-9]+) for tcl connections"),
123 Self::OPENOCD_TCL_READY_TMO,
124 log_stdio,
125 &mut buf,
126 )
127 .context("OpenOCD was not ready in time to accept a connection")?;
128 let openocd_port: u16 = regex_captures.get(1).unwrap().as_str().parse()?;
129 if log_stdio {
131 std::thread::spawn(move || {
132 printer::accumulate(
133 stdout,
134 concat!(module_path!(), "::stdout"),
135 Default::default(),
136 )
137 });
138 std::thread::spawn(move || {
139 printer::accumulate(
140 stderr,
141 concat!(module_path!(), "::stderr"),
142 Default::default(),
143 )
144 });
145 }
146
147 let kill_guard = scopeguard::guard(child, |mut child| {
148 let _ = child.kill();
149 });
150
151 log::info!("Connecting to OpenOCD tcl interface...");
152
153 let stream = TcpStream::connect(("localhost", openocd_port))
154 .context("failed to connect to OpenOCD socket")?;
155
156 stream
160 .set_nodelay(true)
161 .context("failed to disable TCP socket delay")?;
162
163 let mut connection = Self {
164 server_process: scopeguard::ScopeGuard::into_inner(kill_guard),
165 reader: BufReader::new(stream.try_clone()?),
166 writer: stream,
167 };
168
169 let version = connection.execute("version")?;
171 log::info!("OpenOCD version: {version}");
172
173 Ok(connection)
174 }
175
176 fn send(&mut self, cmd: &str) -> Result<()> {
178 if cmd.contains('\x1A') {
183 bail!("TCL command string should be contained inside the text to send");
184 }
185
186 self.writer
187 .write_all(cmd.as_bytes())
188 .context("failed to send a command to OpenOCD server")?;
189 self.writer
190 .write_all(&[0x1a])
191 .context("failed to send the command terminator to OpenOCD server")?;
192 self.writer.flush().context("failed to flush stream")?;
193 Ok(())
194 }
195
196 fn recv(&mut self) -> Result<String> {
197 let mut buf = Vec::new();
198 self.reader.read_until(0x1A, &mut buf)?;
199 if !buf.ends_with(b"\x1A") {
200 bail!(OpenOcdError::PrematureExit(
201 String::from_utf8_lossy(&buf).to_string()
202 ));
203 }
204 buf.pop();
205 String::from_utf8(buf).context("failed to parse OpenOCD response as UTF-8")
206 }
207
208 pub fn shutdown(mut self) -> Result<()> {
209 self.execute("shutdown")?;
210 self.server_process
212 .wait()
213 .context("failed to wait for OpenOCD server to exit")?;
214 Ok(())
215 }
216
217 pub fn execute(&mut self, cmd: &str) -> Result<String> {
219 self.send(cmd)?;
220 self.recv()
221 }
222
223 pub fn irscan(&mut self, tap: &str, ir: u32) -> Result<()> {
225 let cmd = format!("irscan {} {:#x}", tap, ir);
226 let result = self.execute(&cmd)?;
227 ensure!(result.is_empty(), "unexpected response: '{result}'");
228 Ok(())
229 }
230
231 pub fn drscan_cmd<T: ParseInt + LowerHex>(&self, tap: &str, numbits: u32, data: T) -> String {
233 format!("drscan {} {} {:#x}", tap, numbits, data)
234 }
235
236 pub fn drscan<T: ParseInt + LowerHex>(
238 &mut self,
239 tap: &str,
240 numbits: u32,
241 data: T,
242 ) -> Result<T> {
243 let cmd = self.drscan_cmd(tap, numbits, data);
244 let result = self.execute(&cmd)?;
245 Ok(T::from_str_radix(&result, 16).map_err(|x| x.into())?)
246 }
247}
248
249pub struct OpenOcdJtagChain {
251 openocd: OpenOcd,
253}
254
255#[derive(Error, Debug, Deserialize, Serialize)]
257pub enum OpenOcdError {
258 #[error("OpenOCD initialization failed: {0}")]
259 InitializeFailure(String),
260 #[error("OpenOCD server exited prematurely: {0}")]
261 PrematureExit(String),
262 #[error("Generic error {0}")]
263 Generic(String),
264}
265impl_serializable_error!(OpenOcdError);
266
267impl OpenOcdJtagChain {
268 pub fn new(adapter_command: &str, opts: &JtagParams) -> Result<OpenOcdJtagChain> {
270 let mut openocd = OpenOcd::spawn(&opts.openocd, opts.log_stdio)?;
271
272 openocd.execute(adapter_command)?;
273 openocd.execute(&format!("adapter speed {}", opts.adapter_speed_khz))?;
274 openocd.execute("transport select jtag")?;
275 openocd.execute("scan_chain")?;
276
277 Ok(OpenOcdJtagChain { openocd })
278 }
279}
280
281impl JtagChain for OpenOcdJtagChain {
282 fn connect(mut self: Box<Self>, tap: JtagTap) -> Result<Box<dyn Jtag>> {
283 let target = match tap {
285 JtagTap::RiscvTap => include_str!(env!("openocd_riscv_target_cfg")),
286 JtagTap::LcTap => include_str!(env!("openocd_lc_target_cfg")),
287 JtagTap::BackdoorTap => include_str!(env!("openocd_fpga_backdoor_target_cfg")),
288 };
289 self.openocd.execute(target)?;
290
291 let resp = self.openocd.execute("capture init")?;
293 if resp.contains("JTAG scan chain interrogation failed") {
294 bail!(OpenOcdError::InitializeFailure(resp));
295 }
296
297 Ok(Box::new(OpenOcdJtagTap {
298 openocd: self.openocd,
299 jtag_tap: tap,
300 }))
301 }
302
303 fn into_raw(self: Box<Self>) -> Result<OpenOcd> {
304 Ok(self.openocd)
305 }
306}
307
308pub struct OpenOcdJtagTap {
310 openocd: OpenOcd,
312 jtag_tap: JtagTap,
314}
315
316impl OpenOcdJtagTap {
317 fn send_tcl_cmd(&mut self, cmd: &str) -> Result<String> {
319 self.openocd.execute(cmd)
320 }
321
322 fn read_memory_impl<T: ParseInt>(&mut self, addr: u32, buf: &mut [T]) -> Result<usize> {
323 let cmd = format!(
326 "read_memory 0x{addr:x} {width} {count} phys",
327 width = 8 * size_of::<T>(),
328 count = buf.len()
329 );
330 let response = self.send_tcl_cmd(cmd.as_str())?;
331 response.trim().split(' ').try_fold(0, |idx, val| {
332 if idx < buf.len() {
333 buf[idx] = T::from_str(val).context(format!(
334 "expected response to be an hexadecimal byte, got '{response}'"
335 ))?;
336 Ok(idx + 1)
337 } else {
338 bail!("OpenOCD returned too much data on read".to_string())
339 }
340 })
341 }
342
343 fn write_memory_impl<T: ToString>(&mut self, addr: u32, bigbuf: &[T]) -> Result<()> {
344 const CHUNK_SIZE: usize = 1024;
345 for (idx, buf) in bigbuf.chunks(CHUNK_SIZE).enumerate() {
346 let data: Vec<_> = buf.iter().map(ToString::to_string).collect();
348 let data_str = &data[..].join(" ");
349 let cmd = format!(
351 "write_memory 0x{chunk_addr:x} {width} {{ {data_str} }} phys",
352 chunk_addr = addr + (idx * CHUNK_SIZE * size_of::<T>()) as u32,
353 width = 8 * size_of::<T>()
354 );
355 let response = self.send_tcl_cmd(cmd.as_str())?;
356 if !response.is_empty() {
357 bail!("unexpected response: '{response}'");
358 }
359 }
360
361 Ok(())
362 }
363
364 fn read_register<T: ParseInt>(&mut self, reg_name: &str, force: bool) -> Result<T> {
368 let cmd = format!(
369 "get_reg {} {{ {} }}",
370 if force { "-force" } else { "" },
371 reg_name,
372 );
373 let response = self.send_tcl_cmd(cmd.as_str())?;
374 let (out_reg_name, value) = response.trim().split_once(' ').with_context(|| {
376 format!("expected response of the form 'reg value', got '{response}'")
377 })?;
378 ensure!(
379 out_reg_name == reg_name,
380 "OpenOCD returned the value for register '{out_reg_name}' instead of '{reg_name}"
381 );
382 T::from_str(value).context(format!(
383 "expected value to be an hexadecimal string, got '{value}'"
384 ))
385 }
386
387 fn write_register<T: ToString>(&mut self, reg_name: &str, value: T) -> Result<()> {
388 let cmd = format!("set_reg {{ {reg_name} {} }}", T::to_string(&value));
389 let response = self.send_tcl_cmd(cmd.as_str())?;
390 if !response.is_empty() {
391 bail!("unexpected response: '{response}'");
392 }
393
394 Ok(())
395 }
396}
397
398impl Jtag for OpenOcdJtagTap {
399 fn into_raw(self: Box<Self>) -> Result<OpenOcd> {
400 Ok(self.openocd)
401 }
402
403 fn as_raw(&mut self) -> Result<&mut OpenOcd> {
404 Ok(&mut self.openocd)
405 }
406
407 fn disconnect(self: Box<Self>) -> Result<()> {
408 self.openocd.shutdown()
409 }
410
411 fn tap(&self) -> JtagTap {
412 self.jtag_tap
413 }
414
415 fn read_lc_ctrl_reg(&mut self, reg: &LcCtrlReg) -> Result<u32> {
416 ensure!(
417 matches!(self.jtag_tap, JtagTap::LcTap),
418 JtagError::Tap(self.jtag_tap)
419 );
420 let reg_offset = reg.word_offset();
421 let cmd = format!("riscv dmi_read 0x{reg_offset:x}");
422 let response = self.send_tcl_cmd(cmd.as_str())?;
423
424 let value = u32::from_str(response.trim()).context(format!(
425 "expected response to be hexadecimal word, got '{response}'"
426 ))?;
427
428 Ok(value)
429 }
430
431 fn write_lc_ctrl_reg(&mut self, reg: &LcCtrlReg, value: u32) -> Result<()> {
432 ensure!(
433 matches!(self.jtag_tap, JtagTap::LcTap),
434 JtagError::Tap(self.jtag_tap)
435 );
436 let reg_offset = reg.word_offset();
437 let cmd = format!("riscv dmi_write 0x{reg_offset:x} 0x{value:x}");
438 let response = self.send_tcl_cmd(cmd.as_str())?;
439
440 if !response.is_empty() {
441 bail!("unexpected response: '{response}'");
442 }
443
444 Ok(())
445 }
446
447 fn read_memory(&mut self, addr: u32, buf: &mut [u8]) -> Result<usize> {
448 ensure!(
449 matches!(self.jtag_tap, JtagTap::RiscvTap),
450 JtagError::Tap(self.jtag_tap)
451 );
452 self.read_memory_impl(addr, buf)
453 }
454
455 fn read_memory32(&mut self, addr: u32, buf: &mut [u32]) -> Result<usize> {
456 ensure!(
457 matches!(self.jtag_tap, JtagTap::RiscvTap),
458 JtagError::Tap(self.jtag_tap)
459 );
460 self.read_memory_impl(addr, buf)
461 }
462
463 fn write_memory(&mut self, addr: u32, buf: &[u8]) -> Result<()> {
464 ensure!(
465 matches!(self.jtag_tap, JtagTap::RiscvTap),
466 JtagError::Tap(self.jtag_tap)
467 );
468 self.write_memory_impl(addr, buf)
469 }
470
471 fn write_memory32(&mut self, addr: u32, buf: &[u32]) -> Result<()> {
472 ensure!(
473 matches!(self.jtag_tap, JtagTap::RiscvTap),
474 JtagError::Tap(self.jtag_tap)
475 );
476 self.write_memory_impl(addr, buf)
477 }
478
479 fn halt(&mut self) -> Result<()> {
480 ensure!(
481 matches!(self.jtag_tap, JtagTap::RiscvTap),
482 JtagError::Tap(self.jtag_tap)
483 );
484 let response = self.send_tcl_cmd("halt")?;
485 if !response.is_empty() {
486 bail!("unexpected response: '{response}'");
487 }
488
489 Ok(())
490 }
491
492 fn wait_halt(&mut self, timeout: Duration) -> Result<()> {
493 ensure!(
494 matches!(self.jtag_tap, JtagTap::RiscvTap),
495 JtagError::Tap(self.jtag_tap)
496 );
497 let cmd = format!("wait_halt {}", timeout.as_millis());
498 let response = self.send_tcl_cmd(cmd.as_str())?;
499 if !response.is_empty() {
500 bail!("unexpected response: '{response}'");
501 }
502 Ok(())
503 }
504
505 fn resume(&mut self) -> Result<()> {
506 ensure!(
507 matches!(self.jtag_tap, JtagTap::RiscvTap),
508 JtagError::Tap(self.jtag_tap)
509 );
510 let response = self.send_tcl_cmd("resume")?;
511 if !response.is_empty() {
512 bail!("unexpected response: '{response}'");
513 }
514
515 Ok(())
516 }
517
518 fn resume_at(&mut self, addr: u32) -> Result<()> {
519 ensure!(
520 matches!(self.jtag_tap, JtagTap::RiscvTap),
521 JtagError::Tap(self.jtag_tap)
522 );
523 let cmd = format!("resume 0x{:x}", addr);
524 let response = self.send_tcl_cmd(&cmd)?;
525 if !response.is_empty() {
526 bail!("unexpected response: '{response}'");
527 }
528
529 Ok(())
530 }
531
532 fn reset(&mut self, run: bool) -> Result<()> {
533 ensure!(
534 matches!(self.jtag_tap, JtagTap::RiscvTap),
535 JtagError::Tap(self.jtag_tap)
536 );
537 let cmd = format!("reset {}", if run { "run" } else { "halt" });
538 let response = self.send_tcl_cmd(&cmd)?;
539 if !response.is_empty() {
540 bail!("unexpected response: '{response}'");
541 }
542
543 Ok(())
544 }
545
546 fn step(&mut self) -> Result<()> {
547 ensure!(
548 matches!(self.jtag_tap, JtagTap::RiscvTap),
549 JtagError::Tap(self.jtag_tap)
550 );
551 let response = self.send_tcl_cmd("step")?;
552 if !response.is_empty() {
553 bail!("unexpected response: '{response}'");
554 }
555
556 Ok(())
557 }
558
559 fn step_at(&mut self, addr: u32) -> Result<()> {
560 ensure!(
561 matches!(self.jtag_tap, JtagTap::RiscvTap),
562 JtagError::Tap(self.jtag_tap)
563 );
564 let cmd = format!("step 0x{:x}", addr);
565 let response = self.send_tcl_cmd(&cmd)?;
566 if !response.is_empty() {
567 bail!("unexpected response: '{response}'");
568 }
569
570 Ok(())
571 }
572
573 fn read_riscv_reg(&mut self, reg: &RiscvReg) -> Result<u32> {
574 ensure!(
575 matches!(self.jtag_tap, JtagTap::RiscvTap),
576 JtagError::Tap(self.jtag_tap)
577 );
578 self.read_register::<u32>(reg.name(), true)
579 }
580
581 fn write_riscv_reg(&mut self, reg: &RiscvReg, val: u32) -> Result<()> {
582 ensure!(
583 matches!(self.jtag_tap, JtagTap::RiscvTap),
584 JtagError::Tap(self.jtag_tap)
585 );
586 self.write_register(reg.name(), val)
587 }
588
589 fn set_breakpoint(&mut self, address: u32, hw: bool) -> Result<()> {
590 let cmd = format!("bp {:#x} 2{}", address, if hw { " hw" } else { "" });
591 let response = self.send_tcl_cmd(&cmd)?;
592 if !response.starts_with("breakpoint set at ") {
593 bail!("unexpected response: '{response}'");
594 }
595 Ok(())
596 }
597
598 fn remove_breakpoint(&mut self, addr: u32) -> Result<()> {
599 let cmd = format!("rbp {:#x}", addr);
600 let response = self.send_tcl_cmd(&cmd)?;
601 if !response.is_empty() {
602 bail!("unexpected response: '{response}'");
603 }
604 Ok(())
605 }
606
607 fn remove_all_breakpoints(&mut self) -> Result<()> {
608 let response = self.send_tcl_cmd("rbp all")?;
609 if !response.is_empty() {
610 bail!("unexpected response: '{response}'");
611 }
612 Ok(())
613 }
614}