use crate::architecture::arm::ap::AccessPort;
use crate::architecture::arm::component::get_arm_components;
use crate::architecture::arm::sequences::{ArmDebugSequence, DefaultArmSequence};
use crate::architecture::arm::{ArmError, DpAddress};
use crate::architecture::riscv::communication_interface::RiscvError;
use crate::architecture::xtensa::communication_interface::{
XtensaCommunicationInterface, XtensaError,
};
use crate::config::{ChipInfo, CoreExt, RegistryError, Target, TargetSelector};
use crate::core::{Architecture, CombinedCoreState};
use crate::probe::fake_probe::FakeProbe;
use crate::{
architecture::{
arm::{
communication_interface::ArmProbeInterface, component::TraceSink,
memory::CoresightComponent, SwoReader,
},
riscv::communication_interface::RiscvCommunicationInterface,
},
config::DebugSequence,
};
use crate::{
probe::{list::Lister, AttachMethod, DebugProbeError, Probe},
Core, CoreType, Error,
};
use anyhow::anyhow;
use std::ops::DerefMut;
use std::{fmt, sync::Arc, time::Duration};
#[derive(Debug)]
pub struct Session {
target: Target,
interface: ArchitectureInterface,
cores: Vec<CombinedCoreState>,
configured_trace_sink: Option<TraceSink>,
}
pub(crate) enum ArchitectureInterface {
Arm(Box<dyn ArmProbeInterface + 'static>),
Riscv(Box<RiscvCommunicationInterface>),
Xtensa(Box<XtensaCommunicationInterface>),
}
impl fmt::Debug for ArchitectureInterface {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ArchitectureInterface::Arm(_) => f.write_str("ArchitectureInterface::Arm(..)"),
ArchitectureInterface::Riscv(iface) => f
.debug_tuple("ArchitectureInterface::Riscv")
.field(iface)
.finish(),
ArchitectureInterface::Xtensa(_) => {
f.debug_tuple("ArchitectureInterface::Xtensa(..)").finish()
}
}
}
}
impl From<&ArchitectureInterface> for Architecture {
fn from(value: &ArchitectureInterface) -> Self {
match value {
ArchitectureInterface::Arm(_) => Architecture::Arm,
ArchitectureInterface::Riscv(_) => Architecture::Riscv,
ArchitectureInterface::Xtensa(_) => Architecture::Xtensa,
}
}
}
impl ArchitectureInterface {
fn attach<'probe, 'target: 'probe>(
&'probe mut self,
target: &'probe Target,
combined_state: &'probe mut CombinedCoreState,
) -> Result<Core<'probe>, Error> {
match self {
ArchitectureInterface::Arm(iface) => combined_state.attach_arm(target, iface),
ArchitectureInterface::Riscv(iface) => combined_state.attach_riscv(target, iface),
ArchitectureInterface::Xtensa(iface) => combined_state.attach_xtensa(target, iface),
}
}
}
impl Session {
pub(crate) fn new(
probe: Probe,
target: TargetSelector,
attach_method: AttachMethod,
permissions: Permissions,
) -> Result<Self, Error> {
let (probe, target) = get_target_from_selector(target, attach_method, probe)?;
let cores = target
.cores
.iter()
.enumerate()
.map(|(id, core)| {
Core::create_state(
id,
core.core_access_options.clone(),
&target,
core.core_type,
)
})
.collect();
let mut session = match target.architecture() {
Architecture::Arm => {
Self::attach_arm(probe, target, attach_method, permissions, cores)?
}
Architecture::Riscv => {
Self::attach_riscv(probe, target, attach_method, permissions, cores)?
}
Architecture::Xtensa => {
Self::attach_xtensa(probe, target, attach_method, permissions, cores)?
}
};
session.clear_all_hw_breakpoints()?;
Ok(session)
}
fn attach_arm(
mut probe: Probe,
target: Target,
attach_method: AttachMethod,
permissions: Permissions,
cores: Vec<CombinedCoreState>,
) -> Result<Self, Error> {
let default_core = target.default_core();
let default_memory_ap = default_core.memory_ap().ok_or_else(|| {
Error::Other(anyhow::anyhow!(
"Unable to connect to core {default_core:?}, no memory AP configured"
))
})?;
let default_dp = default_memory_ap.ap_address().dp;
let sequence_handle = match &target.debug_sequence {
DebugSequence::Arm(sequence) => sequence.clone(),
_ => unreachable!("Mismatch between architecture and sequence type!"),
};
if AttachMethod::UnderReset == attach_method {
let span = tracing::debug_span!("Asserting hardware assert");
let _enter = span.enter();
if let Some(dap_probe) = probe.try_as_dap_probe() {
sequence_handle.reset_hardware_assert(dap_probe)?;
} else {
tracing::info!(
"Custom reset sequences are not supported on {}.",
probe.get_name()
);
tracing::info!("Falling back to standard probe reset.");
probe.target_reset_assert()?;
}
}
if let Some(jtag) = target.jtag.as_ref() {
if let Some(scan_chain) = jtag.scan_chain.clone() {
probe.set_scan_chain(scan_chain)?;
}
}
probe.attach_to_unspecified()?;
let interface = probe.try_into_arm_interface().map_err(|(_, err)| err)?;
let mut interface = interface
.initialize(sequence_handle.clone(), default_dp)
.map_err(|(_interface, e)| e)?;
let unlock_span = tracing::debug_span!("debug_device_unlock").entered();
let unlock_res =
sequence_handle.debug_device_unlock(&mut *interface, default_memory_ap, &permissions);
drop(unlock_span);
match unlock_res {
Ok(()) => (),
Err(ArmError::ReAttachRequired) => {
Self::reattach_arm_interface(&mut interface, &sequence_handle)?;
}
Err(e) => return Err(Error::Arm(e)),
}
for core in &cores {
core.enable_arm_debug(&mut *interface)?;
}
if attach_method == AttachMethod::UnderReset {
{
for core in &cores {
core.arm_reset_catch_set(&mut *interface)?;
}
let reset_hardware_deassert =
tracing::debug_span!("reset_hardware_deassert").entered();
let mut memory_interface = interface.memory_interface(default_memory_ap)?;
if let Err(e) = sequence_handle.reset_hardware_deassert(&mut *memory_interface) {
if matches!(e, ArmError::Timeout) {
tracing::warn!("Timeout while deasserting hardware reset pin. This indicates that the reset pin is not properly connected. Please check your hardware setup.");
}
return Err(e.into());
}
drop(reset_hardware_deassert);
}
let mut session = Session {
target,
interface: ArchitectureInterface::Arm(interface),
cores,
configured_trace_sink: None,
};
{
for core_id in 0..session.cores.len() {
let mut core = session.core(core_id)?;
core.wait_for_core_halted(Duration::from_millis(100))?;
core.reset_catch_clear()?;
}
}
Ok(session)
} else {
Ok(Session {
target,
interface: ArchitectureInterface::Arm(interface),
cores,
configured_trace_sink: None,
})
}
}
fn attach_riscv(
mut probe: Probe,
target: Target,
_attach_method: AttachMethod,
_permissions: Permissions,
cores: Vec<CombinedCoreState>,
) -> Result<Self, Error> {
let sequence_handle = match &target.debug_sequence {
DebugSequence::Riscv(sequence) => sequence.clone(),
_ => unreachable!("Mismatch between architecture and sequence type!"),
};
if let Some(jtag) = target.jtag.as_ref() {
if let Some(scan_chain) = jtag.scan_chain.clone() {
probe.set_scan_chain(scan_chain)?;
}
}
probe.attach_to_unspecified()?;
let interface = probe
.try_into_riscv_interface()
.map_err(|(_probe, err)| err)?;
let mut session = Session {
target,
interface: ArchitectureInterface::Riscv(Box::new(interface)),
cores,
configured_trace_sink: None,
};
session.halted_access(|sess| sequence_handle.on_connect(sess.get_riscv_interface()?))?;
Ok(session)
}
fn attach_xtensa(
mut probe: Probe,
target: Target,
_attach_method: AttachMethod,
_permissions: Permissions,
cores: Vec<CombinedCoreState>,
) -> Result<Self, Error> {
let sequence_handle = match &target.debug_sequence {
DebugSequence::Xtensa(sequence) => sequence.clone(),
_ => unreachable!("Mismatch between architecture and sequence type!"),
};
if let Some(jtag) = target.jtag.as_ref() {
if let Some(scan_chain) = jtag.scan_chain.clone() {
probe.set_scan_chain(scan_chain)?;
}
}
probe.attach_to_unspecified()?;
let interface = probe
.try_into_xtensa_interface()
.map_err(|(_probe, err)| err)?;
let mut session = Session {
target,
interface: ArchitectureInterface::Xtensa(Box::new(interface)),
cores,
configured_trace_sink: None,
};
session.halted_access(|sess| sequence_handle.on_connect(sess.get_xtensa_interface()?))?;
Ok(session)
}
#[tracing::instrument(skip(target))]
pub fn auto_attach(
target: impl Into<TargetSelector>,
permissions: Permissions,
) -> Result<Session, Error> {
let lister = Lister::new();
let probes = lister.list_all();
let probe = probes
.first()
.ok_or(Error::UnableToOpenProbe("No probe was found"))?
.open(&lister)?;
probe.attach(target, permissions)
}
pub fn list_cores(&self) -> Vec<(usize, CoreType)> {
self.cores.iter().map(|t| (t.id(), t.core_type())).collect()
}
pub(crate) fn halted_access<R>(
&mut self,
f: impl FnOnce(&mut Self) -> Result<R, Error>,
) -> Result<R, Error> {
let mut resume_state = vec![];
for (core, _) in self.list_cores() {
let mut c = self.core(core)?;
tracing::info!("Core status: {:?}", c.status()?);
if !c.core_halted()? {
tracing::info!("Halting core...");
resume_state.push(core);
c.halt(Duration::from_millis(100))?;
}
}
let r = f(self);
for core in resume_state {
tracing::info!("Resuming core...");
self.core(core)?.run()?;
}
r
}
#[tracing::instrument(skip(self), name = "attach_to_core")]
pub fn core(&mut self, core_index: usize) -> Result<Core<'_>, Error> {
let combined_state = self
.cores
.get_mut(core_index)
.ok_or(Error::CoreNotFound(core_index))?;
self.interface.attach(&self.target, combined_state)
}
#[tracing::instrument(skip(self))]
pub fn read_trace_data(&mut self) -> Result<Vec<u8>, ArmError> {
let sink = self
.configured_trace_sink
.as_ref()
.ok_or(ArmError::TracingUnconfigured)?;
match sink {
TraceSink::Swo(_) => {
let interface = self.get_arm_interface()?;
interface.read_swo()
}
TraceSink::Tpiu(_) => {
panic!("Probe-rs does not yet support reading parallel trace ports");
}
TraceSink::TraceMemory => {
let components = self.get_arm_components(DpAddress::Default)?;
let interface = self.get_arm_interface()?;
crate::architecture::arm::component::read_trace_memory(interface, &components)
}
}
}
pub fn swo_reader(&mut self) -> Result<SwoReader, Error> {
let interface = self.get_arm_interface()?;
Ok(SwoReader::new(interface))
}
pub fn get_arm_interface(&mut self) -> Result<&mut dyn ArmProbeInterface, ArmError> {
let interface = match &mut self.interface {
ArchitectureInterface::Arm(state) => state.deref_mut(),
_ => return Err(ArmError::NoArmTarget),
};
Ok(interface)
}
pub fn get_riscv_interface(&mut self) -> Result<&mut RiscvCommunicationInterface, RiscvError> {
let interface = match &mut self.interface {
ArchitectureInterface::Riscv(interface) => interface,
_ => return Err(RiscvError::NoRiscvTarget),
};
Ok(interface)
}
pub fn get_xtensa_interface(
&mut self,
) -> Result<&mut XtensaCommunicationInterface, XtensaError> {
let interface = match &mut self.interface {
ArchitectureInterface::Xtensa(interface) => interface,
_ => return Err(XtensaError::NoXtensaTarget),
};
Ok(interface)
}
#[tracing::instrument(skip_all)]
fn reattach_arm_interface(
interface: &mut Box<dyn ArmProbeInterface>,
debug_sequence: &Arc<dyn ArmDebugSequence>,
) -> Result<(), Error> {
use crate::probe::DebugProbe;
let current_dp = interface.current_debug_port();
let tmp_interface = Box::<FakeProbe>::default().try_get_arm_interface().unwrap();
let mut tmp_interface = tmp_interface
.initialize(DefaultArmSequence::create(), DpAddress::Default)
.unwrap();
std::mem::swap(interface, &mut tmp_interface);
tracing::debug!("Re-attaching Probe");
let mut probe = tmp_interface.close();
probe.detach()?;
probe.attach_to_unspecified()?;
let new_interface = probe.try_into_arm_interface().map_err(|(_, err)| err)?;
tmp_interface = new_interface
.initialize(debug_sequence.clone(), current_dp)
.map_err(|(_interface, e)| e)?;
std::mem::swap(interface, &mut tmp_interface);
tracing::debug!("Probe re-attached");
Ok(())
}
pub fn has_sequence_erase_all(&self) -> bool {
match &self.target.debug_sequence {
DebugSequence::Arm(seq) => seq.debug_erase_sequence().is_some(),
_ => false,
}
}
pub fn sequence_erase_all(&mut self) -> Result<(), Error> {
let ArchitectureInterface::Arm(ref mut interface) = self.interface else {
return Err(Error::Probe(DebugProbeError::NotImplemented(
"Debug Erase Sequence",
)));
};
let DebugSequence::Arm(ref debug_sequence) = self.target.debug_sequence else {
unreachable!("This should never happen. Please file a bug if it does.");
};
let Some(erase_sequence) = debug_sequence.debug_erase_sequence() else {
return Err(Error::Probe(DebugProbeError::NotImplemented(
"Debug Erase Sequence",
)));
};
tracing::info!("Trying Debug Erase Sequence");
let erase_result = erase_sequence.erase_all(interface.deref_mut());
match erase_result {
Ok(()) => (),
Err(ArmError::ReAttachRequired) => {
Self::reattach_arm_interface(interface, debug_sequence)?;
for core_state in &self.cores {
core_state.enable_arm_debug(interface.deref_mut())?;
}
}
Err(e) => return Err(Error::Arm(e)),
}
tracing::info!("Device Erased Successfully");
Ok(())
}
pub fn get_arm_components(
&mut self,
dp: DpAddress,
) -> Result<Vec<CoresightComponent>, ArmError> {
let interface = self.get_arm_interface()?;
get_arm_components(interface, dp)
}
pub fn target(&self) -> &Target {
&self.target
}
pub fn setup_tracing(
&mut self,
core_index: usize,
destination: TraceSink,
) -> Result<(), Error> {
{
let mut core = self.core(core_index)?;
crate::architecture::arm::component::enable_tracing(&mut core)?;
}
let sequence_handle = match &self.target.debug_sequence {
DebugSequence::Arm(sequence) => sequence.clone(),
_ => unreachable!("Mismatch between architecture and sequence type!"),
};
let components = self.get_arm_components(DpAddress::Default)?;
let interface = self.get_arm_interface()?;
match destination {
TraceSink::Swo(ref config) => {
interface.enable_swo(config)?;
}
TraceSink::Tpiu(ref config) => {
interface.enable_swo(config)?;
}
TraceSink::TraceMemory => {}
}
sequence_handle.trace_start(interface, &components, &destination)?;
crate::architecture::arm::component::setup_tracing(interface, &components, &destination)?;
self.configured_trace_sink.replace(destination);
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn disable_swv(&mut self, core_index: usize) -> Result<(), Error> {
crate::architecture::arm::component::disable_swv(&mut self.core(core_index)?)
}
pub fn add_swv_data_trace(&mut self, unit: usize, address: u32) -> Result<(), ArmError> {
let components = self.get_arm_components(DpAddress::Default)?;
let interface = self.get_arm_interface()?;
crate::architecture::arm::component::add_swv_data_trace(
interface,
&components,
unit,
address,
)
}
pub fn remove_swv_data_trace(&mut self, unit: usize) -> Result<(), ArmError> {
let components = self.get_arm_components(DpAddress::Default)?;
let interface = self.get_arm_interface()?;
crate::architecture::arm::component::remove_swv_data_trace(interface, &components, unit)
}
pub fn architecture(&self) -> Architecture {
Architecture::from(&self.interface)
}
pub fn clear_all_hw_breakpoints(&mut self) -> Result<(), Error> {
self.halted_access(|session| {
{ 0..session.cores.len() }.try_for_each(|n| {
session
.core(n)
.and_then(|mut core| core.clear_all_hw_breakpoints())
})
})
}
}
static_assertions::assert_impl_all!(Session: Send);
impl Drop for Session {
#[tracing::instrument(name = "session_drop", skip(self))]
fn drop(&mut self) {
if let Err(err) = self.clear_all_hw_breakpoints() {
tracing::warn!(
"Could not clear all hardware breakpoints: {:?}",
anyhow!(err)
);
}
if let Err(err) = { 0..self.cores.len() }
.try_for_each(|i| self.core(i).and_then(|mut core| core.debug_core_stop()))
{
tracing::warn!(
"Failed to deconfigure device during shutdown: {:?}",
anyhow!(err)
);
}
}
}
fn get_target_from_selector(
target: TargetSelector,
attach_method: AttachMethod,
probe: Probe,
) -> Result<(Probe, Target), Error> {
let mut probe = probe;
let target = match target {
TargetSelector::Unspecified(name) => crate::config::get_target_by_name(name)?,
TargetSelector::Specified(target) => target,
TargetSelector::Auto => {
let mut found_chip = None;
let dp_address = DpAddress::Default;
if AttachMethod::UnderReset == attach_method {
probe.target_reset_assert()?;
}
probe.attach_to_unspecified()?;
if probe.has_arm_interface() {
match probe.try_into_arm_interface() {
Ok(interface) => {
let mut interface = interface
.initialize(DefaultArmSequence::create(), dp_address)
.map_err(|(_probe, err)| err)?;
let dp = DpAddress::Default;
let found_arm_chip = interface
.read_chip_info_from_rom_table(dp)
.unwrap_or_else(|e| {
tracing::info!("Error during auto-detection of ARM chips: {}", e);
None
});
found_chip = found_arm_chip.map(ChipInfo::from);
probe = interface.close();
}
Err((returned_probe, err)) => {
probe = returned_probe;
tracing::debug!("Error using ARM interface: {}", err);
}
}
} else {
tracing::debug!("No ARM interface was present. Skipping Riscv autodetect.");
}
if found_chip.is_none() && probe.has_riscv_interface() {
match probe.try_into_riscv_interface() {
Ok(mut interface) => {
let idcode = interface.read_idcode();
tracing::debug!("ID Code read over JTAG: {:x?}", idcode);
probe = interface.close();
}
Err((returned_probe, err)) => {
tracing::debug!("Error during autodetection of RISC-V chips: {}", err);
probe = returned_probe;
}
}
} else {
tracing::debug!("No RISC-V interface was present. Skipping Riscv autodetect.");
}
probe.target_reset_deassert()?;
if let Some(chip) = found_chip {
crate::config::get_target_by_chip_info(chip)?
} else {
return Err(Error::ChipNotFound(RegistryError::ChipAutodetectFailed));
}
}
};
Ok((probe, target))
}
#[non_exhaustive]
#[derive(Debug, Clone, Default)]
pub struct Permissions {
erase_all: bool,
}
impl Permissions {
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn allow_erase_all(self) -> Self {
Self {
erase_all: true,
..self
}
}
pub(crate) fn erase_all(&self) -> Result<(), MissingPermissions> {
if self.erase_all {
Ok(())
} else {
Err(MissingPermissions("erase_all".into()))
}
}
}
#[derive(Debug, Clone, thiserror::Error)]
#[error("An operation could not be performed because it lacked the permission to do so: {0}")]
pub struct MissingPermissions(pub String);