use crate::{
architecture::{
arm::{
communication_interface::ArmProbeInterface,
component::{get_arm_components, TraceSink},
memory::CoresightComponent,
sequences::{ArmDebugSequence, DefaultArmSequence},
ArmError, DpAddress, SwoReader,
},
riscv::communication_interface::{
RiscvCommunicationInterface, RiscvDebugInterfaceState, RiscvError,
},
xtensa::communication_interface::{
XtensaCommunicationInterface, XtensaDebugInterfaceState, XtensaError,
},
},
config::{CoreExt, DebugSequence, RegistryError, Target, TargetSelector},
core::{Architecture, CombinedCoreState},
probe::{
fake_probe::FakeProbe, list::Lister, AttachMethod, DebugProbeError, Probe,
ProbeCreationError,
},
Core, CoreType, Error,
};
use std::ops::DerefMut;
use std::{fmt, sync::Arc, time::Duration};
#[derive(Debug)]
pub struct Session {
target: Target,
interfaces: ArchitectureInterface,
cores: Vec<CombinedCoreState>,
configured_trace_sink: Option<TraceSink>,
}
#[allow(clippy::large_enum_variant)]
enum JtagInterface {
Riscv(RiscvDebugInterfaceState),
Xtensa(XtensaDebugInterfaceState),
Unknown,
}
impl JtagInterface {
fn architecture(&self) -> Option<Architecture> {
match self {
JtagInterface::Riscv(_) => Some(Architecture::Riscv),
JtagInterface::Xtensa(_) => Some(Architecture::Xtensa),
JtagInterface::Unknown => None,
}
}
}
impl fmt::Debug for JtagInterface {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
JtagInterface::Riscv(_) => f.write_str("Riscv(..)"),
JtagInterface::Xtensa(_) => f.write_str("Xtensa(..)"),
JtagInterface::Unknown => f.write_str("Unknown"),
}
}
}
enum ArchitectureInterface {
Arm(Box<dyn ArmProbeInterface + 'static>),
Jtag(Probe, Vec<JtagInterface>),
}
impl fmt::Debug for ArchitectureInterface {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ArchitectureInterface::Arm(_) => f.write_str("ArchitectureInterface::Arm(..)"),
ArchitectureInterface::Jtag(_, ifaces) => f
.debug_tuple("ArchitectureInterface::Other(..)")
.field(ifaces)
.finish(),
}
}
}
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(interface) => combined_state.attach_arm(target, interface),
ArchitectureInterface::Jtag(probe, ifaces) => {
let idx = combined_state.interface_idx();
probe.select_jtag_tap(idx)?;
match &mut ifaces[idx] {
JtagInterface::Riscv(state) => {
let factory = probe.try_get_riscv_interface_builder()?;
let iface = factory.attach_auto(target, state)?;
combined_state.attach_riscv(target, iface)
}
JtagInterface::Xtensa(state) => {
let iface = probe.try_get_xtensa_interface(state)?;
combined_state.attach_xtensa(target, iface)
}
JtagInterface::Unknown => {
unreachable!(
"Tried to attach to unknown interface {idx}. This should never happen."
)
}
}
}
}
}
}
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 = if let Architecture::Arm = target.architecture() {
Self::attach_arm(probe, target, attach_method, permissions, cores)?
} else {
Self::attach_jtag(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(format!(
"Unable to connect to core {default_core:?}, no memory AP configured"
))
})?;
let default_dp = default_memory_ap.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 reset").entered();
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()?;
if probe.scan_chain().iter().len() > 0 {
for core in &cores {
probe.select_jtag_tap(core.interface_idx())?;
}
}
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,
interfaces: 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,
interfaces: ArchitectureInterface::Arm(interface),
cores,
configured_trace_sink: None,
})
}
}
fn attach_jtag(
mut probe: Probe,
target: Target,
_attach_method: AttachMethod,
_permissions: Permissions,
cores: Vec<CombinedCoreState>,
) -> Result<Self, Error> {
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 highest_idx = cores.iter().map(|c| c.interface_idx()).max().unwrap_or(0);
let tap_count = match probe.scan_chain() {
Ok(scan_chain) => scan_chain.len().max(highest_idx + 1),
Err(_) => highest_idx + 1,
};
let mut interfaces = std::iter::repeat_with(|| JtagInterface::Unknown)
.take(tap_count)
.collect::<Vec<_>>();
for core in cores.iter() {
let iface_idx = core.interface_idx();
let core_arch = core.core_type().architecture();
if let Some(debug_arch) = interfaces[iface_idx].architecture() {
if core_arch == debug_arch {
continue;
}
return Err(Error::Probe(DebugProbeError::Other(format!(
"{core_arch:?} core can not be mixed with a {debug_arch:?} debug module.",
))));
}
probe.select_jtag_tap(iface_idx)?;
interfaces[iface_idx] = match core_arch {
Architecture::Riscv => {
let factory = probe.try_get_riscv_interface_builder()?;
let mut state = factory.create_state();
{
let mut interface = factory.attach_auto(&target, &mut state)?;
interface.enter_debug_mode()?;
}
JtagInterface::Riscv(state)
}
Architecture::Xtensa => JtagInterface::Xtensa(XtensaDebugInterfaceState::default()),
_ => {
return Err(Error::Probe(DebugProbeError::Other(format!(
"Unsupported core architecture {core_arch:?}",
))));
}
};
}
let interfaces = ArchitectureInterface::Jtag(probe, interfaces);
let mut session = Session {
target,
interfaces,
cores,
configured_trace_sink: None,
};
for core_id in 0..session.cores.len() {
match session.core(core_id) {
Ok(mut core) => {
if !core.core_halted()? {
core.halt(Duration::from_millis(100))?;
}
}
Err(Error::CoreDisabled(i)) => tracing::debug!("Core {i} is disabled"),
Err(error) => return Err(error),
}
}
match session.target.debug_sequence.clone() {
DebugSequence::Xtensa(_) => {}
DebugSequence::Riscv(sequence) => {
for core_id in 0..session.cores.len() {
sequence.on_connect(&mut session.get_riscv_interface(core_id)?)?;
}
}
_ => unreachable!("Other architectures should have already been handled"),
};
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::Probe(DebugProbeError::ProbeCouldNotBeCreated(
ProbeCreationError::NotFound,
)))?
.open()?;
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 = match self.core(core) {
Err(Error::CoreDisabled(_)) => continue,
other => other?,
};
if c.core_halted()? {
tracing::info!("Core {core} already halted");
} else {
tracing::info!("Halting core {core}...");
resume_state.push(core);
c.halt(Duration::from_millis(100))?;
}
}
let r = f(self);
for core in resume_state {
tracing::debug!("Resuming core...");
self.core(core)?.run()?;
}
r
}
fn interface_idx(&self, core: usize) -> Result<usize, Error> {
self.cores
.get(core)
.map(|c| c.interface_idx())
.ok_or(Error::CoreNotFound(core))
}
#[tracing::instrument(level = "trace", 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))?;
match self.interfaces.attach(&self.target, combined_state) {
Err(Error::Xtensa(XtensaError::CoreDisabled)) => {
Err(Error::CoreDisabled(core_index))
}
other => other,
}
}
#[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.interfaces {
ArchitectureInterface::Arm(state) => state.deref_mut(),
_ => return Err(ArmError::NoArmTarget),
};
Ok(interface)
}
pub fn get_riscv_interface(
&mut self,
core_id: usize,
) -> Result<RiscvCommunicationInterface, Error> {
let tap_idx = self.interface_idx(core_id)?;
if let ArchitectureInterface::Jtag(probe, ifaces) = &mut self.interfaces {
probe.select_jtag_tap(tap_idx)?;
if let JtagInterface::Riscv(state) = &mut ifaces[tap_idx] {
let factory = probe.try_get_riscv_interface_builder()?;
return Ok(factory.attach_auto(&self.target, state)?);
}
}
Err(RiscvError::NoRiscvTarget.into())
}
pub fn get_xtensa_interface(
&mut self,
core_id: usize,
) -> Result<XtensaCommunicationInterface, Error> {
let tap_idx = self.interface_idx(core_id)?;
if let ArchitectureInterface::Jtag(probe, ifaces) = &mut self.interfaces {
probe.select_jtag_tap(tap_idx)?;
if let JtagInterface::Xtensa(state) = &mut ifaces[tap_idx] {
return Ok(probe.try_get_xtensa_interface(state)?);
}
}
Err(XtensaError::NoXtensaTarget.into())
}
#[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 prepare_running_on_ram(&mut self, vector_table_addr: u64) -> Result<(), crate::Error> {
match &self.target.debug_sequence.clone() {
crate::config::DebugSequence::Arm(arm) => {
arm.prepare_running_on_ram(vector_table_addr, self)
}
_ => Err(crate::Error::NotImplemented(
"ram flash non-ARM architectures",
)),
}
}
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.interfaces else {
return Err(Error::NotImplemented(
"Debug Erase Sequence is not implemented for non-ARM targets.",
));
};
let DebugSequence::Arm(ref debug_sequence) = self.target.debug_sequence else {
unreachable!("This should never happen. Please file a bug if it does.");
};
let erase_sequence = debug_sequence
.debug_erase_sequence()
.ok_or(Error::Arm(ArmError::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 {
match &self.interfaces {
ArchitectureInterface::Arm(_) => Architecture::Arm,
ArchitectureInterface::Jtag(_, ifaces) => {
if let JtagInterface::Riscv(_) = &ifaces[0] {
Architecture::Riscv
} else {
Architecture::Xtensa
}
}
}
}
pub fn clear_all_hw_breakpoints(&mut self) -> Result<(), Error> {
self.halted_access(|session| {
{ 0..session.cores.len() }.try_for_each(|core| {
tracing::info!("Clearing breakpoints for core {core}");
match session.core(core) {
Ok(mut core) => core.clear_all_hw_breakpoints(),
Err(Error::CoreDisabled(_)) => Ok(()),
Err(err) => Err(err),
}
})
})
}
pub fn resume_all_cores(&mut self) -> Result<(), Error> {
for core_id in 0..self.cores.len() {
match self.core(core_id) {
Ok(mut core) => {
if core.core_halted()? {
core.run()?;
}
}
Err(Error::CoreDisabled(i)) => tracing::debug!("Core {i} is disabled"),
Err(error) => return Err(error),
}
}
Ok(())
}
}
const _: fn() = || {
fn assert_impl_all<T: ?Sized + Send>() {}
assert_impl_all::<Session>();
};
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::anyhow!(err)
);
}
if let Err(err) = { 0..self.cores.len() }.try_for_each(|core| match self.core(core) {
Ok(mut core) => core.debug_core_stop(),
Err(Error::CoreDisabled(_)) => Ok(()),
Err(err) => Err(err),
}) {
tracing::warn!("Failed to deconfigure device during shutdown: {:?}", err);
}
}
}
fn get_target_from_selector(
target: TargetSelector,
attach_method: AttachMethod,
mut probe: Probe,
) -> Result<(Probe, Target), Error> {
let target = match target {
TargetSelector::Unspecified(name) => crate::config::get_target_by_name(name)?,
TargetSelector::Specified(target) => target,
TargetSelector::Auto => {
if AttachMethod::UnderReset == attach_method {
probe.target_reset_assert()?;
}
probe.attach_to_unspecified()?;
let (returned_probe, found_target) = crate::vendor::auto_determine_target(probe)?;
probe = returned_probe;
if AttachMethod::UnderReset == attach_method {
probe.target_reset_deassert()?;
}
if let Some(target) = found_target {
target
} 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);