use std::{
collections::HashMap,
time::{Duration, Instant},
};
use crate::{
architecture::xtensa::{
arch::{instruction::Instruction, CpuRegister, Register, SpecialRegister},
xdm::XdmState,
},
probe::{DebugProbeError, DeferredResultIndex, JTAGAccess},
BreakpointCause, Error as ProbeRsError, HaltReason, MemoryInterface,
};
use super::xdm::{Error as XdmError, Xdm};
#[derive(thiserror::Error, Debug, docsplay::Display)]
pub enum XtensaError {
DebugProbe(#[from] DebugProbeError),
XdmError(#[from] XdmError),
Timeout,
NoXtensaTarget,
RegisterNotAvailable,
BatchedResultNotAvailable,
}
impl From<XtensaError> for DebugProbeError {
fn from(e: XtensaError) -> DebugProbeError {
match e {
XtensaError::DebugProbe(err) => err,
other_error => DebugProbeError::Other(other_error.into()),
}
}
}
impl From<XtensaError> for ProbeRsError {
fn from(err: XtensaError) -> Self {
match err {
XtensaError::DebugProbe(e) => e.into(),
other => ProbeRsError::Xtensa(other),
}
}
}
#[derive(Clone, Copy)]
#[allow(unused)]
enum DebugLevel {
L2 = 2,
L3 = 3,
L4 = 4,
L5 = 5,
L6 = 6,
L7 = 7,
}
impl DebugLevel {
pub fn pc(self) -> SpecialRegister {
match self {
DebugLevel::L2 => SpecialRegister::Epc2,
DebugLevel::L3 => SpecialRegister::Epc3,
DebugLevel::L4 => SpecialRegister::Epc4,
DebugLevel::L5 => SpecialRegister::Epc5,
DebugLevel::L6 => SpecialRegister::Epc6,
DebugLevel::L7 => SpecialRegister::Epc7,
}
}
pub fn ps(self) -> SpecialRegister {
match self {
DebugLevel::L2 => SpecialRegister::Eps2,
DebugLevel::L3 => SpecialRegister::Eps3,
DebugLevel::L4 => SpecialRegister::Eps4,
DebugLevel::L5 => SpecialRegister::Eps5,
DebugLevel::L6 => SpecialRegister::Eps6,
DebugLevel::L7 => SpecialRegister::Eps7,
}
}
}
pub(super) struct XtensaInterfaceState {
saved_registers: HashMap<Register, Option<DeferredResultIndex>>,
is_halted: bool,
hw_breakpoint_num: u32,
debug_level: DebugLevel,
}
impl Default for XtensaInterfaceState {
fn default() -> Self {
Self {
saved_registers: Default::default(),
is_halted: Default::default(),
hw_breakpoint_num: 2,
debug_level: DebugLevel::L6,
}
}
}
#[derive(Default)]
pub struct XtensaDebugInterfaceState {
interface_state: XtensaInterfaceState,
xdm_state: XdmState,
}
pub struct XtensaCommunicationInterface<'probe> {
pub(super) xdm: Xdm<'probe>,
state: &'probe mut XtensaInterfaceState,
}
impl<'probe> XtensaCommunicationInterface<'probe> {
pub fn new(
probe: &'probe mut dyn JTAGAccess,
state: &'probe mut XtensaDebugInterfaceState,
) -> Self {
let XtensaDebugInterfaceState {
interface_state,
xdm_state,
} = state;
let xdm = Xdm::new(probe, xdm_state);
Self {
xdm,
state: interface_state,
}
}
pub fn read_idcode(&mut self) -> Result<u32, DebugProbeError> {
Ok(self.xdm.read_idcode()?)
}
pub fn enter_debug_mode(&mut self) -> Result<(), XtensaError> {
self.xdm.enter_debug_mode()?;
self.state.is_halted = self.xdm.status()?.stopped();
Ok(())
}
pub fn available_breakpoint_units(&self) -> u32 {
self.state.hw_breakpoint_num
}
pub fn halt(&mut self) -> Result<(), XtensaError> {
tracing::debug!("Halting core");
self.xdm.halt()
}
pub fn is_halted(&mut self) -> Result<bool, XtensaError> {
if !self.state.is_halted {
self.state.is_halted = self.xdm.status()?.stopped();
}
Ok(self.state.is_halted)
}
pub fn wait_for_core_halted(&mut self, timeout: Duration) -> Result<(), XtensaError> {
let now = Instant::now();
while !self.is_halted()? {
if now.elapsed() > timeout {
tracing::warn!("Timeout waiting for core to halt");
return Err(XtensaError::Timeout);
}
std::thread::sleep(Duration::from_millis(1));
}
tracing::debug!("Core halted");
Ok(())
}
pub fn step(&mut self) -> Result<(), XtensaError> {
self.schedule_write_register(ICountLevel(self.state.debug_level as u32))?;
self.schedule_write_register(ICount(-2_i32 as u32))?;
self.resume()?;
self.wait_for_core_halted(Duration::from_millis(100))?;
self.schedule_write_register(ICountLevel(self.state.debug_level as u32 + 1))?;
Ok(())
}
pub fn resume(&mut self) -> Result<(), XtensaError> {
tracing::debug!("Resuming core");
self.state.is_halted = false;
self.xdm.resume()?;
Ok(())
}
fn schedule_read_cpu_register(&mut self, register: CpuRegister) -> DeferredResultIndex {
self.xdm
.schedule_execute_instruction(Instruction::Wsr(SpecialRegister::Ddr, register));
self.xdm.schedule_read_ddr()
}
fn schedule_read_special_register(
&mut self,
register: SpecialRegister,
) -> Result<DeferredResultIndex, XtensaError> {
let save_key = self.save_register(CpuRegister::A3)?;
self.xdm
.schedule_execute_instruction(Instruction::Rsr(register, CpuRegister::A3));
let reader = self.schedule_read_cpu_register(CpuRegister::A3);
self.restore_register(save_key)?;
Ok(reader)
}
fn schedule_write_special_register(
&mut self,
register: SpecialRegister,
value: u32,
) -> Result<(), XtensaError> {
tracing::debug!("Writing special register: {:?}", register);
let save_key = self.save_register(CpuRegister::A3)?;
self.xdm.schedule_write_ddr(value);
self.xdm
.schedule_execute_instruction(Instruction::Rsr(SpecialRegister::Ddr, CpuRegister::A3));
self.xdm
.schedule_execute_instruction(Instruction::Wsr(register, CpuRegister::A3));
self.restore_register(save_key)?;
Ok(())
}
#[tracing::instrument(skip(self))]
fn schedule_write_cpu_register(
&mut self,
register: CpuRegister,
value: u32,
) -> Result<(), XtensaError> {
tracing::debug!("Writing {:x} to register: {:?}", value, register);
self.xdm.schedule_write_ddr(value);
self.xdm
.schedule_execute_instruction(Instruction::Rsr(SpecialRegister::Ddr, register));
Ok(())
}
pub fn read_register<R: TypedRegister>(&mut self) -> Result<R, XtensaError> {
let value = self.read_register_untyped(R::register())?;
Ok(R::from_u32(value))
}
pub fn schedule_read_register<R: TypedRegister>(
&mut self,
) -> Result<DeferredResultIndex, XtensaError> {
self.schedule_read_register_untyped(R::register())
}
pub fn write_register<R: TypedRegister>(&mut self, reg: R) -> Result<(), XtensaError> {
self.write_register_untyped(R::register(), reg.as_u32())?;
Ok(())
}
pub fn schedule_write_register<R: TypedRegister>(&mut self, reg: R) -> Result<(), XtensaError> {
self.schedule_write_register_untyped(R::register(), reg.as_u32())?;
Ok(())
}
pub fn schedule_read_register_untyped(
&mut self,
register: impl Into<Register>,
) -> Result<DeferredResultIndex, XtensaError> {
match register.into() {
Register::Cpu(register) => Ok(self.schedule_read_cpu_register(register)),
Register::Special(register) => self.schedule_read_special_register(register),
Register::CurrentPc => self.schedule_read_special_register(self.state.debug_level.pc()),
Register::CurrentPs => self.schedule_read_special_register(self.state.debug_level.ps()),
}
}
pub fn read_register_untyped(
&mut self,
register: impl Into<Register>,
) -> Result<u32, XtensaError> {
let reader = self.schedule_read_register_untyped(register)?;
Ok(self.xdm.read_deferred_result(reader)?.into_u32())
}
pub fn schedule_write_register_untyped(
&mut self,
register: impl Into<Register>,
value: u32,
) -> Result<(), XtensaError> {
match register.into() {
Register::Cpu(register) => self.schedule_write_cpu_register(register, value),
Register::Special(register) => self.schedule_write_special_register(register, value),
Register::CurrentPc => {
self.schedule_write_special_register(self.state.debug_level.pc(), value)
}
Register::CurrentPs => {
self.schedule_write_special_register(self.state.debug_level.ps(), value)
}
}
}
pub fn write_register_untyped(
&mut self,
register: impl Into<Register>,
value: u32,
) -> Result<(), XtensaError> {
self.schedule_write_register_untyped(register, value)?;
self.xdm.execute()
}
#[tracing::instrument(skip(self, register), fields(register))]
fn save_register(
&mut self,
register: impl Into<Register>,
) -> Result<Option<Register>, XtensaError> {
let register = register.into();
tracing::Span::current().record("register", &format!("{register:?}"));
if matches!(
register,
Register::Special(
SpecialRegister::Ddr | SpecialRegister::ICount | SpecialRegister::ICountLevel
)
) {
return Ok(None);
}
let is_saved = self.state.saved_registers.contains_key(®ister);
if is_saved {
return Ok(None);
}
tracing::debug!("Saving register: {:?}", register);
let value = self.schedule_read_register_untyped(register)?;
self.state.saved_registers.insert(register, Some(value));
Ok(Some(register))
}
#[tracing::instrument(skip(self))]
fn restore_register(&mut self, key: Option<Register>) -> Result<(), XtensaError> {
let Some(key) = key else {
return Ok(());
};
tracing::debug!("Restoring register: {:?}", key);
if let Some(value) = self.state.saved_registers.remove(&key) {
let reader = value.unwrap();
let value = self.xdm.read_deferred_result(reader)?.into_u32();
self.write_register_untyped(key, value)?;
}
Ok(())
}
#[tracing::instrument(skip(self))]
pub(super) fn restore_registers(&mut self) -> Result<(), XtensaError> {
tracing::debug!("Restoring registers");
let dirty_regs = self
.state
.saved_registers
.keys()
.copied()
.collect::<Vec<_>>();
let dirty_count = dirty_regs.len();
let mut restore_scratch = None;
for register in dirty_regs {
let reader = self
.state
.saved_registers
.get_mut(®ister)
.unwrap()
.take()
.unwrap_or_else(|| {
panic!(
"Failed to get original value of dirty register {:?}. This is a bug.",
register
)
});
let value = self.xdm.read_deferred_result(reader)?.into_u32();
if register == Register::Cpu(CpuRegister::A3) {
restore_scratch = Some(value);
} else {
self.schedule_write_register_untyped(register, value)?;
}
}
if self.state.saved_registers.len() != dirty_count {
if let Some(reader) = self
.state
.saved_registers
.get_mut(&Register::Cpu(CpuRegister::A3))
{
if let Some(reader) = reader.take() {
let value = self.xdm.read_deferred_result(reader)?.into_u32();
restore_scratch = Some(value);
}
}
}
if let Some(value) = restore_scratch {
self.schedule_write_register_untyped(CpuRegister::A3, value)?;
}
self.state.saved_registers.clear();
self.xdm.execute()
}
fn read_memory(&mut self, address: u64, mut dst: &mut [u8]) -> Result<(), XtensaError> {
tracing::debug!("Reading {} bytes from address {:08x}", dst.len(), address);
if dst.is_empty() {
return Ok(());
}
let was_halted = self.is_halted()?;
if !was_halted {
self.xdm.schedule_halt();
self.wait_for_core_halted(Duration::from_millis(100))?;
}
let key = self.save_register(CpuRegister::A3)?;
self.schedule_write_cpu_register(CpuRegister::A3, address as u32 & !0x3)?;
self.xdm
.schedule_execute_instruction(Instruction::Lddr32P(CpuRegister::A3));
let mut to_read = dst.len();
let first_read = if address % 4 != 0 {
let offset = address as usize % 4;
let first_read = if offset + to_read <= 4 {
self.xdm.schedule_read_ddr()
} else {
self.xdm.schedule_read_ddr_and_execute()
};
let bytes_to_copy = (4 - offset).min(to_read);
to_read -= bytes_to_copy;
Some((first_read, offset, bytes_to_copy))
} else {
None
};
let mut aligned_reads = vec![];
if to_read > 0 {
let words = if to_read % 4 == 0 {
to_read / 4
} else {
to_read / 4 + 1
};
for _ in 0..words - 1 {
aligned_reads.push(self.xdm.schedule_read_ddr_and_execute());
}
aligned_reads.push(self.xdm.schedule_read_ddr());
};
if let Some((read, offset, bytes_to_copy)) = first_read {
let word = self
.xdm
.read_deferred_result(read)?
.into_u32()
.to_le_bytes();
dst[..bytes_to_copy].copy_from_slice(&word[offset..][..bytes_to_copy]);
dst = &mut dst[bytes_to_copy..];
}
for read in aligned_reads {
let word = self
.xdm
.read_deferred_result(read)?
.into_u32()
.to_le_bytes();
let bytes = dst.len().min(4);
dst[..bytes].copy_from_slice(&word[..bytes]);
dst = &mut dst[bytes..];
}
self.restore_register(key)?;
if !was_halted {
self.resume()?;
}
Ok(())
}
fn write_memory_unaligned8(&mut self, address: u32, data: &[u8]) -> Result<(), XtensaError> {
if data.is_empty() {
return Ok(());
}
let key = self.save_register(CpuRegister::A3)?;
let offset = address as usize % 4;
let aligned_address = address & !0x3;
assert!(
offset + data.len() <= 4,
"Trying to write data crossing a word boundary"
);
let data = if offset == 0 && data.len() == 4 {
data.try_into().unwrap()
} else {
let mut word = [0; 4];
self.read_memory(aligned_address as u64, &mut word)?;
word[offset..][..data.len()].copy_from_slice(data);
word
};
self.schedule_write_register_untyped(CpuRegister::A3, aligned_address)?;
self.xdm.schedule_write_ddr(u32::from_le_bytes(data));
self.xdm
.schedule_execute_instruction(Instruction::Sddr32P(CpuRegister::A3));
self.restore_register(key)?;
self.xdm.execute()
}
pub(crate) fn write_memory(&mut self, address: u64, data: &[u8]) -> Result<(), XtensaError> {
tracing::debug!("Writing {} bytes to address {:08x}", data.len(), address);
if data.is_empty() {
return Ok(());
}
let was_halted = self.is_halted()?;
if !was_halted {
self.xdm.schedule_halt();
self.wait_for_core_halted(Duration::from_millis(100))?;
}
let key = self.save_register(CpuRegister::A3)?;
let address = address as u32;
let mut addr = address;
let mut buffer = data;
if addr % 4 != 0 {
let unaligned_bytes = (4 - (addr % 4) as usize).min(buffer.len());
self.write_memory_unaligned8(addr, &buffer[..unaligned_bytes])?;
buffer = &buffer[unaligned_bytes..];
addr += unaligned_bytes as u32;
}
if buffer.len() > 4 {
self.schedule_write_register_untyped(CpuRegister::A3, addr)?;
self.xdm
.schedule_write_instruction(Instruction::Sddr32P(CpuRegister::A3));
while buffer.len() > 4 {
let mut word = [0; 4];
word[..].copy_from_slice(&buffer[..4]);
let word = u32::from_le_bytes(word);
self.xdm.schedule_write_ddr_and_execute(word);
buffer = &buffer[4..];
addr += 4;
}
}
if !buffer.is_empty() {
self.write_memory_unaligned8(addr, buffer)?;
}
self.restore_register(key)?;
self.xdm.execute()?;
if !was_halted {
self.resume()?;
}
Ok(())
}
pub(crate) fn reset_and_halt(&mut self, timeout: Duration) -> Result<(), XtensaError> {
self.xdm.reset_and_halt()?;
self.wait_for_core_halted(timeout)?;
Ok(())
}
}
unsafe trait DataType: Sized {}
unsafe impl DataType for u8 {}
unsafe impl DataType for u16 {}
unsafe impl DataType for u32 {}
unsafe impl DataType for u64 {}
fn as_bytes<T: DataType>(data: &[T]) -> &[u8] {
unsafe { std::slice::from_raw_parts(data.as_ptr() as *mut u8, std::mem::size_of_val(data)) }
}
fn as_bytes_mut<T: DataType>(data: &mut [T]) -> &mut [u8] {
unsafe {
std::slice::from_raw_parts_mut(data.as_mut_ptr() as *mut u8, std::mem::size_of_val(data))
}
}
impl<'probe> MemoryInterface for XtensaCommunicationInterface<'probe> {
fn read(&mut self, address: u64, dst: &mut [u8]) -> Result<(), crate::Error> {
self.read_memory(address, dst)?;
Ok(())
}
fn supports_native_64bit_access(&mut self) -> bool {
false
}
fn read_word_64(&mut self, address: u64) -> anyhow::Result<u64, crate::Error> {
let mut out = [0; 8];
self.read(address, &mut out)?;
Ok(u64::from_le_bytes(out))
}
fn read_word_32(&mut self, address: u64) -> Result<u32, crate::Error> {
let mut out = [0; 4];
self.read(address, &mut out)?;
Ok(u32::from_le_bytes(out))
}
fn read_word_16(&mut self, address: u64) -> Result<u16, crate::Error> {
let mut out = [0; 2];
self.read(address, &mut out)?;
Ok(u16::from_le_bytes(out))
}
fn read_word_8(&mut self, address: u64) -> anyhow::Result<u8, crate::Error> {
let mut out = 0;
self.read(address, std::slice::from_mut(&mut out))?;
Ok(out)
}
fn read_64(&mut self, address: u64, data: &mut [u64]) -> anyhow::Result<(), crate::Error> {
self.read_8(address, as_bytes_mut(data))
}
fn read_32(&mut self, address: u64, data: &mut [u32]) -> anyhow::Result<(), crate::Error> {
self.read_8(address, as_bytes_mut(data))
}
fn read_16(&mut self, address: u64, data: &mut [u16]) -> anyhow::Result<(), crate::Error> {
self.read_8(address, as_bytes_mut(data))
}
fn read_8(&mut self, address: u64, data: &mut [u8]) -> anyhow::Result<(), crate::Error> {
self.read(address, data)
}
fn write(&mut self, address: u64, data: &[u8]) -> Result<(), crate::Error> {
self.write_memory(address, data)?;
Ok(())
}
fn write_word_64(&mut self, address: u64, data: u64) -> anyhow::Result<(), crate::Error> {
self.write(address, &data.to_le_bytes())
}
fn write_word_32(&mut self, address: u64, data: u32) -> anyhow::Result<(), crate::Error> {
self.write(address, &data.to_le_bytes())
}
fn write_word_16(&mut self, address: u64, data: u16) -> anyhow::Result<(), crate::Error> {
self.write(address, &data.to_le_bytes())
}
fn write_word_8(&mut self, address: u64, data: u8) -> anyhow::Result<(), crate::Error> {
self.write(address, &[data])
}
fn write_64(&mut self, address: u64, data: &[u64]) -> anyhow::Result<(), crate::Error> {
self.write_8(address, as_bytes(data))
}
fn write_32(&mut self, address: u64, data: &[u32]) -> anyhow::Result<(), crate::Error> {
self.write_8(address, as_bytes(data))
}
fn write_16(&mut self, address: u64, data: &[u16]) -> anyhow::Result<(), crate::Error> {
self.write_8(address, as_bytes(data))
}
fn write_8(&mut self, address: u64, data: &[u8]) -> anyhow::Result<(), crate::Error> {
self.write(address, data)
}
fn supports_8bit_transfers(&self) -> anyhow::Result<bool, crate::Error> {
Ok(true)
}
fn flush(&mut self) -> anyhow::Result<(), crate::Error> {
Ok(())
}
}
pub trait TypedRegister: Copy {
fn register() -> Register;
fn from_u32(value: u32) -> Self;
fn as_u32(self) -> u32;
}
macro_rules! u32_register {
($name:ident, $register:expr) => {
impl TypedRegister for $name {
fn register() -> Register {
Register::from($register)
}
fn from_u32(value: u32) -> Self {
Self(value)
}
fn as_u32(self) -> u32 {
self.0
}
}
};
}
bitfield::bitfield! {
#[derive(Copy, Clone)]
pub struct DebugCause(u32);
impl Debug;
pub icount_exception, set_icount_exception : 0;
pub ibreak_exception, set_ibreak_exception : 1;
pub dbreak_exception, set_dbreak_exception : 2;
pub break_instruction, set_break_instruction : 3;
pub break_n_instruction, set_break_n_instruction: 4;
pub debug_interrupt, set_debug_interrupt : 5;
pub dbreak_num, set_dbreak_num : 11, 8;
}
u32_register!(DebugCause, SpecialRegister::DebugCause);
impl DebugCause {
pub fn halt_reason(&self) -> HaltReason {
let is_icount_exception = self.icount_exception();
let is_ibreak_exception = self.ibreak_exception();
let is_break_instruction = self.break_instruction();
let is_break_n_instruction = self.break_n_instruction();
let is_dbreak_exception = self.dbreak_exception();
let is_debug_interrupt = self.debug_interrupt();
let is_breakpoint = is_break_instruction || is_break_n_instruction;
let count = is_icount_exception as u8
+ is_ibreak_exception as u8
+ is_break_instruction as u8
+ is_break_n_instruction as u8
+ is_dbreak_exception as u8
+ is_debug_interrupt as u8;
if count > 1 {
tracing::debug!("DebugCause: {:?}", self);
if is_breakpoint {
HaltReason::Breakpoint(BreakpointCause::Unknown)
} else {
HaltReason::Multiple
}
} else if is_icount_exception {
HaltReason::Step
} else if is_ibreak_exception {
HaltReason::Breakpoint(BreakpointCause::Hardware)
} else if is_breakpoint {
HaltReason::Breakpoint(BreakpointCause::Software)
} else if is_dbreak_exception {
HaltReason::Watchpoint
} else if is_debug_interrupt {
HaltReason::Request
} else {
HaltReason::Unknown
}
}
}
bitfield::bitfield! {
#[derive(Copy, Clone)]
pub struct ProgramStatus(u32);
impl Debug;
pub intlevel, set_intlevel : 3, 0;
pub excm, set_excm : 4;
pub user_mode, set_user_mode: 5;
pub ring, set_ring : 7, 6;
pub owb, set_owb : 11, 8;
pub callinc, set_callinc : 17, 16;
pub woe, set_woe : 18;
}
u32_register!(ProgramStatus, Register::CurrentPs);
#[derive(Copy, Clone, Debug)]
pub struct IBreakEn(pub u32);
u32_register!(IBreakEn, SpecialRegister::IBreakEnable);
#[derive(Copy, Clone, Debug)]
pub struct ICount(pub u32);
u32_register!(ICount, SpecialRegister::ICount);
#[derive(Copy, Clone, Debug)]
pub struct ICountLevel(pub u32);
u32_register!(ICountLevel, SpecialRegister::ICountLevel);