opentitanlib/debug/
dmi.rs1use std::ops::{Deref, DerefMut};
6use std::time::Duration;
7
8use anyhow::{Result, bail, ensure};
9use thiserror::Error;
10
11use super::openocd::OpenOcd;
12use crate::test_utils::poll::poll_until;
13
14pub mod consts {
16 pub const DTMCS: u32 = 0x10;
18 pub const DMI: u32 = 0x11;
19
20 pub const DTMCS_VERSION_SHIFT: u32 = 0;
21 pub const DTMCS_ABITS_SHIFT: u32 = 4;
22 pub const DTMCS_DMIRESET_SHIFT: u32 = 16;
23
24 pub const DTMCS_VERSION_MASK: u32 = 0xf << DTMCS_VERSION_SHIFT;
25 pub const DTMCS_ABITS_MASK: u32 = 0x3f << DTMCS_ABITS_SHIFT;
26 pub const DTMCS_DMIRESET_MASK: u32 = 1 << DTMCS_DMIRESET_SHIFT;
27
28 pub const DTMCS_VERSION_0_13: u32 = 1;
29
30 pub const DMI_ADDRESS_SHIFT: u32 = 34;
31 pub const DMI_DATA_SHIFT: u32 = 2;
32
33 pub const DMI_OP_READ: u64 = 0x1;
34 pub const DMI_OP_WRITE: u64 = 0x2;
35
36 pub const DATA0: u32 = 0x04;
38 pub const DATA1: u32 = 0x05;
39 pub const DMCONTROL: u32 = 0x10;
40 pub const DMSTATUS: u32 = 0x11;
41 pub const HARTINFO: u32 = 0x12;
42 pub const ABSTRACTCS: u32 = 0x16;
43
44 pub const DMSTATUS_ANYHALTED_MASK: u32 = 1 << 8;
45 pub const DMSTATUS_ANYRUNNING_MASK: u32 = 1 << 10;
46 pub const DMSTATUS_ANYUNAVAIL_MASK: u32 = 1 << 12;
47 pub const DMSTATUS_ANYNONEXISTENT_MASK: u32 = 1 << 14;
48 pub const DMSTATUS_ANYRESUMEACK_MASK: u32 = 1 << 16;
49 pub const DMSTATUS_ANYHAVERESET_MASK: u32 = 1 << 18;
50 pub const DMSTATUS_ALLHAVERESET_MASK: u32 = 1 << 19;
51
52 pub const DMCONTROL_HASEL_SHIFT: u32 = 26;
53 pub const DMCONTROL_HARTSELHI_SHIFT: u32 = 6;
54 pub const DMCONTROL_HARTSELLO_SHIFT: u32 = 16;
55
56 pub const DMCONTROL_DMACTIVE_MASK: u32 = 1 << 0;
57 pub const DMCONTROL_NDMRESET_MASK: u32 = 1 << 1;
58 pub const DMCONTROL_ACKHAVERESET_MASK: u32 = 1 << 28;
59 pub const DMCONTROL_RESUMEREQ_MASK: u32 = 1 << 30;
60 pub const DMCONTROL_HALTREQ_MASK: u32 = 1 << 31;
61
62 pub const ABSTRACTCS_CMDERR_MASK: u32 = (1 << 11) - (1 << 8);
63 pub const ABSTRACTCS_BUSY_MASK: u32 = 1 << 12;
64
65 pub const ABSTRACTCS_CMDERR_SHIFT: u32 = 8;
66
67 pub const ABSTRACTCS_CMDERR_NONE: u32 = 0;
68}
69
70use consts::*;
71
72pub trait Dmi {
74 fn dmi_read(&mut self, addr: u32) -> Result<u32>;
76
77 fn dmi_write(&mut self, addr: u32, data: u32) -> Result<()>;
79}
80
81impl<T: Dmi> Dmi for &mut T {
82 fn dmi_read(&mut self, addr: u32) -> Result<u32> {
83 T::dmi_read(self, addr)
84 }
85
86 fn dmi_write(&mut self, addr: u32, data: u32) -> Result<()> {
87 T::dmi_write(self, addr, data)
88 }
89}
90
91pub struct OpenOcdDmi {
93 openocd: OpenOcd,
94 tap: String,
95 abits: u32,
96}
97
98impl OpenOcdDmi {
99 pub fn new(mut openocd: OpenOcd, tap: &str) -> Result<Self> {
105 let target_names = openocd.execute("target names")?;
106 ensure!(
107 target_names.is_empty(),
108 "Target must not be setup when accessing DMI directly"
109 );
110
111 openocd.irscan(tap, DTMCS)?;
112 let res = openocd.drscan(tap, 32, DTMCS_DMIRESET_MASK)?;
113 let version = (res & DTMCS_VERSION_MASK) >> DTMCS_VERSION_SHIFT;
114 let abits = (res & DTMCS_ABITS_MASK) >> DTMCS_ABITS_SHIFT;
115
116 ensure!(
117 version == DTMCS_VERSION_0_13,
118 "DTMCS indicates version other than 0.13"
119 );
120
121 openocd.irscan(tap, DMI)?;
122 Ok(Self {
123 openocd,
124 tap: tap.to_owned(),
125 abits,
126 })
127 }
128
129 fn dmi_op(&mut self, op: u64) -> Result<u64> {
130 let res = self
131 .openocd
132 .drscan(&self.tap, self.abits + DMI_ADDRESS_SHIFT, op)?;
133
134 ensure!(res == 0, "Unexpected DMI initial response {res:#x}");
136
137 self.openocd.execute("runtest 10")?;
143
144 let res = self
146 .openocd
147 .drscan(&self.tap, self.abits + DMI_ADDRESS_SHIFT, 0)?;
148 ensure!(res & 3 == 0, "DMI operation failed with {res:#x}");
149
150 ensure!(
152 res >> DMI_ADDRESS_SHIFT == op >> DMI_ADDRESS_SHIFT,
153 "DMI operation address mismatch {res:#x}"
154 );
155
156 Ok(res)
157 }
158}
159
160impl Dmi for OpenOcdDmi {
161 fn dmi_read(&mut self, addr: u32) -> Result<u32> {
162 let output = (self.dmi_op((addr as u64) << DMI_ADDRESS_SHIFT | DMI_OP_READ)?
163 >> DMI_DATA_SHIFT) as u32;
164 log::info!("DMI read {:#x} -> {:#x}", addr, output);
165 Ok(output)
166 }
167
168 fn dmi_write(&mut self, addr: u32, value: u32) -> Result<()> {
169 self.dmi_op(
170 (addr as u64) << DMI_ADDRESS_SHIFT | (value as u64) << DMI_DATA_SHIFT | DMI_OP_WRITE,
171 )?;
172 log::info!("DMI write {:#x} <- {:#x}", addr, value);
173 Ok(())
174 }
175}
176
177#[derive(Debug, Error)]
178pub enum DmiError {
179 #[error("Hart does not exist")]
180 Nonexistent,
181 #[error("Hart is not currently available")]
182 Unavailable,
183 #[error("Timeout waiting for hart to halt")]
184 WaitTimeout,
185}
186
187pub struct DmiDebugger<D> {
189 dmi: D,
190 hartsel_mask: Option<u32>,
191}
192
193impl<D> Deref for DmiDebugger<D> {
194 type Target = D;
195
196 fn deref(&self) -> &Self::Target {
197 &self.dmi
198 }
199}
200
201impl<D> DerefMut for DmiDebugger<D> {
202 fn deref_mut(&mut self) -> &mut Self::Target {
203 &mut self.dmi
204 }
205}
206
207impl<D: Dmi> DmiDebugger<D> {
208 pub fn new(dmi: D) -> Self {
209 Self {
210 dmi,
211 hartsel_mask: None,
212 }
213 }
214
215 pub fn hartsel_mask(&mut self) -> Result<u32> {
217 if self.hartsel_mask.is_none() {
218 let dm_control = 0 << DMCONTROL_HASEL_SHIFT
220 | 0x3ff << DMCONTROL_HARTSELLO_SHIFT
221 | 0x3ff << DMCONTROL_HARTSELHI_SHIFT
222 | DMCONTROL_DMACTIVE_MASK;
223 self.dmi.dmi_write(DMCONTROL, dm_control)?;
224
225 let dm_control = self.dmi.dmi_read(DMCONTROL)?;
228 let hart_select = (dm_control >> DMCONTROL_HARTSELLO_SHIFT) & 0x3ff
229 | ((dm_control >> DMCONTROL_HARTSELHI_SHIFT) & 0x3ff) << 10;
230
231 self.hartsel_mask = Some(hart_select);
232 }
233
234 Ok(self.hartsel_mask.unwrap())
235 }
236
237 pub fn select_hart(&mut self, hartid: u32) -> Result<DmiHart<'_, D>> {
239 if hartid >= (1 << 20) {
241 bail!("Invalid hartid: {hartid}");
242 }
243
244 if hartid != 0 {
246 let mask = self.hartsel_mask()?;
247 if (hartid & mask) != hartid {
248 bail!(DmiError::Nonexistent);
249 }
250 }
251
252 let hart_select = 0 << DMCONTROL_HASEL_SHIFT
253 | (hartid & 0x3ff) << DMCONTROL_HARTSELLO_SHIFT
254 | (hartid >> 10) << DMCONTROL_HARTSELHI_SHIFT
255 | DMCONTROL_DMACTIVE_MASK;
256 self.dmi.dmi_write(DMCONTROL, hart_select)?;
257
258 let mut hart = DmiHart {
259 debugger: self,
260 hart_select,
261 };
262
263 let dmstatus = hart.dmstatus()?;
264 if dmstatus & DMSTATUS_ANYNONEXISTENT_MASK != 0 {
265 bail!(DmiError::Nonexistent);
266 }
267 if dmstatus & DMSTATUS_ANYUNAVAIL_MASK != 0 {
268 bail!(DmiError::Unavailable);
269 }
270
271 Ok(hart)
272 }
273
274 pub fn data(&mut self, idx: u32) -> Result<u32> {
276 ensure!(idx < 12, "data register index out of range {:#x}", idx);
277 self.dmi_read(DATA0 + idx)
278 }
279
280 pub fn set_data(&mut self, idx: u32, data: u32) -> Result<()> {
282 ensure!(idx < 12, "data register index out of range {:#x}", idx);
283 self.dmi_write(DATA0 + idx, data)
284 }
285}
286
287pub struct DmiHart<'a, D> {
289 debugger: &'a mut DmiDebugger<D>,
290
291 hart_select: u32,
293}
294
295impl<D> Deref for DmiHart<'_, D> {
296 type Target = DmiDebugger<D>;
297
298 fn deref(&self) -> &Self::Target {
299 self.debugger
300 }
301}
302
303impl<D> DerefMut for DmiHart<'_, D> {
304 fn deref_mut(&mut self) -> &mut Self::Target {
305 self.debugger
306 }
307}
308
309pub struct HartState {
314 pub running: bool,
315 pub halted: bool,
316}
317
318impl<D: Dmi> DmiHart<'_, D> {
319 pub fn dmstatus(&mut self) -> Result<u32> {
321 let dmstatus = self.debugger.dmi_read(DMSTATUS)?;
322
323 if (dmstatus ^ (dmstatus >> 1))
326 & (DMSTATUS_ANYHALTED_MASK
327 | DMSTATUS_ANYRUNNING_MASK
328 | DMSTATUS_ANYUNAVAIL_MASK
329 | DMSTATUS_ANYNONEXISTENT_MASK
330 | DMSTATUS_ANYRESUMEACK_MASK
331 | DMSTATUS_ANYHAVERESET_MASK)
332 != 0
333 {
334 bail!(
335 "Invalid dmstatus {:#x}: any and all bits mismatch",
336 dmstatus
337 );
338 }
339
340 Ok(dmstatus)
341 }
342
343 pub fn set_dmcontrol(&mut self, value: u32) -> Result<()> {
345 self.debugger.dmi_write(DMCONTROL, value | self.hart_select)
346 }
347
348 pub fn hartinfo(&mut self) -> Result<u32> {
350 self.debugger.dmi_read(HARTINFO)
351 }
352
353 pub fn state(&mut self) -> Result<HartState> {
355 let dmstatus = self.dmstatus()?;
356 let running = dmstatus & DMSTATUS_ANYRUNNING_MASK != 0;
357 let halted = dmstatus & DMSTATUS_ANYHALTED_MASK != 0;
358 assert!(!(running && halted));
359 Ok(HartState { running, halted })
360 }
361
362 pub fn set_halt_request(&mut self, active: bool) -> Result<()> {
364 self.set_dmcontrol(if active { DMCONTROL_HALTREQ_MASK } else { 0 })
365 }
366
367 pub fn wait_halt(&mut self) -> Result<()> {
369 poll_until(Duration::from_secs(1), Duration::from_millis(50), || {
372 Ok(self.state()?.halted)
373 })
374 }
375
376 pub fn set_resume_request(&mut self, active: bool) -> Result<()> {
378 self.set_dmcontrol(if active { DMCONTROL_RESUMEREQ_MASK } else { 0 })
379 }
380
381 pub fn wait_resume(&mut self) -> Result<()> {
383 poll_until(Duration::from_secs(1), Duration::from_secs(1), || {
386 Ok(self.state()?.running)
387 })
388 }
389}