use std::ops::{Deref, DerefMut};
use std::time::Duration;
use anyhow::{bail, ensure, Result};
use thiserror::Error;
use super::openocd::OpenOcd;
use crate::test_utils::poll::poll_until;
pub mod consts {
pub const DTMCS: u32 = 0x10;
pub const DMI: u32 = 0x11;
pub const DTMCS_VERSION_SHIFT: u32 = 0;
pub const DTMCS_ABITS_SHIFT: u32 = 4;
pub const DTMCS_DMIRESET_SHIFT: u32 = 16;
pub const DTMCS_VERSION_MASK: u32 = 0xf << DTMCS_VERSION_SHIFT;
pub const DTMCS_ABITS_MASK: u32 = 0x3f << DTMCS_ABITS_SHIFT;
pub const DTMCS_DMIRESET_MASK: u32 = 1 << DTMCS_DMIRESET_SHIFT;
pub const DTMCS_VERSION_0_13: u32 = 1;
pub const DMI_ADDRESS_SHIFT: u32 = 34;
pub const DMI_DATA_SHIFT: u32 = 2;
pub const DMI_OP_READ: u64 = 0x1;
pub const DMI_OP_WRITE: u64 = 0x2;
pub const DATA0: u32 = 0x04;
pub const DATA1: u32 = 0x05;
pub const DMCONTROL: u32 = 0x10;
pub const DMSTATUS: u32 = 0x11;
pub const HARTINFO: u32 = 0x12;
pub const ABSTRACTCS: u32 = 0x16;
pub const DMSTATUS_ANYHALTED_MASK: u32 = 1 << 8;
pub const DMSTATUS_ANYRUNNING_MASK: u32 = 1 << 10;
pub const DMSTATUS_ANYUNAVAIL_MASK: u32 = 1 << 12;
pub const DMSTATUS_ANYNONEXISTENT_MASK: u32 = 1 << 14;
pub const DMSTATUS_ANYRESUMEACK_MASK: u32 = 1 << 16;
pub const DMSTATUS_ANYHAVERESET_MASK: u32 = 1 << 18;
pub const DMSTATUS_ALLHAVERESET_MASK: u32 = 1 << 19;
pub const DMCONTROL_HASEL_SHIFT: u32 = 26;
pub const DMCONTROL_HARTSELHI_SHIFT: u32 = 6;
pub const DMCONTROL_HARTSELLO_SHIFT: u32 = 16;
pub const DMCONTROL_DMACTIVE_MASK: u32 = 1 << 0;
pub const DMCONTROL_NDMRESET_MASK: u32 = 1 << 1;
pub const DMCONTROL_ACKHAVERESET_MASK: u32 = 1 << 28;
pub const DMCONTROL_RESUMEREQ_MASK: u32 = 1 << 30;
pub const DMCONTROL_HALTREQ_MASK: u32 = 1 << 31;
pub const ABSTRACTCS_CMDERR_MASK: u32 = (1 << 11) - (1 << 8);
pub const ABSTRACTCS_BUSY_MASK: u32 = 1 << 12;
pub const ABSTRACTCS_CMDERR_SHIFT: u32 = 8;
pub const ABSTRACTCS_CMDERR_NONE: u32 = 0;
}
use consts::*;
pub trait Dmi {
fn dmi_read(&mut self, addr: u32) -> Result<u32>;
fn dmi_write(&mut self, addr: u32, data: u32) -> Result<()>;
}
impl<T: Dmi> Dmi for &mut T {
fn dmi_read(&mut self, addr: u32) -> Result<u32> {
T::dmi_read(self, addr)
}
fn dmi_write(&mut self, addr: u32, data: u32) -> Result<()> {
T::dmi_write(self, addr, data)
}
}
pub struct OpenOcdDmi {
openocd: OpenOcd,
tap: String,
abits: u32,
}
impl OpenOcdDmi {
pub fn new(mut openocd: OpenOcd, tap: &str) -> Result<Self> {
let target_names = openocd.execute("target names")?;
ensure!(
target_names.is_empty(),
"Target must not be setup when accessing DMI directly"
);
openocd.irscan(tap, DTMCS)?;
let res = openocd.drscan(tap, 32, DTMCS_DMIRESET_MASK)?;
let version = (res & DTMCS_VERSION_MASK) >> DTMCS_VERSION_SHIFT;
let abits = (res & DTMCS_ABITS_MASK) >> DTMCS_ABITS_SHIFT;
ensure!(
version == DTMCS_VERSION_0_13,
"DTMCS indicates version other than 0.13"
);
openocd.irscan(tap, DMI)?;
Ok(Self {
openocd,
tap: tap.to_owned(),
abits,
})
}
fn dmi_op(&mut self, op: u64) -> Result<u64> {
let res = self
.openocd
.drscan(&self.tap, self.abits + DMI_ADDRESS_SHIFT, op)?;
ensure!(res == 0, "Unexpected DMI initial response {res:#x}");
self.openocd.execute("runtest 10")?;
let res = self
.openocd
.drscan(&self.tap, self.abits + DMI_ADDRESS_SHIFT, 0)?;
ensure!(res & 3 == 0, "DMI operation failed with {res:#x}");
ensure!(
res >> DMI_ADDRESS_SHIFT == op >> DMI_ADDRESS_SHIFT,
"DMI operation address mismatch {res:#x}"
);
Ok(res)
}
}
impl Dmi for OpenOcdDmi {
fn dmi_read(&mut self, addr: u32) -> Result<u32> {
let output = (self.dmi_op((addr as u64) << DMI_ADDRESS_SHIFT | DMI_OP_READ)?
>> DMI_DATA_SHIFT) as u32;
log::info!("DMI read {:#x} -> {:#x}", addr, output);
Ok(output)
}
fn dmi_write(&mut self, addr: u32, value: u32) -> Result<()> {
self.dmi_op(
(addr as u64) << DMI_ADDRESS_SHIFT | (value as u64) << DMI_DATA_SHIFT | DMI_OP_WRITE,
)?;
log::info!("DMI write {:#x} <- {:#x}", addr, value);
Ok(())
}
}
#[derive(Debug, Error)]
pub enum DmiError {
#[error("Hart does not exist")]
Nonexistent,
#[error("Hart is not currently available")]
Unavailable,
#[error("Timeout waiting for hart to halt")]
WaitTimeout,
}
pub struct DmiDebugger<D> {
dmi: D,
hartsel_mask: Option<u32>,
}
impl<D> Deref for DmiDebugger<D> {
type Target = D;
fn deref(&self) -> &Self::Target {
&self.dmi
}
}
impl<D> DerefMut for DmiDebugger<D> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.dmi
}
}
impl<D: Dmi> DmiDebugger<D> {
pub fn new(dmi: D) -> Self {
Self {
dmi,
hartsel_mask: None,
}
}
pub fn hartsel_mask(&mut self) -> Result<u32> {
if self.hartsel_mask.is_none() {
let dm_control = 0 << DMCONTROL_HASEL_SHIFT
| 0x3ff << DMCONTROL_HARTSELLO_SHIFT
| 0x3ff << DMCONTROL_HARTSELHI_SHIFT
| DMCONTROL_DMACTIVE_MASK;
self.dmi.dmi_write(DMCONTROL, dm_control)?;
let dm_control = self.dmi.dmi_read(DMCONTROL)?;
let hart_select = (dm_control >> DMCONTROL_HARTSELLO_SHIFT) & 0x3ff
| ((dm_control >> DMCONTROL_HARTSELHI_SHIFT) & 0x3ff) << 10;
self.hartsel_mask = Some(hart_select);
}
Ok(self.hartsel_mask.unwrap())
}
pub fn select_hart(&mut self, hartid: u32) -> Result<DmiHart<'_, D>> {
if hartid >= (1 << 20) {
bail!("Invalid hartid: {hartid}");
}
if hartid != 0 {
let mask = self.hartsel_mask()?;
if (hartid & mask) != hartid {
bail!(DmiError::Nonexistent);
}
}
let hart_select = 0 << DMCONTROL_HASEL_SHIFT
| (hartid & 0x3ff) << DMCONTROL_HARTSELLO_SHIFT
| (hartid >> 10) << DMCONTROL_HARTSELHI_SHIFT
| DMCONTROL_DMACTIVE_MASK;
self.dmi.dmi_write(DMCONTROL, hart_select)?;
let mut hart = DmiHart {
debugger: self,
hart_select,
};
let dmstatus = hart.dmstatus()?;
if dmstatus & DMSTATUS_ANYNONEXISTENT_MASK != 0 {
bail!(DmiError::Nonexistent);
}
if dmstatus & DMSTATUS_ANYUNAVAIL_MASK != 0 {
bail!(DmiError::Unavailable);
}
Ok(hart)
}
pub fn data(&mut self, idx: u32) -> Result<u32> {
ensure!(idx < 12, "data register index out of range {:#x}", idx);
self.dmi_read(DATA0 + idx)
}
pub fn set_data(&mut self, idx: u32, data: u32) -> Result<()> {
ensure!(idx < 12, "data register index out of range {:#x}", idx);
self.dmi_write(DATA0 + idx, data)
}
}
pub struct DmiHart<'a, D> {
debugger: &'a mut DmiDebugger<D>,
hart_select: u32,
}
impl<D> Deref for DmiHart<'_, D> {
type Target = DmiDebugger<D>;
fn deref(&self) -> &Self::Target {
self.debugger
}
}
impl<D> DerefMut for DmiHart<'_, D> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.debugger
}
}
pub struct HartState {
pub running: bool,
pub halted: bool,
}
impl<D: Dmi> DmiHart<'_, D> {
pub fn dmstatus(&mut self) -> Result<u32> {
let dmstatus = self.debugger.dmi_read(DMSTATUS)?;
if (dmstatus ^ (dmstatus >> 1))
& (DMSTATUS_ANYHALTED_MASK
| DMSTATUS_ANYRUNNING_MASK
| DMSTATUS_ANYUNAVAIL_MASK
| DMSTATUS_ANYNONEXISTENT_MASK
| DMSTATUS_ANYRESUMEACK_MASK
| DMSTATUS_ANYHAVERESET_MASK)
!= 0
{
bail!(
"Invalid dmstatus {:#x}: any and all bits mismatch",
dmstatus
);
}
Ok(dmstatus)
}
pub fn set_dmcontrol(&mut self, value: u32) -> Result<()> {
self.debugger.dmi_write(DMCONTROL, value | self.hart_select)
}
pub fn hartinfo(&mut self) -> Result<u32> {
self.debugger.dmi_read(HARTINFO)
}
pub fn state(&mut self) -> Result<HartState> {
let dmstatus = self.dmstatus()?;
let running = dmstatus & DMSTATUS_ANYRUNNING_MASK != 0;
let halted = dmstatus & DMSTATUS_ANYHALTED_MASK != 0;
assert!(!(running && halted));
Ok(HartState { running, halted })
}
pub fn set_halt_request(&mut self, active: bool) -> Result<()> {
self.set_dmcontrol(if active { DMCONTROL_HALTREQ_MASK } else { 0 })
}
pub fn wait_halt(&mut self) -> Result<()> {
poll_until(Duration::from_secs(1), Duration::from_millis(50), || {
Ok(self.state()?.halted)
})
}
pub fn set_resume_request(&mut self, active: bool) -> Result<()> {
self.set_dmcontrol(if active { DMCONTROL_RESUMEREQ_MASK } else { 0 })
}
pub fn wait_resume(&mut self) -> Result<()> {
poll_until(Duration::from_secs(1), Duration::from_secs(1), || {
Ok(self.state()?.running)
})
}
}