probe_rs/architecture/arm/
sequences.rs

1//! Debug sequences to operate special requirements ARM targets.
2
3use std::{
4    error::Error,
5    fmt::Debug,
6    sync::Arc,
7    thread,
8    time::{Duration, Instant},
9};
10
11use probe_rs_target::CoreType;
12
13use crate::{
14    MemoryInterface, MemoryMappedRegister, Session,
15    architecture::arm::{
16        ArmDebugInterface, DapError, RegisterAddress,
17        core::registers::cortex_m::{PC, SP},
18        dp::{Ctrl, DLPIDR, DebugPortError, DpRegister, TARGETID},
19    },
20    probe::WireProtocol,
21};
22
23use super::{
24    ArmError, DapAccess, FullyQualifiedApAddress, Pins,
25    ap::AccessPortError,
26    armv6m::Demcr,
27    communication_interface::DapProbe,
28    component::{TraceFunnel, TraceSink},
29    core::cortex_m::{Dhcsr, Vtor},
30    dp::{Abort, DPIDR, DpAccess, DpAddress, SelectV1},
31    memory::{
32        ArmMemoryInterface,
33        romtable::{CoresightComponent, PeripheralType},
34    },
35};
36
37/// An error occurred when executing an ARM debug sequence
38#[derive(thiserror::Error, Debug)]
39pub enum ArmDebugSequenceError {
40    /// Debug base address is required but not specified
41    #[error("Core access requries debug_base to be specified, but it is not")]
42    DebugBaseNotSpecified,
43
44    /// CTI base address is required but not specified
45    #[error("Core access requries cti_base to be specified, but it is not")]
46    CtiBaseNotSpecified,
47
48    /// An error occurred in a debug sequence.
49    #[error("An error occurred in a debug sequnce: {0}")]
50    SequenceSpecific(#[from] Box<dyn Error + Send + Sync + 'static>),
51}
52
53impl ArmDebugSequenceError {
54    pub(crate) fn custom(message: impl Into<Box<dyn Error + Send + Sync + 'static>>) -> Self {
55        ArmDebugSequenceError::SequenceSpecific(message.into())
56    }
57}
58
59/// The default sequences that is used for ARM chips that do not specify a specific sequence.
60#[derive(Debug)]
61pub struct DefaultArmSequence(pub(crate) ());
62
63impl DefaultArmSequence {
64    /// Creates a new default ARM debug sequence.
65    pub fn create() -> Arc<dyn ArmDebugSequence> {
66        Arc::new(Self(()))
67    }
68}
69
70impl ArmDebugSequence for DefaultArmSequence {}
71
72/// ResetCatchSet for Cortex-A devices
73fn armv7a_reset_catch_set(
74    core: &mut dyn ArmMemoryInterface,
75    debug_base: Option<u64>,
76) -> Result<(), ArmError> {
77    use crate::architecture::arm::core::armv7a_debug_regs::Dbgprcr;
78
79    let debug_base =
80        debug_base.ok_or_else(|| ArmError::from(ArmDebugSequenceError::DebugBaseNotSpecified))?;
81
82    let address = Dbgprcr::get_mmio_address_from_base(debug_base)?;
83    let mut dbgprcr = Dbgprcr(core.read_word_32(address)?);
84
85    dbgprcr.set_hcwr(true);
86
87    core.write_word_32(address, dbgprcr.into())?;
88
89    Ok(())
90}
91
92/// ResetCatchClear for Cortex-A devices
93fn armv7a_reset_catch_clear(
94    core: &mut dyn ArmMemoryInterface,
95    debug_base: Option<u64>,
96) -> Result<(), ArmError> {
97    use crate::architecture::arm::core::armv7a_debug_regs::Dbgprcr;
98
99    let debug_base =
100        debug_base.ok_or_else(|| ArmError::from(ArmDebugSequenceError::DebugBaseNotSpecified))?;
101
102    let address = Dbgprcr::get_mmio_address_from_base(debug_base)?;
103    let mut dbgprcr = Dbgprcr(core.read_word_32(address)?);
104
105    dbgprcr.set_hcwr(false);
106
107    core.write_word_32(address, dbgprcr.into())?;
108
109    Ok(())
110}
111
112fn armv7a_reset_system(
113    interface: &mut dyn ArmMemoryInterface,
114    debug_base: Option<u64>,
115) -> Result<(), ArmError> {
116    use crate::architecture::arm::core::armv7a_debug_regs::{Dbgprcr, Dbgprsr};
117
118    let debug_base =
119        debug_base.ok_or_else(|| ArmError::from(ArmDebugSequenceError::DebugBaseNotSpecified))?;
120
121    // Request reset
122    let address = Dbgprcr::get_mmio_address_from_base(debug_base)?;
123    let mut dbgprcr = Dbgprcr(interface.read_word_32(address)?);
124
125    dbgprcr.set_cwrr(true);
126
127    interface.write_word_32(address, dbgprcr.into())?;
128
129    // Wait until reset happens
130    let address = Dbgprsr::get_mmio_address_from_base(debug_base)?;
131
132    loop {
133        let dbgprsr = Dbgprsr(interface.read_word_32(address)?);
134        if dbgprsr.sr() {
135            break;
136        }
137    }
138
139    Ok(())
140}
141
142/// DebugCoreStart for v7 Cortex-A devices
143fn armv7a_core_start(
144    core: &mut dyn ArmMemoryInterface,
145    debug_base: Option<u64>,
146) -> Result<(), ArmError> {
147    use crate::architecture::arm::core::armv7a_debug_regs::{Dbgdsccr, Dbgdscr, Dbgdsmcr, Dbglar};
148
149    let debug_base =
150        debug_base.ok_or_else(|| ArmError::from(ArmDebugSequenceError::DebugBaseNotSpecified))?;
151    tracing::debug!(
152        "Starting debug for ARMv7-A core with registers at {:#X}",
153        debug_base
154    );
155
156    // Lock OS register access to prevent race conditions
157    let address = Dbglar::get_mmio_address_from_base(debug_base)?;
158    core.write_word_32(address, Dbglar(0).into())?;
159
160    // Force write through / disable caching for debugger access
161    let address = Dbgdsccr::get_mmio_address_from_base(debug_base)?;
162    core.write_word_32(address, Dbgdsccr(0).into())?;
163
164    // Disable TLB matching and updates for debugger operations
165    let address = Dbgdsmcr::get_mmio_address_from_base(debug_base)?;
166    core.write_word_32(address, Dbgdsmcr(0).into())?;
167
168    // Enable halting
169    let address = Dbgdscr::get_mmio_address_from_base(debug_base)?;
170    let mut dbgdscr = Dbgdscr(core.read_word_32(address)?);
171
172    if dbgdscr.hdbgen() && dbgdscr.extdccmode() == 0 {
173        tracing::debug!("Core is already in debug mode, no need to enable it again");
174        return Ok(());
175    }
176
177    dbgdscr.set_hdbgen(true);
178    dbgdscr.set_extdccmode(0);
179    core.write_word_32(address, dbgdscr.into())?;
180
181    Ok(())
182}
183
184/// ResetCatchSet for ARMv8-A devices
185fn armv8a_reset_catch_set(
186    core: &mut dyn ArmMemoryInterface,
187    debug_base: Option<u64>,
188) -> Result<(), ArmError> {
189    use crate::architecture::arm::core::armv8a_debug_regs::Edecr;
190
191    let debug_base =
192        debug_base.ok_or_else(|| ArmError::from(ArmDebugSequenceError::DebugBaseNotSpecified))?;
193
194    let address = Edecr::get_mmio_address_from_base(debug_base)?;
195    let mut edecr = Edecr(core.read_word_32(address)?);
196
197    edecr.set_rce(true);
198
199    core.write_word_32(address, edecr.into())?;
200
201    Ok(())
202}
203
204/// ResetCatchClear for ARMv8-a devices
205fn armv8a_reset_catch_clear(
206    core: &mut dyn ArmMemoryInterface,
207    debug_base: Option<u64>,
208) -> Result<(), ArmError> {
209    use crate::architecture::arm::core::armv8a_debug_regs::Edecr;
210
211    let debug_base =
212        debug_base.ok_or_else(|| ArmError::from(ArmDebugSequenceError::DebugBaseNotSpecified))?;
213
214    let address = Edecr::get_mmio_address_from_base(debug_base)?;
215    let mut edecr = Edecr(core.read_word_32(address)?);
216
217    edecr.set_rce(false);
218
219    core.write_word_32(address, edecr.into())?;
220
221    Ok(())
222}
223
224fn armv8a_reset_system(
225    interface: &mut dyn ArmMemoryInterface,
226    debug_base: Option<u64>,
227) -> Result<(), ArmError> {
228    use crate::architecture::arm::core::armv8a_debug_regs::{Edprcr, Edprsr};
229
230    let debug_base =
231        debug_base.ok_or_else(|| ArmError::from(ArmDebugSequenceError::DebugBaseNotSpecified))?;
232
233    // Request reset
234    let address = Edprcr::get_mmio_address_from_base(debug_base)?;
235    let mut edprcr = Edprcr(interface.read_word_32(address)?);
236
237    edprcr.set_cwrr(true);
238
239    interface.write_word_32(address, edprcr.into())?;
240
241    // Wait until reset happens
242    let address = Edprsr::get_mmio_address_from_base(debug_base)?;
243
244    loop {
245        let edprsr = Edprsr(interface.read_word_32(address)?);
246        if edprsr.sr() {
247            break;
248        }
249    }
250
251    Ok(())
252}
253
254/// DebugCoreStart for v8 Cortex-A devices
255fn armv8a_core_start(
256    core: &mut dyn ArmMemoryInterface,
257    debug_base: Option<u64>,
258    cti_base: Option<u64>,
259) -> Result<(), ArmError> {
260    use crate::architecture::arm::core::armv8a_debug_regs::{
261        CtiControl, CtiGate, CtiOuten, Edlar, Edscr, Oslar,
262    };
263
264    let debug_base =
265        debug_base.ok_or_else(|| ArmError::from(ArmDebugSequenceError::DebugBaseNotSpecified))?;
266    let cti_base =
267        cti_base.ok_or_else(|| ArmError::from(ArmDebugSequenceError::CtiBaseNotSpecified))?;
268
269    tracing::debug!(
270        "Starting debug for ARMv8-A core with registers at {:#X}",
271        debug_base
272    );
273
274    // Lock OS register access to prevent race conditions
275    let address = Edlar::get_mmio_address_from_base(debug_base)?;
276    core.write_word_32(address, Edlar(0).into())?;
277
278    // Unlock the OS Lock to enable access to debug registers
279    let address = Oslar::get_mmio_address_from_base(debug_base)?;
280    core.write_word_32(address, Oslar(0).into())?;
281
282    // Configure CTI
283    let mut cticontrol = CtiControl(0);
284    cticontrol.set_glben(true);
285
286    let address = CtiControl::get_mmio_address_from_base(cti_base)?;
287    core.write_word_32(address, cticontrol.into())?;
288
289    // Gate all events by default
290    let address = CtiGate::get_mmio_address_from_base(cti_base)?;
291    core.write_word_32(address, 0)?;
292
293    // Configure output channels for halt and resume
294    // Channel 0 - halt requests
295    let mut ctiouten = CtiOuten(0);
296    ctiouten.set_outen(0, 1);
297
298    let address = CtiOuten::get_mmio_address_from_base(cti_base)?;
299    core.write_word_32(address, ctiouten.into())?;
300
301    // Channel 1 - resume requests
302    let mut ctiouten = CtiOuten(0);
303    ctiouten.set_outen(1, 1);
304
305    let address = CtiOuten::get_mmio_address_from_base(cti_base)? + 4;
306    core.write_word_32(address, ctiouten.into())?;
307
308    // Enable halting
309    let address = Edscr::get_mmio_address_from_base(debug_base)?;
310    let mut edscr = Edscr(core.read_word_32(address)?);
311
312    if edscr.hde() && !edscr.ma() {
313        tracing::debug!("Core is already in debug mode, no need to enable it again");
314        return Ok(());
315    }
316
317    edscr.set_hde(true);
318    edscr.set_ma(false);
319    core.write_word_32(address, edscr.into())?;
320
321    Ok(())
322}
323
324/// DebugCoreStart for Cortex-M devices
325pub(crate) fn cortex_m_core_start(core: &mut dyn ArmMemoryInterface) -> Result<(), ArmError> {
326    use crate::architecture::arm::core::armv7m::Dhcsr;
327
328    let current_dhcsr = Dhcsr(core.read_word_32(Dhcsr::get_mmio_address())?);
329
330    // Note: Manual addition for debugging, not part of the original DebugCoreStart function
331    if current_dhcsr.c_debugen() {
332        tracing::debug!("Core is already in debug mode, no need to enable it again");
333        return Ok(());
334    }
335    // -- End addition
336
337    let mut dhcsr = Dhcsr(0);
338    dhcsr.set_c_debugen(true);
339    dhcsr.enable_write();
340
341    core.write_word_32(Dhcsr::get_mmio_address(), dhcsr.into())?;
342
343    Ok(())
344}
345
346/// ResetCatchClear for Cortex-M devices
347fn cortex_m_reset_catch_clear(core: &mut dyn ArmMemoryInterface) -> Result<(), ArmError> {
348    use crate::architecture::arm::core::armv7m::Demcr;
349
350    // Clear reset catch bit
351    let mut demcr = Demcr(core.read_word_32(Demcr::get_mmio_address())?);
352    demcr.set_vc_corereset(false);
353
354    core.write_word_32(Demcr::get_mmio_address(), demcr.into())?;
355    Ok(())
356}
357
358/// ResetCatchSet for Cortex-M devices
359fn cortex_m_reset_catch_set(core: &mut dyn ArmMemoryInterface) -> Result<(), ArmError> {
360    use crate::architecture::arm::core::armv7m::{Demcr, Dhcsr};
361
362    // Request halt after reset
363    let mut demcr = Demcr(core.read_word_32(Demcr::get_mmio_address())?);
364    demcr.set_vc_corereset(true);
365
366    core.write_word_32(Demcr::get_mmio_address(), demcr.into())?;
367
368    // Clear the status bits by reading from DHCSR
369    let _ = core.read_word_32(Dhcsr::get_mmio_address())?;
370
371    Ok(())
372}
373
374/// ResetSystem for Cortex-M devices
375fn cortex_m_reset_system(interface: &mut dyn ArmMemoryInterface) -> Result<(), ArmError> {
376    use crate::architecture::arm::core::armv7m::Aircr;
377
378    let mut aircr = Aircr(0);
379    aircr.vectkey();
380    aircr.set_sysresetreq(true);
381
382    interface.write_word_32(Aircr::get_mmio_address(), aircr.into())?;
383
384    cortex_m_wait_for_reset(interface)
385}
386
387/// Wait for Cortex-M device to reset
388pub(crate) fn cortex_m_wait_for_reset(
389    interface: &mut dyn ArmMemoryInterface,
390) -> Result<(), ArmError> {
391    use crate::architecture::arm::core::armv7m::Dhcsr;
392
393    let start = Instant::now();
394
395    // PSOC 6 documentation states 600ms is the maximum possible time
396    // before the debug port becomes available again after reset
397    while start.elapsed() < Duration::from_millis(600) {
398        let dhcsr = match interface.read_word_32(Dhcsr::get_mmio_address()) {
399            // Some combinations of debug probe and target (in
400            // particular, hs-probe and ATSAMD21) result in
401            // register read errors while the target is
402            // resetting.
403            Ok(val) => Dhcsr(val),
404            Err(ArmError::AccessPort {
405                source: AccessPortError::RegisterRead { source, .. },
406                ..
407            }) => {
408                if let Some(ArmError::Dap(DapError::NoAcknowledge)) =
409                    source.downcast_ref::<ArmError>()
410                {
411                    // On PSOC 6, a system reset resets the SWD interface as well,
412                    // so we have to reinitialize.
413                    if let Ok(probe) = interface.get_arm_debug_interface() {
414                        probe.reinitialize()?;
415                    }
416                }
417                continue;
418            }
419            Err(err) => return Err(err),
420        };
421        if !dhcsr.s_reset_st() {
422            return Ok(());
423        }
424    }
425
426    Err(ArmError::Timeout)
427}
428
429/// A interface to operate debug sequences for ARM targets.
430///
431/// Should be implemented on a custom handle for chips that require special sequence code.
432pub trait ArmDebugSequence: Send + Sync + Debug {
433    /// Assert a system-wide reset line nRST. This is based on the
434    /// `ResetHardwareAssert` function from the [ARM SVD Debug Description].
435    ///
436    /// [ARM SVD Debug Description]: https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/debug_description.html#resetHardwareAssert
437    #[doc(alias = "ResetHardwareAssert")]
438    fn reset_hardware_assert(&self, interface: &mut dyn DapProbe) -> Result<(), ArmError> {
439        let mut n_reset = Pins(0);
440        n_reset.set_nreset(true);
441
442        let _ = interface.swj_pins(0, n_reset.0 as u32, 0)?;
443
444        Ok(())
445    }
446
447    /// De-Assert a system-wide reset line nRST. This is based on the
448    /// `ResetHardwareDeassert` function from the [ARM SVD Debug Description].
449    ///
450    /// This function is called _once_ when using the "connect under reset" functionality,
451    /// after a connection to each core has been established, and it is expected that after
452    /// this function is called, the target will start executing code.
453    ///
454    /// The provided `default_ap` is the default access port that should be used for memory
455    /// access, if the sequence requires it.
456    ///
457    /// [ARM SVD Debug Description]: https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/debug_description.html#resetHardwareDeassert
458    #[doc(alias = "ResetHardwareDeassert")]
459    fn reset_hardware_deassert(
460        &self,
461        probe: &mut dyn ArmDebugInterface,
462        _default_ap: &FullyQualifiedApAddress,
463    ) -> Result<(), ArmError> {
464        let mut n_reset = Pins(0);
465        n_reset.set_nreset(true);
466        let n_reset = n_reset.0 as u32;
467
468        let can_read_pins = probe.swj_pins(n_reset, n_reset, 0)? != 0xffff_ffff;
469
470        if can_read_pins {
471            let start = Instant::now();
472
473            loop {
474                if Pins(probe.swj_pins(n_reset, n_reset, 0)? as u8).nreset() {
475                    return Ok(());
476                }
477                if start.elapsed() >= Duration::from_secs(1) {
478                    return Err(ArmError::Timeout);
479                }
480                thread::sleep(Duration::from_millis(100));
481            }
482        } else {
483            thread::sleep(Duration::from_millis(100));
484            Ok(())
485        }
486    }
487
488    /// Prepare the target debug port for connection. This is based on the `DebugPortSetup` function
489    /// from the [ARM SVD Debug Description].
490    ///
491    /// After this function has been executed, it should be possible to read and write registers
492    /// using SWD requests.
493    ///
494    /// If this function cannot read the DPIDR register, it will retry up to 5 times, and return an
495    /// error if it still cannot read it.
496    ///
497    /// [ARM SVD Debug Description]:
498    ///     https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/debug_description.html#debugPortSetup
499    #[doc(alias = "DebugPortSetup")]
500    fn debug_port_setup(
501        &self,
502        interface: &mut dyn DapProbe,
503        dp: DpAddress,
504    ) -> Result<(), ArmError> {
505        // TODO: Handle this differently for ST-Link?
506        tracing::debug!("Setting up debug port {dp:x?}");
507
508        // A multidrop address implies SWD version 2 and dormant state.  In
509        // cases where SWD version 2 is used but not multidrop addressing
510        // (ex. ADIv6), the SWD version 1 sequence is attempted before trying
511        // the SWD version 2 sequence.
512        let mut has_dormant = matches!(dp, DpAddress::Multidrop(_));
513
514        fn alert_sequence(interface: &mut dyn DapProbe) -> Result<(), ArmError> {
515            tracing::trace!("Sending Selection Alert sequence");
516
517            // Ensure target is not in the middle of detecting a selection alert
518            interface.swj_sequence(8, 0xFF)?;
519
520            // Alert Sequence Bits  0.. 63
521            interface.swj_sequence(64, 0x86852D956209F392)?;
522
523            // Alert Sequence Bits 64..127
524            interface.swj_sequence(64, 0x19BC0EA2E3DDAFE9)?;
525
526            Ok(())
527        }
528
529        // TODO: Use atomic block
530
531        let mut result = Ok(());
532        const NUM_RETRIES: usize = 5;
533        for retry in 0..NUM_RETRIES {
534            // Ensure current debug interface is in reset state.
535            swd_line_reset(interface, 0)?;
536
537            // Make sure the debug port is in the correct mode based on what the probe
538            // has selected via active_protocol
539            match interface.active_protocol() {
540                Some(WireProtocol::Jtag) => {
541                    if has_dormant {
542                        tracing::debug!("Select Dormant State (from SWD)");
543                        interface.swj_sequence(16, 0xE3BC)?;
544
545                        // Send alert sequence
546                        alert_sequence(interface)?;
547
548                        // 4 cycles SWDIO/TMS LOW + 8-Bit JTAG Activation Code (0x0A)
549                        interface.swj_sequence(12, 0x0A0)?;
550                    } else {
551                        // Execute SWJ-DP Switch Sequence SWD to JTAG (0xE73C).
552                        interface.swj_sequence(16, 0xE73C)?;
553                    }
554
555                    // Execute at least >5 TCK cycles with TMS high to enter the Test-Logic-Reset state
556                    interface.swj_sequence(6, 0x3F)?;
557
558                    // Enter Run-Test-Idle state, as required by the DAP_Transfer command when using JTAG
559                    interface.jtag_sequence(1, false, 0x01)?;
560
561                    // Configure JTAG IR lengths in probe
562                    interface.configure_jtag(false)?;
563                }
564                Some(WireProtocol::Swd) => {
565                    if has_dormant {
566                        // Select Dormant State (from JTAG)
567                        tracing::debug!("SelectV1 Dormant State (from JTAG)");
568                        interface.swj_sequence(31, 0x33BBBBBA)?;
569
570                        // Leave dormant state
571                        alert_sequence(interface)?;
572
573                        // 4 cycles SWDIO/TMS LOW + 8-Bit SWD Activation Code (0x1A)
574                        interface.swj_sequence(12, 0x1A0)?;
575                    } else {
576                        // Execute SWJ-DP Switch Sequence JTAG to SWD (0xE79E).
577                        // Change if SWJ-DP uses deprecated switch code (0xEDB6).
578                        interface.swj_sequence(16, 0xE79E)?;
579
580                        // > 50 cycles SWDIO/TMS High, at least 2 idle cycles (SWDIO/TMS Low).
581                        // -> done in debug_port_connect
582                    }
583                }
584                _ => {
585                    return Err(ArmDebugSequenceError::SequenceSpecific(
586                        "Cannot detect current protocol".into(),
587                    )
588                    .into());
589                }
590            }
591
592            // End of atomic block.
593
594            // SWD or JTAG should now be activated, so we can try and connect to the debug port.
595            result = self.debug_port_connect(interface, dp);
596            if result.is_ok() {
597                // Successful connection, we can stop retrying.
598                break;
599            }
600
601            // If two retries have failed, try using SWD version 2 wake from
602            // dormant sequence.
603            if retry >= 1 {
604                has_dormant = true;
605            }
606        }
607
608        result
609    }
610
611    /// Connect to the target debug port and power it up. This is based on the
612    /// `DebugPortStart` function from the [ARM SVD Debug Description].
613    ///
614    /// [ARM SVD Debug Description]: https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/debug_description.html#debugPortStart
615    #[doc(alias = "DebugPortStart")]
616    fn debug_port_start(
617        &self,
618        interface: &mut dyn DapAccess,
619        dp: DpAddress,
620    ) -> Result<(), ArmError> {
621        interface.write_dp_register(dp, SelectV1(0))?;
622        let dpidr: DPIDR = interface.read_dp_register(dp)?;
623
624        // Clear all errors and see if we need to power up the target
625        let ctrl = interface.read_dp_register::<Ctrl>(dp)?;
626        let powered_down = if dpidr.version() == 0 || dpidr.designer() == 0 {
627            // Abort the current transaction. This is the only bit present in `Abort` in `DPv0`.
628            let mut abort = Abort(0);
629            abort.set_dapabort(true);
630            interface.write_dp_register(dp, abort)?;
631
632            // To clear errors on DPv0, we need to set W1C values for STICKYORUN, STICKYCMP, and STICKYERR
633            interface.write_dp_register::<Ctrl>(dp, Ctrl(0x32))?;
634
635            // DPv0 always needs to be powered up
636            true
637        } else {
638            // CMSIS says this is only necessary to do inside the `if powered_down`, but
639            // without it here, nRF52840 faults in the next access.
640            let mut abort = Abort(0);
641            abort.set_dapabort(true);
642            abort.set_orunerrclr(true);
643            abort.set_wderrclr(true);
644            abort.set_stkerrclr(true);
645            abort.set_stkcmpclr(true);
646            interface.write_dp_register(dp, abort)?;
647
648            !(ctrl.csyspwrupack() && ctrl.cdbgpwrupack())
649        };
650
651        if powered_down {
652            tracing::info!("Debug port {dp:x?} is powered down, powering up");
653            let mut ctrl = Ctrl(0);
654            ctrl.set_cdbgpwrupreq(true);
655            ctrl.set_csyspwrupreq(true);
656            if !dpidr.min() {
657                // Init AP Transfer Mode, Transaction Counter, and Lane Mask (Normal Transfer Mode,
658                // Include all Byte Lanes).
659                //
660                // Setting this on MINDP is unpredictable.
661                ctrl.set_mask_lane(0b1111);
662            }
663
664            match interface
665                .write_dp_register(dp, ctrl.clone())
666                .and_then(|_| interface.flush())
667            {
668                Ok(()) => {}
669                Err(e @ ArmError::Dap(DapError::NoAcknowledge)) => {
670                    // If we get a NACK from the power-up request, ignore the error & perform a line reset.
671                    // (CMSIS-DAP transports read DP.RDBUFF right after a write to DP.CTRL_STAT.
672                    //  This fails in some cases on PSOC 6, for example if the device is waking from DeepSleep.
673                    //  If something really went wrong, we'll hit an error or timeout in the polling loop below.)
674                    let Some(probe) = interface.try_dap_probe_mut() else {
675                        tracing::warn!(
676                            "Power-up request returned NACK, but we don't have a DapProbe, so we can't reconnect"
677                        );
678                        return Err(e);
679                    };
680                    tracing::info!("Power-up request returned NACK, reconnecting");
681                    self.debug_port_connect(probe, dp)?;
682                }
683                Err(e) => return Err(e),
684            }
685
686            let start = Instant::now();
687            loop {
688                let ctrl = interface.read_dp_register::<Ctrl>(dp)?;
689                if ctrl.csyspwrupack() && ctrl.cdbgpwrupack() {
690                    break;
691                }
692                if start.elapsed() >= Duration::from_secs(1) {
693                    return Err(ArmError::Timeout);
694                }
695            }
696
697            // TODO: Handle JTAG Specific part
698
699            // TODO: Only run the following code when the SWD protocol is used
700
701            // Write the CTRL register after powerup completes. It's unknown if this is required
702            // for all devices, but there's little harm in writing the same value back to the
703            // register after powerup.
704            interface.write_dp_register(dp, ctrl)?;
705
706            let ctrl_reg: Ctrl = interface.read_dp_register(dp)?;
707            if !(ctrl_reg.csyspwrupack() && ctrl_reg.cdbgpwrupack()) {
708                tracing::error!("Debug power request failed");
709                return Err(DebugPortError::TargetPowerUpFailed.into());
710            }
711
712            // According to CMSIS docs, here's where we would clear errors
713            // in ABORT, but we do that above instead.
714        }
715
716        Ok(())
717    }
718
719    /// Initialize core debug system. This is based on the
720    /// `DebugCoreStart` function from the [ARM SVD Debug Description].
721    ///
722    /// [ARM SVD Debug Description]: https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/debug_description.html#debugCoreStart
723    #[doc(alias = "DebugCoreStart")]
724    fn debug_core_start(
725        &self,
726        interface: &mut dyn ArmDebugInterface,
727        core_ap: &FullyQualifiedApAddress,
728        core_type: CoreType,
729        debug_base: Option<u64>,
730        cti_base: Option<u64>,
731    ) -> Result<(), ArmError> {
732        let mut core = interface.memory_interface(core_ap)?;
733
734        // Dispatch based on core type (Cortex-A vs M)
735        match core_type {
736            CoreType::Armv7a => armv7a_core_start(&mut *core, debug_base),
737            CoreType::Armv8a => armv8a_core_start(&mut *core, debug_base, cti_base),
738            CoreType::Armv6m | CoreType::Armv7m | CoreType::Armv7em | CoreType::Armv8m => {
739                cortex_m_core_start(&mut *core)
740            }
741            _ => panic!("Logic inconsistency bug - non ARM core type passed {core_type:?}"),
742        }
743    }
744
745    /// Configure the target to stop code execution after a reset. After this, the core will halt when it comes
746    /// out of reset. This is based on the `ResetCatchSet` function from
747    /// the [ARM SVD Debug Description].
748    ///
749    /// [ARM SVD Debug Description]: https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/debug_description.html#resetCatchSet
750    #[doc(alias = "ResetCatchSet")]
751    fn reset_catch_set(
752        &self,
753        core: &mut dyn ArmMemoryInterface,
754        core_type: CoreType,
755        debug_base: Option<u64>,
756    ) -> Result<(), ArmError> {
757        // Dispatch based on core type (Cortex-A vs M)
758        match core_type {
759            CoreType::Armv7a => armv7a_reset_catch_set(core, debug_base),
760            CoreType::Armv8a => armv8a_reset_catch_set(core, debug_base),
761            CoreType::Armv6m | CoreType::Armv7m | CoreType::Armv7em | CoreType::Armv8m => {
762                cortex_m_reset_catch_set(core)
763            }
764            _ => panic!("Logic inconsistency bug - non ARM core type passed {core_type:?}"),
765        }
766    }
767
768    /// Free hardware resources allocated by ResetCatchSet.
769    /// This is based on the `ResetCatchSet` function from
770    /// the [ARM SVD Debug Description].
771    ///
772    /// [ARM SVD Debug Description]: https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/debug_description.html#resetCatchClear
773    #[doc(alias = "ResetCatchClear")]
774    fn reset_catch_clear(
775        &self,
776        core: &mut dyn ArmMemoryInterface,
777        core_type: CoreType,
778        debug_base: Option<u64>,
779    ) -> Result<(), ArmError> {
780        // Dispatch based on core type (Cortex-A vs M)
781        match core_type {
782            CoreType::Armv7a => armv7a_reset_catch_clear(core, debug_base),
783            CoreType::Armv8a => armv8a_reset_catch_clear(core, debug_base),
784            CoreType::Armv6m | CoreType::Armv7m | CoreType::Armv7em | CoreType::Armv8m => {
785                cortex_m_reset_catch_clear(core)
786            }
787            _ => panic!("Logic inconsistency bug - non ARM core type passed {core_type:?}"),
788        }
789    }
790
791    /// Enable target trace capture.
792    ///
793    /// # Note
794    /// This function is responsible for configuring any of the CoreSight link components, such as
795    /// trace funnels, to route trace data to the specified trace sink.
796    ///
797    /// This is based on the `TraceStart` function from the [ARM SVD Debug Description].
798    ///
799    /// [ARM SVD Debug Description]: https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/debug_description.html#traceStart
800    fn trace_start(
801        &self,
802        interface: &mut dyn ArmDebugInterface,
803        components: &[CoresightComponent],
804        _sink: &TraceSink,
805    ) -> Result<(), ArmError> {
806        // As a default implementation, enable all of the slave port inputs of any trace funnels
807        // found. This should enable _all_ sinks simultaneously. Device-specific implementations
808        // can be written to properly configure the specified sink.
809        for trace_funnel in components
810            .iter()
811            .filter_map(|comp| comp.find_component(PeripheralType::TraceFunnel))
812        {
813            let mut funnel = TraceFunnel::new(interface, trace_funnel);
814            funnel.unlock()?;
815            funnel.enable_port(0xFF)?;
816        }
817
818        Ok(())
819    }
820
821    /// Executes a system-wide reset without debug domain (or warm-reset that preserves debug connection) via software mechanisms,
822    /// for example AIRCR.SYSRESETREQ.  This is based on the
823    /// `ResetSystem` function from the [ARM SVD Debug Description].
824    ///
825    /// [ARM SVD Debug Description]: https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/debug_description.html#resetSystem
826    #[doc(alias = "ResetSystem")]
827    fn reset_system(
828        &self,
829        interface: &mut dyn ArmMemoryInterface,
830        core_type: CoreType,
831        debug_base: Option<u64>,
832    ) -> Result<(), ArmError> {
833        // Dispatch based on core type (Cortex-A vs M)
834        match core_type {
835            CoreType::Armv7a => armv7a_reset_system(interface, debug_base),
836            CoreType::Armv8a => armv8a_reset_system(interface, debug_base),
837            CoreType::Armv6m | CoreType::Armv7m | CoreType::Armv7em | CoreType::Armv8m => {
838                cortex_m_reset_system(interface)
839            }
840            _ => panic!("Logic inconsistency bug - non ARM core type passed {core_type:?}"),
841        }
842    }
843
844    /// Check if the device is in a locked state and unlock it.
845    /// Use query command elements for user confirmation.
846    /// Executed after having powered up the debug port. This is based on the
847    /// `DebugDeviceUnlock` function from the [ARM SVD Debug Description].
848    ///
849    /// [ARM SVD Debug Description]: https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/debug_description.html#debugDeviceUnlock
850    #[doc(alias = "DebugDeviceUnlock")]
851    fn debug_device_unlock(
852        &self,
853        _interface: &mut dyn ArmDebugInterface,
854        _default_ap: &FullyQualifiedApAddress,
855        _permissions: &crate::Permissions,
856    ) -> Result<(), ArmError> {
857        tracing::debug!("debug_device_unlock - empty by default");
858        Ok(())
859    }
860
861    /// Executed before step or run command to support recovery from a lost target connection, e.g. after a low power mode.
862    /// This is based on the `RecoverSupportStart` function from the [ARM SVD Debug Description].
863    ///
864    /// [ARM SVD Debug Description]: https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/debug_description.htmll#recoverSupportStart
865    #[doc(alias = "RecoverSupportStart")]
866    fn recover_support_start(
867        &self,
868        _interface: &mut dyn ArmMemoryInterface,
869    ) -> Result<(), ArmError> {
870        // Empty by default
871        Ok(())
872    }
873
874    /// Executed when the debugger session is disconnected from the core.
875    ///
876    /// This is based on the `DebugCoreStop` function from the [ARM SVD Debug Description].
877    ///
878    /// [ARM SVD Debug Description]: https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/debug_description.html#debugCoreStop
879    #[doc(alias = "DebugCoreStop")]
880    fn debug_core_stop(
881        &self,
882        interface: &mut dyn ArmMemoryInterface,
883        core_type: CoreType,
884    ) -> Result<(), ArmError> {
885        if core_type.is_cortex_m() {
886            // System Control Space (SCS) offset as defined in Armv6-M/Armv7-M.
887            // Disable Core Debug via DHCSR
888            let mut dhcsr = Dhcsr(0);
889            dhcsr.enable_write();
890            interface.write_word_32(Dhcsr::get_mmio_address(), dhcsr.0)?;
891
892            // Disable DWT and ITM blocks, DebugMonitor handler,
893            // halting debug traps, and Reset Vector Catch.
894            interface.write_word_32(Demcr::get_mmio_address(), 0x0)?;
895        }
896
897        Ok(())
898    }
899
900    /// Sequence executed when disconnecting from a debug port.
901    ///
902    /// Based on the `DebugPortStop` function from the [ARM SVD Debug Description].
903    ///
904    /// [ARM SVD Debug Description]: https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/debug_description.html#debugPortStop
905    #[doc(alias = "DebugPortStop")]
906    fn debug_port_stop(&self, interface: &mut dyn DapProbe, dp: DpAddress) -> Result<(), ArmError> {
907        tracing::info!("Powering down debug port {dp:x?}");
908        // Select Bank 0
909        interface.raw_write_register(SelectV1::ADDRESS.into(), 0)?;
910
911        // De-assert debug power request
912        interface.raw_write_register(Ctrl::ADDRESS.into(), 0)?;
913
914        // Wait for the power domains to go away
915        let start = Instant::now();
916        loop {
917            let ctrl = interface.raw_read_register(Ctrl::ADDRESS.into())?;
918            let ctrl = Ctrl(ctrl);
919            if !(ctrl.csyspwrupack() || ctrl.cdbgpwrupack()) {
920                return Ok(());
921            }
922
923            if start.elapsed() >= Duration::from_secs(1) {
924                return Err(ArmError::Timeout);
925            }
926        }
927    }
928
929    /// Perform a SWD line reset or enter the JTAG Run-Test-Idle state, and then try to connect to a debug port.
930    ///
931    /// This is executed as part of the standard `debug_port_setup` sequence,
932    /// and when switching between debug ports in a SWD multi-drop configuration.
933    ///
934    /// If the `dp` parameter is `DpAddress::Default`, a read of the DPIDR register will be
935    /// performed after the line reset.
936    ///
937    /// If the `dp` parameter is `DpAddress::Multidrop`, a write of the `TARGETSEL` register is
938    /// done after the line reset, followed by a read of the `DPIDR` register.
939    ///
940    /// This is not based on a sequence from the Open-CMSIS-Pack standard.
941    #[tracing::instrument(level = "debug", skip_all)]
942    fn debug_port_connect(
943        &self,
944        interface: &mut dyn DapProbe,
945        dp: DpAddress,
946    ) -> Result<(), ArmError> {
947        match interface.active_protocol() {
948            Some(WireProtocol::Jtag) => {
949                tracing::debug!("JTAG: No special sequence needed to connect to debug port");
950                return Ok(());
951            }
952            Some(WireProtocol::Swd) => {
953                tracing::debug!("SWD: Connecting to debug port with address {:x?}", dp);
954            }
955            None => {
956                return Err(ArmDebugSequenceError::SequenceSpecific(
957                    "Cannot detect current protocol".into(),
958                )
959                .into());
960            }
961        }
962
963        // Time guard for DPIDR register to become readable after line reset
964        const RESET_RECOVERY_TIMEOUT: Duration = Duration::from_secs(1);
965        const RESET_RECOVERY_RETRY_INTERVAL: Duration = Duration::from_millis(5);
966
967        // Enter SWD Line Reset State, afterwards at least 2 idle cycles (SWDIO/TMS Low)
968        // Guard gives time for the target to recover
969        let guard = Instant::now();
970        let dpidr = loop {
971            swd_line_reset(interface, 3)?;
972
973            // If multidrop is used, we now have to select a target
974            if let DpAddress::Multidrop(targetsel) = dp {
975                // Deselect other debug ports first?
976
977                tracing::debug!("Writing targetsel {:#x}", targetsel);
978                // TARGETSEL write.
979                // The TARGETSEL write is not ACKed by design. We can't use a normal register write
980                // because many probes don't even send the data phase when NAK.
981                let parity = targetsel.count_ones() % 2;
982                let data = ((parity as u64) << 45) | ((targetsel as u64) << 13) | 0x1f99;
983
984                // Should this be a swd_sequence?
985                // Technically we shouldn't drive SWDIO all the time when sending a request.
986                interface.swj_sequence(6 * 8, data)?;
987            }
988
989            tracing::debug!("Reading DPIDR to enable SWD interface");
990
991            // Read DPIDR to enable SWD interface.
992            match interface.raw_read_register(RegisterAddress::DpRegister(DPIDR::ADDRESS)) {
993                Ok(x) => break x,
994                Err(z) => {
995                    if guard.elapsed() > RESET_RECOVERY_TIMEOUT {
996                        tracing::debug!("DPIDR didn't become readable within guard time");
997                        return Err(z);
998                    }
999                }
1000            }
1001
1002            // Be nice - checking at intervals is plenty
1003            std::thread::sleep(RESET_RECOVERY_RETRY_INTERVAL);
1004        };
1005        tracing::debug!(
1006            "DPIDR became readable after {}ms",
1007            guard.elapsed().as_millis()
1008        );
1009        tracing::debug!("Result of DPIDR read: {:#x?}", dpidr);
1010
1011        tracing::debug!("Clearing errors using ABORT register");
1012        let mut abort = Abort(0);
1013        abort.set_orunerrclr(true);
1014        abort.set_wderrclr(true);
1015        abort.set_stkerrclr(true);
1016        abort.set_stkcmpclr(true);
1017
1018        // DPBANKSEL does not matter for ABORT
1019        interface.raw_write_register(Abort::ADDRESS.into(), abort.0)?;
1020        interface.raw_flush()?;
1021
1022        // Check that we are connected to the right DP
1023
1024        if let DpAddress::Multidrop(targetsel) = dp {
1025            tracing::debug!("Checking TARGETID and DLPIDR match");
1026            // Select DP Bank 2
1027            interface.raw_write_register(SelectV1::ADDRESS.into(), 2)?;
1028
1029            let target_id = interface.raw_read_register(TARGETID::ADDRESS.into())?;
1030
1031            // Select DP Bank 3
1032            interface.raw_write_register(SelectV1::ADDRESS.into(), 3)?;
1033            let dlpidr = interface.raw_read_register(DLPIDR::ADDRESS.into())?;
1034
1035            const TARGETID_MASK: u32 = 0x0FFF_FFFF;
1036            const DLPIDR_MASK: u32 = 0xF000_0000;
1037
1038            let targetid_match = (target_id & TARGETID_MASK) == (targetsel & TARGETID_MASK);
1039            let dlpdir_match = (dlpidr & DLPIDR_MASK) == (targetsel & DLPIDR_MASK);
1040
1041            if !(targetid_match && dlpdir_match) {
1042                tracing::warn!(
1043                    "Target ID and DLPIDR do not match, failed to select debug port. Target ID: {:#x?}, DLPIDR: {:#x?}",
1044                    target_id,
1045                    dlpidr
1046                );
1047                return Err(ArmError::Other(
1048                    "Target ID and DLPIDR do not match, failed to select debug port".to_string(),
1049                ));
1050            }
1051        }
1052
1053        interface.raw_write_register(SelectV1::ADDRESS.into(), 0)?;
1054        let ctrl_stat = interface.raw_read_register(Ctrl::ADDRESS.into()).map(Ctrl);
1055
1056        match ctrl_stat {
1057            Ok(ctrl_stat) => {
1058                tracing::debug!("Result of CTRL/STAT read: {:?}", ctrl_stat);
1059            }
1060            Err(e) => {
1061                // According to the SPEC, reading from CTRL/STAT should never fail. In practice,
1062                // it seems to fail sometimes.
1063                tracing::debug!("Failed to read CTRL/STAT: {:?}", e);
1064            }
1065        }
1066
1067        Ok(())
1068    }
1069
1070    /// This ARM sequence is called if an image was flashed to RAM directly.
1071    /// It will perform the necessary preparation to run that image.
1072    ///
1073    /// Core should be already `reset_and_halt`ed right before this call.
1074    fn prepare_running_on_ram(
1075        &self,
1076        vector_table_addr: u64,
1077        session: &mut Session,
1078    ) -> Result<(), crate::Error> {
1079        tracing::info!("Performing RAM flash start");
1080        const SP_MAIN_OFFSET: usize = 0;
1081        const RESET_VECTOR_OFFSET: usize = 1;
1082
1083        if session.list_cores().len() > 1 {
1084            return Err(crate::Error::NotImplemented(
1085                "multi-core ram flash start not implemented yet",
1086            ));
1087        }
1088
1089        let (_, core_type) = session.list_cores()[0];
1090        match core_type {
1091            CoreType::Armv7a | CoreType::Armv8a => {
1092                return Err(crate::Error::NotImplemented(
1093                    "RAM flash not implemented for ARM Cortex-A",
1094                ));
1095            }
1096            CoreType::Armv6m | CoreType::Armv7m | CoreType::Armv7em | CoreType::Armv8m => {
1097                tracing::debug!("RAM flash start for Cortex-M single core target");
1098                let mut core = session.core(0)?;
1099                // See ARMv7-M Architecture Reference Manual Chapter B1.5 for more details. The
1100                // process appears to be the same for the other Cortex-M architectures as well.
1101                let vtor = Vtor(vector_table_addr as u32);
1102                let mut first_table_entries: [u32; 2] = [0; 2];
1103                core.read_32(vector_table_addr, &mut first_table_entries)?;
1104                // The first entry in the vector table is the SP_main reset value of the main stack pointer,
1105                // so we set the stack pointer register accordingly.
1106                core.write_core_reg(SP.id, first_table_entries[SP_MAIN_OFFSET])?;
1107                // The second entry in the vector table is the reset vector. It needs to be loaded
1108                // as the initial PC value on a reset, see chapter A2.3.1 of the reference manual.
1109                core.write_core_reg(PC.id, first_table_entries[RESET_VECTOR_OFFSET])?;
1110                core.write_word_32(Vtor::get_mmio_address(), vtor.0)?;
1111            }
1112            _ => {
1113                panic!("Logic inconsistency bug - non ARM core type passed {core_type:?}");
1114            }
1115        }
1116        Ok(())
1117    }
1118
1119    /// Return the Debug Erase Sequence implementation if it exists
1120    fn debug_erase_sequence(&self) -> Option<Arc<dyn DebugEraseSequence>> {
1121        None
1122    }
1123
1124    /// Return the APs that are expected to work.
1125    fn allowed_access_ports(&self) -> Vec<u8> {
1126        (0..=255).collect()
1127    }
1128}
1129
1130/// Chip-Erase Handling via the Device's Debug Interface
1131pub trait DebugEraseSequence: Send + Sync {
1132    /// Perform Chip-Erase by vendor specific means.
1133    ///
1134    /// Some devices provide custom methods for mass erasing the entire flash area and even reset
1135    /// other non-volatile chip state to its default setting.
1136    ///
1137    /// # Errors
1138    /// May fail if the device is e.g. permanently locked or due to communication issues with the device.
1139    /// Some devices require the probe to be disconnected and re-attached after a successful chip-erase in
1140    /// which case it will return `Error::Probe(DebugProbeError::ReAttachRequired)`
1141    fn erase_all(&self, _interface: &mut dyn ArmDebugInterface) -> Result<(), ArmError> {
1142        Err(ArmError::NotImplemented("erase_all"))
1143    }
1144}
1145
1146/// Perform a SWD line reset (SWDIO high for 50 clock cycles)
1147///
1148/// After the line reset, SWDIO will be kept low for `swdio_low_cycles` cycles.
1149fn swd_line_reset(interface: &mut dyn DapProbe, swdio_low_cycles: u8) -> Result<(), ArmError> {
1150    assert!(swdio_low_cycles + 51 <= 64);
1151
1152    tracing::debug!("Performing SWD line reset");
1153    interface.swj_sequence(51 + swdio_low_cycles, 0x0007_FFFF_FFFF_FFFF)?;
1154
1155    Ok(())
1156}