opentitanlib/debug/
dmi.rs

1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
5use 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
14/// Constants defined by RISC-V Debug Specification 0.13.
15pub mod consts {
16    // JTAG registers.
17    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    // Debug module registers.
37    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
72/// Debug module interface (DMI) abstraction.
73pub trait Dmi {
74    /// Read a DMI register.
75    fn dmi_read(&mut self, addr: u32) -> Result<u32>;
76
77    /// Write a DMI register.
78    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
91/// DMI interface via OpenOCD.
92pub struct OpenOcdDmi {
93    openocd: OpenOcd,
94    tap: String,
95    abits: u32,
96}
97
98impl OpenOcdDmi {
99    /// Create a new DMI interface via OpenOCD.
100    ///
101    /// This should be an OpenOCD instance with JTAG scan chain already set up,
102    /// but not with target set up. If target has been set up, OpenOCD will access
103    /// DMI registers on its own, which will interfere with raw DMI operations.
104    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        // We just scanned into the DMI register, so the scanned result should be empty.
135        ensure!(res == 0, "Unexpected DMI initial response {res:#x}");
136
137        // Run the DMI operation.
138        // TODO: The proper way is to run for a small number of cycles, and then try to read the result.
139        // If an error occurs indicating that the number of cycles are not sufficient, then increase that number
140        // and try again. Here we just use a large enough number to avoid having to implement the retry logic,
141        // which is good enough for now.
142        self.openocd.execute("runtest 10")?;
143
144        // Read the result.
145        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        // Double check the address matches.
151        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
187/// A debugger that communicates with the target via RISC-V Debug Module Interface (DMI).
188pub 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    /// Obtain bits valid in hartsel as a bitmask.
216    pub fn hartsel_mask(&mut self) -> Result<u32> {
217        if self.hartsel_mask.is_none() {
218            // Write all 1s to hartsel.
219            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            // This is a WARL register so after writing 1, the readback value would be
226            // the mask for valid bits in the register.
227            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    /// Selects a hart to debug.
238    pub fn select_hart(&mut self, hartid: u32) -> Result<DmiHart<'_, D>> {
239        // The hart selection is up to 20 bits.
240        if hartid >= (1 << 20) {
241            bail!("Invalid hartid: {hartid}");
242        }
243
244        // When selecting non-zero hart, ensure written bit to HARTSEL is legal.
245        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    /// Read a data register from DMI.
275    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    /// Write a data register from DMI.
281    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
287/// A DMI debugger with specific hart selected.
288pub struct DmiHart<'a, D> {
289    debugger: &'a mut DmiDebugger<D>,
290
291    /// The value of DMCONTROL with hasel, hartsello and hartselhi set.
292    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
309/// State of the hart.
310///
311/// If both `running` and `halted` are false, then the hart is in the process of transitioning between
312/// the two states (i.e. resuming or halting).
313pub struct HartState {
314    pub running: bool,
315    pub halted: bool,
316}
317
318impl<D: Dmi> DmiHart<'_, D> {
319    /// Read `dmstatus` for the selected hart.
320    pub fn dmstatus(&mut self) -> Result<u32> {
321        let dmstatus = self.debugger.dmi_read(DMSTATUS)?;
322
323        // `dmstatus` register have fields for any hart and all harts. If only a single hart is selected,
324        // then the "all" and "any" values should match. This performs a sanity check.
325        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    /// Write to dmcontrol without affecting hart selection.
344    pub fn set_dmcontrol(&mut self, value: u32) -> Result<()> {
345        self.debugger.dmi_write(DMCONTROL, value | self.hart_select)
346    }
347
348    /// Read hart info of the selected hart.
349    pub fn hartinfo(&mut self) -> Result<u32> {
350        self.debugger.dmi_read(HARTINFO)
351    }
352
353    /// Return the state of the hart.
354    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    /// Set the halt request bit.
363    pub fn set_halt_request(&mut self, active: bool) -> Result<()> {
364        self.set_dmcontrol(if active { DMCONTROL_HALTREQ_MASK } else { 0 })
365    }
366
367    /// Wait for the hart to halt.
368    pub fn wait_halt(&mut self) -> Result<()> {
369        // Per RISC-V debug specification, harts must respond within 1 second of receiving a halt or
370        // resume request.
371        poll_until(Duration::from_secs(1), Duration::from_millis(50), || {
372            Ok(self.state()?.halted)
373        })
374    }
375
376    /// Set the resume request bit.
377    pub fn set_resume_request(&mut self, active: bool) -> Result<()> {
378        self.set_dmcontrol(if active { DMCONTROL_RESUMEREQ_MASK } else { 0 })
379    }
380
381    /// Wait for the hart to resume.
382    pub fn wait_resume(&mut self) -> Result<()> {
383        // Per RISC-V debug specification, harts must respond within 1 second of receiving a halt or
384        // resume request.
385        poll_until(Duration::from_secs(1), Duration::from_secs(1), || {
386            Ok(self.state()?.running)
387        })
388    }
389}