use std::{sync::Arc, time::Duration};
use probe_rs_target::{Architecture, CoreType, InstructionSet};
use crate::{
CoreInformation, CoreInterface, CoreRegister, CoreStatus, Error, HaltReason, MemoryInterface,
architecture::xtensa::{
arch::{
CpuRegister, Register, SpecialRegister,
instruction::{Instruction, InstructionEncoding},
},
communication_interface::{
DebugCause, IBreakEn, ProgramStatus, WindowProperties, XtensaCommunicationInterface,
},
registers::{FP, PC, RA, SP, XTENSA_CORE_REGISTERS},
sequences::XtensaDebugSequence,
xdm::PowerStatus,
},
core::{
BreakpointCause,
registers::{CoreRegisters, RegisterId, RegisterValue},
},
memory::CoreMemoryInterface,
semihosting::{SemihostingCommand, decode_semihosting_syscall},
};
pub(crate) mod arch;
pub(crate) mod xdm;
pub mod communication_interface;
pub(crate) mod register_cache;
pub mod registers;
pub(crate) mod sequences;
#[derive(Debug)]
pub struct XtensaCoreState {
enabled: bool,
breakpoints_enabled: bool,
breakpoint_set: [bool; 2],
pc_written: bool,
semihosting_command: Option<SemihostingCommand>,
}
impl XtensaCoreState {
pub(crate) fn new() -> Self {
Self {
enabled: false,
breakpoints_enabled: false,
breakpoint_set: [false; 2],
pc_written: false,
semihosting_command: None,
}
}
fn breakpoint_mask(&self) -> u32 {
self.breakpoint_set
.iter()
.enumerate()
.fold(0, |acc, (i, &set)| if set { acc | (1 << i) } else { acc })
}
}
pub struct Xtensa<'probe> {
interface: XtensaCommunicationInterface<'probe>,
state: &'probe mut XtensaCoreState,
sequence: Arc<dyn XtensaDebugSequence>,
}
impl<'probe> Xtensa<'probe> {
const IBREAKA_REGS: [SpecialRegister; 2] =
[SpecialRegister::IBreakA0, SpecialRegister::IBreakA1];
pub fn new(
interface: XtensaCommunicationInterface<'probe>,
state: &'probe mut XtensaCoreState,
sequence: Arc<dyn XtensaDebugSequence>,
) -> Result<Self, Error> {
let mut this = Self {
interface,
state,
sequence,
};
this.on_attach()?;
Ok(this)
}
fn clear_cache(&mut self) {
self.interface.clear_register_cache();
}
fn on_attach(&mut self) -> Result<(), Error> {
let core_reset;
if self.state.enabled {
let status = self.interface.xdm.power_status({
let mut clear_value = PowerStatus(0);
clear_value.set_core_was_reset(true);
clear_value.set_debug_was_reset(true);
clear_value
})?;
core_reset = status.core_was_reset() || !status.core_domain_on();
let debug_reset = status.debug_was_reset() || !status.debug_domain_on();
if core_reset {
tracing::debug!("Core was reset");
*self.state = XtensaCoreState::new();
}
if debug_reset {
tracing::debug!("Debug was reset");
self.state.enabled = false;
}
} else {
core_reset = true;
}
if !self.state.enabled {
self.interface.enter_debug_mode()?;
self.state.enabled = true;
if core_reset {
let was_running = self
.interface
.halt_with_previous(Duration::from_millis(500))?;
self.sequence.on_connect(&mut self.interface)?;
if was_running {
self.run()?;
}
}
}
Ok(())
}
fn core_info(&mut self) -> Result<CoreInformation, Error> {
let pc = self.read_core_reg(self.program_counter().id)?;
Ok(CoreInformation { pc: pc.try_into()? })
}
fn skip_breakpoint(&mut self) -> Result<(), Error> {
self.state.semihosting_command = None;
if !self.state.pc_written {
let debug_cause = self.debug_cause()?;
let pc_increment = if debug_cause.break_instruction() {
3
} else if debug_cause.break_n_instruction() {
2
} else {
0
};
if pc_increment > 0 {
let mut pc_value = self.interface.read_register_untyped(Register::CurrentPc)?;
pc_value += pc_increment;
self.interface
.write_register_untyped(Register::CurrentPc, pc_value)?;
} else if debug_cause.ibreak_exception() {
let pc_value = self.interface.read_register_untyped(Register::CurrentPc)?;
let bps = self.hw_breakpoints()?;
if let Some(bp_unit) = bps.iter().position(|bp| *bp == Some(pc_value as u64)) {
self.clear_hw_breakpoint(bp_unit)?;
let ps = self.current_ps()?;
self.interface.step(1, ps.intlevel())?;
self.set_hw_breakpoint(bp_unit, pc_value as u64)?;
}
}
}
Ok(())
}
fn check_for_semihosting(&mut self) -> Result<Option<SemihostingCommand>, Error> {
const SEMI_BREAK: u32 = const {
let InstructionEncoding::Narrow(bytes) = Instruction::Break(1, 14).encode();
bytes
};
if let Some(command) = self.state.semihosting_command {
return Ok(Some(command));
}
let pc: u64 = self.read_core_reg(self.program_counter().id)?.try_into()?;
let mut actual_instruction = [0u8; 3];
self.read_8(pc, &mut actual_instruction)?;
let actual_instruction = u32::from_le_bytes([
actual_instruction[0],
actual_instruction[1],
actual_instruction[2],
0,
]);
tracing::debug!("Semihosting check pc={pc:#x} instruction={actual_instruction:#010x}");
let command = if actual_instruction == SEMI_BREAK {
let syscall = decode_semihosting_syscall(self)?;
if let SemihostingCommand::Unknown(details) = syscall {
self.sequence
.clone()
.on_unknown_semihosting_command(self, details)?
} else {
Some(syscall)
}
} else {
None
};
self.state.semihosting_command = command;
Ok(command)
}
fn on_halted(&mut self) -> Result<(), Error> {
self.state.pc_written = false;
self.clear_cache();
let status = self.status()?;
tracing::debug!("Core halted: {:#?}", status);
if status.is_halted() {
self.sequence.on_halt(&mut self.interface)?;
}
Ok(())
}
fn halt_with_previous(&mut self, timeout: Duration) -> Result<bool, Error> {
let was_running = self.interface.halt_with_previous(timeout)?;
if was_running {
self.on_halted()?;
}
Ok(was_running)
}
fn halted_access<F, T>(&mut self, op: F) -> Result<T, Error>
where
F: FnOnce(&mut Self) -> Result<T, Error>,
{
let was_running = self.halt_with_previous(Duration::from_millis(100))?;
let result = op(self);
if was_running {
self.run()?;
}
result
}
fn current_ps(&mut self) -> Result<ProgramStatus, Error> {
Ok(self
.interface
.read_register_untyped(Register::CurrentPs)
.map(ProgramStatus)?)
}
fn debug_cause(&mut self) -> Result<DebugCause, Error> {
Ok(self.interface.read_register::<DebugCause>()?)
}
fn spill_registers(&mut self) -> Result<(), Error> {
if self.current_ps()?.excm() {
return Ok(());
}
if self.current_ps()?.woe() {
let register_file = self.read_window_registers()?;
register_file.spill(&mut self.interface)?;
}
Ok(())
}
fn read_window_registers(&mut self) -> Result<RegisterFile, Error> {
let register_file = RegisterFile::read(
self.interface.core_properties().window_option_properties,
&mut self.interface,
)?;
let window_reg_count = register_file.core.window_regs;
for reg in 0..window_reg_count {
let reg =
CpuRegister::try_from(reg).expect("Could not convert register to CpuRegister");
let value = register_file.read_register(reg);
self.interface
.state
.register_cache
.store(Register::Cpu(reg), value);
}
Ok(register_file)
}
}
impl CoreMemoryInterface for Xtensa<'_> {
type ErrorType = Error;
fn memory(&self) -> &dyn MemoryInterface<Self::ErrorType> {
&self.interface
}
fn memory_mut(&mut self) -> &mut dyn MemoryInterface<Self::ErrorType> {
&mut self.interface
}
}
impl CoreInterface for Xtensa<'_> {
fn wait_for_core_halted(&mut self, timeout: Duration) -> Result<(), Error> {
self.interface.wait_for_core_halted(timeout)?;
self.on_halted()?;
Ok(())
}
fn core_halted(&mut self) -> Result<bool, Error> {
let was_halted = self.interface.state.is_halted;
let is_halted = self.interface.core_halted()?;
if !was_halted && is_halted {
self.on_halted()?;
}
Ok(is_halted)
}
fn status(&mut self) -> Result<CoreStatus, Error> {
let status = if self.core_halted()? {
let debug_cause = self.debug_cause()?;
let mut reason = debug_cause.halt_reason();
if reason == HaltReason::Breakpoint(BreakpointCause::Software) {
self.state.pc_written = false;
if let Some(cmd) = self.check_for_semihosting()? {
reason = HaltReason::Breakpoint(BreakpointCause::Semihosting(cmd));
}
}
CoreStatus::Halted(reason)
} else {
CoreStatus::Running
};
Ok(status)
}
fn halt(&mut self, timeout: Duration) -> Result<CoreInformation, Error> {
self.halt_with_previous(timeout)?;
self.core_info()
}
fn run(&mut self) -> Result<(), Error> {
self.skip_breakpoint()?;
Ok(self.interface.resume_core()?)
}
fn reset(&mut self) -> Result<(), Error> {
self.reset_and_halt(Duration::from_millis(500))?;
self.run()
}
fn reset_and_halt(&mut self, timeout: Duration) -> Result<CoreInformation, Error> {
self.state.semihosting_command = None;
self.sequence
.reset_system_and_halt(&mut self.interface, timeout)?;
self.on_halted()?;
self.on_attach()?;
self.core_info()
}
fn step(&mut self) -> Result<CoreInformation, Error> {
self.skip_breakpoint()?;
let ps = self.current_ps()?;
self.interface.step(1, ps.intlevel())?;
self.on_halted()?;
self.core_info()
}
fn read_core_reg(&mut self, address: RegisterId) -> Result<RegisterValue, Error> {
self.halted_access(|this| {
let register = Register::try_from(address)?;
let value = this.interface.read_register_untyped(register)?;
Ok(RegisterValue::U32(value))
})
}
fn write_core_reg(&mut self, address: RegisterId, value: RegisterValue) -> Result<(), Error> {
self.halted_access(|this| {
let value: u32 = value.try_into()?;
if address == this.program_counter().id {
this.state.pc_written = true;
}
let register = Register::try_from(address)?;
this.interface.write_register_untyped(register, value)?;
Ok(())
})
}
fn available_breakpoint_units(&mut self) -> Result<u32, Error> {
Ok(self.interface.available_breakpoint_units())
}
fn hw_breakpoints(&mut self) -> Result<Vec<Option<u64>>, Error> {
self.halted_access(|this| {
let mut breakpoints = Vec::with_capacity(this.available_breakpoint_units()? as usize);
let enabled_breakpoints = this.interface.read_register::<IBreakEn>()?;
for i in 0..this.available_breakpoint_units()? as usize {
let is_enabled = enabled_breakpoints.0 & (1 << i) != 0;
let breakpoint = if is_enabled {
let address = this
.interface
.read_register_untyped(Self::IBREAKA_REGS[i])?;
Some(address as u64)
} else {
None
};
breakpoints.push(breakpoint);
}
Ok(breakpoints)
})
}
fn enable_breakpoints(&mut self, state: bool) -> Result<(), Error> {
self.halted_access(|this| {
this.state.breakpoints_enabled = state;
let mask = if state {
this.state.breakpoint_mask()
} else {
0
};
this.interface.write_register(IBreakEn(mask))?;
Ok(())
})
}
fn set_hw_breakpoint(&mut self, unit_index: usize, addr: u64) -> Result<(), Error> {
self.halted_access(|this| {
this.state.breakpoint_set[unit_index] = true;
this.interface
.write_register_untyped(Self::IBREAKA_REGS[unit_index], addr as u32)?;
if this.state.breakpoints_enabled {
let mask = this.state.breakpoint_mask();
this.interface.write_register(IBreakEn(mask))?;
}
Ok(())
})
}
fn clear_hw_breakpoint(&mut self, unit_index: usize) -> Result<(), Error> {
self.halted_access(|this| {
this.state.breakpoint_set[unit_index] = false;
if this.state.breakpoints_enabled {
let mask = this.state.breakpoint_mask();
this.interface.write_register(IBreakEn(mask))?;
}
Ok(())
})
}
fn registers(&self) -> &'static CoreRegisters {
&XTENSA_CORE_REGISTERS
}
fn program_counter(&self) -> &'static CoreRegister {
&PC
}
fn frame_pointer(&self) -> &'static CoreRegister {
&FP
}
fn stack_pointer(&self) -> &'static CoreRegister {
&SP
}
fn return_address(&self) -> &'static CoreRegister {
&RA
}
fn hw_breakpoints_enabled(&self) -> bool {
self.state.breakpoints_enabled
}
fn architecture(&self) -> Architecture {
Architecture::Xtensa
}
fn core_type(&self) -> CoreType {
CoreType::Xtensa
}
fn instruction_set(&mut self) -> Result<InstructionSet, Error> {
Ok(InstructionSet::Xtensa)
}
fn fpu_support(&mut self) -> Result<bool, Error> {
Ok(false)
}
fn floating_point_register_count(&mut self) -> Result<usize, Error> {
Ok(0)
}
fn reset_catch_set(&mut self) -> Result<(), Error> {
Err(Error::NotImplemented("reset_catch_set"))
}
fn reset_catch_clear(&mut self) -> Result<(), Error> {
Err(Error::NotImplemented("reset_catch_clear"))
}
fn debug_core_stop(&mut self) -> Result<(), Error> {
self.interface.leave_debug_mode()?;
Ok(())
}
fn spill_registers(&mut self) -> Result<(), Error> {
self.spill_registers()
}
}
struct RegisterFile {
core: WindowProperties,
registers: Vec<u32>,
window_base: u8,
window_start: u32,
}
impl RegisterFile {
fn read(
xtensa: WindowProperties,
interface: &mut XtensaCommunicationInterface<'_>,
) -> Result<Self, Error> {
let window_base_result = interface.schedule_read_register(SpecialRegister::Windowbase)?;
let window_start_result = interface.schedule_read_register(SpecialRegister::Windowstart)?;
let mut register_values = Vec::with_capacity(xtensa.num_aregs as usize);
let ar0 = arch::CpuRegister::A0 as u8;
interface.restore_registers()?;
for ar in ar0..ar0 + xtensa.window_regs {
let reg = CpuRegister::try_from(ar)?;
interface.state.register_cache.remove(reg.into());
}
let mut regs = Vec::with_capacity(xtensa.window_regs as usize);
for _ in 0..xtensa.num_aregs / xtensa.window_regs {
for ar in ar0..ar0 + xtensa.window_regs {
let reg = CpuRegister::try_from(ar)?;
let deferred_result = interface.schedule_read_register(reg)?;
regs.push(deferred_result);
}
let rotw_arg = xtensa.window_regs / xtensa.rotw_rotates;
interface
.xdm
.schedule_execute_instruction(Instruction::Rotw(rotw_arg));
for deferred in regs.drain(..) {
let result = interface.read_deferred_result(deferred)?;
register_values.push(result);
}
for ar in ar0..ar0 + xtensa.window_regs {
let reg = CpuRegister::try_from(ar)?;
interface.state.register_cache.remove(reg.into());
}
}
interface
.xdm
.execute()
.expect("Failed to execute read. This shouldn't happen.");
let window_base = interface.read_deferred_result(window_base_result)?;
let window_start = interface.read_deferred_result(window_start_result)?;
register_values.rotate_right((window_base * xtensa.rotw_rotates as u32) as usize);
Ok(Self {
core: xtensa,
registers: register_values,
window_base: window_base as u8,
window_start,
})
}
fn spill(&self, xtensa: &mut XtensaCommunicationInterface<'_>) -> Result<(), Error> {
if !self.core.has_windowed_registers {
return Ok(());
}
if self.window_start == 0 {
return Ok(());
}
let mut window_base = self.next_window_base(self.window_base);
while let Some(window) = RegisterWindow::at_windowbase(self, window_base) {
window.spill(xtensa)?;
window_base = self.next_window_base(window_base);
if window_base == self.window_base {
break;
}
}
Ok(())
}
fn is_window_start(&self, windowbase: u8) -> bool {
self.window_start & 1 << (windowbase % self.core.windowbase_size()) != 0
}
fn next_window_base(&self, window_base: u8) -> u8 {
let mut wb = (window_base + 1) % self.core.windowbase_size();
while wb != window_base {
if self.is_window_start(wb) {
break;
}
wb = (wb + 1) % self.core.windowbase_size();
}
wb
}
fn wb_offset_to_canonical(&self, idx: u8) -> u8 {
(idx + self.window_base * self.core.rotw_rotates) % self.core.num_aregs
}
fn read_register(&self, reg: CpuRegister) -> u32 {
let index = self.wb_offset_to_canonical(reg as u8);
self.registers[index as usize]
}
}
struct RegisterWindow<'a> {
window_base: u8,
window_size: u8,
file: &'a RegisterFile,
}
impl<'a> RegisterWindow<'a> {
fn at_windowbase(file: &'a RegisterFile, window_base: u8) -> Option<Self> {
if !file.is_window_start(window_base) {
return None;
}
let next_window_base = file.next_window_base(window_base);
let window_size = (next_window_base + file.core.windowbase_size() - window_base)
% file.core.windowbase_size();
Some(Self {
window_base,
file,
window_size: window_size.min(3),
})
}
fn read_register(&self, reg: CpuRegister) -> u32 {
let index = self.wb_offset_to_canonical(reg as u8);
self.file.registers[index as usize]
}
fn wb_offset_to_canonical(&self, idx: u8) -> u8 {
(idx + self.window_base * self.file.core.rotw_rotates) % self.file.core.num_aregs
}
fn spill(&self, interface: &mut XtensaCommunicationInterface<'_>) -> Result<(), Error> {
let a0_a3 = [
self.read_register(CpuRegister::A0),
self.read_register(CpuRegister::A1),
self.read_register(CpuRegister::A2),
self.read_register(CpuRegister::A3),
];
match self.window_size {
0 => {}
1 => interface.write_32(self.read_register(CpuRegister::A5) as u64 - 16, &a0_a3)?,
2 => {
let sp = interface.read_word_32(self.read_register(CpuRegister::A1) as u64 - 12)?;
interface.write_32(self.read_register(CpuRegister::A9) as u64 - 16, &a0_a3)?;
if tracing::enabled!(tracing::Level::INFO) {
let written =
interface.read_word_32(self.read_register(CpuRegister::A9) as u64 - 12)?;
assert!(
written == self.read_register(CpuRegister::A1),
"Failed to spill A1. Expected {:#x}, got {:#x}",
self.read_register(CpuRegister::A1),
written
);
}
let regs = [
self.read_register(CpuRegister::A4),
self.read_register(CpuRegister::A5),
self.read_register(CpuRegister::A6),
self.read_register(CpuRegister::A7),
];
interface.write_32(sp as u64 - 32, ®s)?;
}
3 => {
let sp = interface.read_word_32(self.read_register(CpuRegister::A1) as u64 - 12)?;
interface.write_32(self.read_register(CpuRegister::A13) as u64 - 16, &a0_a3)?;
let regs = [
self.read_register(CpuRegister::A4),
self.read_register(CpuRegister::A5),
self.read_register(CpuRegister::A6),
self.read_register(CpuRegister::A7),
self.read_register(CpuRegister::A8),
self.read_register(CpuRegister::A9),
self.read_register(CpuRegister::A10),
self.read_register(CpuRegister::A11),
];
interface.write_32(sp as u64 - 48, ®s)?;
}
_ => unreachable!(),
}
Ok(())
}
}