Skip to main content

probe_rs/architecture/arm/core/
armv7m.rs

1//! Register types and the core interface for armv7-M
2
3use super::{
4    CortexMState, Dfsr,
5    cortex_m::Mvfr0,
6    registers::cortex_m::{
7        CORTEX_M_CORE_REGISTERS, CORTEX_M_WITH_FP_CORE_REGISTERS, FP, PC, RA, SP,
8    },
9};
10use crate::{
11    BreakpointCause, CoreRegister, CoreType, InstructionSet, MemoryInterface,
12    architecture::arm::{
13        ArmError, core::registers::cortex_m::XPSR, memory::ArmMemoryInterface,
14        sequences::ArmDebugSequence,
15    },
16    core::{
17        Architecture, CoreInformation, CoreInterface, CoreRegisters, CoreStatus, HaltReason,
18        MemoryMappedRegister, RegisterId, RegisterValue, VectorCatchCondition,
19    },
20    error::Error,
21    memory::{CoreMemoryInterface, valid_32bit_address},
22};
23use bitfield::bitfield;
24use std::{
25    mem::size_of,
26    sync::Arc,
27    time::{Duration, Instant},
28};
29
30bitfield! {
31    /// Debug Halting Control and Status Register, DHCSR (see armv7-M Architecture Reference Manual C1.6.2)
32    ///
33    /// To write this register successfully, you need to set the debug key via [`Dhcsr::enable_write`] first!
34    #[derive(Copy, Clone)]
35    pub struct Dhcsr(u32);
36    impl Debug;
37    /// Indicates whether the processor has been reset since the last read of DHCSR:
38    /// `0`: No reset since last DHCSR read.\
39    /// `1`: At least one reset since last DHCSR read.
40    ///
41    /// This is a sticky bit, that clears to `0` on a read of DHCSR.
42    pub s_reset_st, _: 25;
43    /// When not in Debug state, indicates whether the processor has completed
44    /// the execution of an instruction since the last read of DHCSR:
45    ///
46    /// `0`: No instruction has completed since last DHCSR read.\
47    /// `1`: At least one instructions has completed since last DHCSR read.
48    ///
49    /// This is a sticky bit, that clears to `0` on a read of DHCSR.
50    ///
51    /// This bit is UNKNOWN:
52    ///
53    /// - after a Local reset, but is set to `1` as soon as the processor completes
54    /// execution of an instruction.
55    /// - when S_LOCKUP is set to 1.
56    /// - when S_HALT is set to 1.
57    ///
58    /// When the processor is not in Debug state, a debugger can check this bit to
59    /// determine if the processor is stalled on a load, store or fetch access.
60    pub s_retire_st, _: 24;
61    ///  Indicates whether the processor is locked up because of an unrecoverable
62    /// exception:
63    ///
64    /// `0`: Not locked up.\
65    /// `1`: Locked up.
66    ///
67    /// See Unrecoverable exception cases on page B1-206 for more information.
68    ///
69    /// This bit can only read as `1` when accessed by a remote debugger using the
70    /// DAP. The value of `1` indicates that the processor is running but locked up.
71    ///
72    /// The bit clears to `0` when the processor enters Debug state.
73    pub s_lockup, _: 19;
74    /// Indicates whether the processor is sleeping:
75    ///
76    /// `0`: Not sleeping.\
77    /// `1`: Sleeping.
78    ///
79    /// The debugger must set the DHCSR.C_HALT bit to `1` to gain control, or
80    /// wait for an interrupt or other wakeup event to wakeup the system
81    pub s_sleep, _: 18;
82    /// Indicates whether the processor is in Debug state:
83    ///
84    /// `0`: Not in Debug state.\
85    /// `1`: In Debug state.
86    pub s_halt, _: 17;
87    /// A handshake flag for transfers through the DCRDR:
88    ///
89    /// - Writing to DCRSR clears the bit to 0.
90    /// - Completion of the DCRDR transfer then sets the bit to 1.
91    ///
92    /// For more information about DCRDR transfers see Debug Core Register
93    /// Data Register, DCRDR on page C1-292.
94    ///
95    /// `0`: There has been a write to the DCRDR, but the transfer is not complete.\
96    /// `1`: The transfer to or from the DCRDR is complete.
97    ///
98    /// This bit is only valid when the processor is in Debug state, otherwise the
99    /// bit is UNKNOWN.
100    pub s_regrdy, _: 16;
101    /// Allow imprecise entry to Debug state. The actions on writing to this bit are:
102    ///
103    /// `0`: No action.\
104    /// `1`: Allow imprecise entry to Debug state, for example by forcing any stalled load
105    /// or store instruction to complete.
106    ///
107    /// Setting this bit to `1` allows a debugger to request imprecise entry to Debug state.
108    ///
109    /// The effect of setting this bit to `1` is UNPREDICTABLE unless the DHCSR write also sets
110    /// C_DEBUGEN and C_HALT to 1. This means that if the processor is not already in Debug
111    /// state it enters Debug state when the stalled instruction completes.
112    ///
113    /// Writing `1` to this bit makes the state of the memory system UNPREDICTABLE. Therefore, if a
114    /// debugger writes `1` to this bit it must reset the processor before leaving Debug state.
115    ///
116    /// **Note**
117    ///
118    /// - A debugger can write to the DHCSR to clear this bit to 0. However, this does not
119    /// remove the UNPREDICTABLE state of the memory system caused by setting C_SNAPSTALL to 1.
120    /// - The architecture does not guarantee that setting this bit to `1` will force entry to Debug state.
121    /// - Arm strongly recommends that a value of `1` is never written to C_SNAPSTALL when
122    /// the processor is in Debug state.
123    ///
124    /// A power-on reset sets this bit to `0`.
125    pub c_snapstall, set_c_snapstall: 5;
126    /// When debug is enabled, the debugger can write to this bit to mask
127    /// PendSV, SysTick and external configurable interrupts:
128    ///
129    /// `0`: Do not mask.\
130    /// `1`: Mask PendSV, SysTick and external configurable interrupts.
131    ///
132    /// The effect of any attempt to change the value of this bit is UNPREDICTABLE
133    /// unless both:
134    ///
135    /// - before the write to DHCSR, the value of the C_HALT bit is `1`.
136    /// - the write to the DHCSR that changes the C_MASKINTS bit also
137    /// writes `1` to the C_HALT bit.
138    ///
139    /// This means that a single write to DHCSR cannot set the C_HALT to `0` and
140    /// change the value of the C_MASKINTS bit.
141    ///
142    /// The bit does not affect NMI. When DHCSR.C_DEBUGEN is set to `0`, the
143    /// value of this bit is UNKNOWN.
144    ///
145    /// For more information about the use of this bit see Table C1-9 on
146    /// page C1-282.
147    ///
148    /// This bit is UNKNOWN after a power-on reset.
149    pub c_maskints, set_c_maskints: 3;
150    /// Processor step bit. The effects of writes to this bit are:
151    ///
152    /// `0`: Single-stepping disabled.\
153    /// `1`: Single-stepping enabled.
154    ///
155    /// For more information about the use of this bit see Table C1-9 on
156    /// page C1-282.
157    ///
158    /// This bit is UNKNOWN after a power-on reset.
159    pub c_step, set_c_step: 2;
160    /// Processor halt bit. The effects of writes to this bit are:
161    ///
162    /// `0`: Request a halted processor to run.\
163    /// `1`: Request a running processor to halt.
164    ///
165    /// Table C1-9 on page C1-282 shows the effect of writes to this bit when the
166    /// processor is in Debug state.
167    ///
168    /// This bit is `0` after a System reset
169    pub c_halt, set_c_halt: 1;
170    ///  Halting debug enable bit:
171    ///
172    /// `0`: Halting debug disabled.\
173    /// `1`: Halting debug enabled.
174    ///
175    /// If a debugger writes to DHCSR to change the value of this bit from `0` to
176    /// `1`, it must also write `0` to the C_MASKINTS bit, otherwise behavior is UNPREDICTABLE.
177    ///
178    /// This bit can only be written from the DAP. Access to the DHCSR from
179    /// software running on the processor is IMPLEMENTATION DEFINED.
180    ///
181    /// However, writes to this bit from software running on the processor are ignored.
182    ///
183    /// This bit is `0` after a power-on reset.
184    pub c_debugen, set_c_debugen: 0;
185}
186
187impl Dhcsr {
188    /// This function sets the bit to enable writes to this register.
189    pub fn enable_write(&mut self) {
190        self.0 &= !(0xffff << 16);
191        self.0 |= 0xa05f << 16;
192    }
193}
194
195impl From<u32> for Dhcsr {
196    fn from(value: u32) -> Self {
197        Self(value)
198    }
199}
200
201impl From<Dhcsr> for u32 {
202    fn from(value: Dhcsr) -> Self {
203        value.0
204    }
205}
206
207impl MemoryMappedRegister<u32> for Dhcsr {
208    const ADDRESS_OFFSET: u64 = 0xE000_EDF0;
209    const NAME: &'static str = "DHCSR";
210}
211
212/// Debug Core Register Data Register, DCRDR (see armv7-M Architecture Reference Manual C1.6.3)
213#[derive(Debug, Copy, Clone)]
214pub struct Dcrdr(u32);
215
216impl From<u32> for Dcrdr {
217    fn from(value: u32) -> Self {
218        Self(value)
219    }
220}
221
222impl From<Dcrdr> for u32 {
223    fn from(value: Dcrdr) -> Self {
224        value.0
225    }
226}
227
228impl MemoryMappedRegister<u32> for Dcrdr {
229    const ADDRESS_OFFSET: u64 = 0xE000_EDF8;
230    const NAME: &'static str = "DCRDR";
231}
232
233bitfield! {
234    /// Application Interrupt and Reset Control Register, AIRCR (see armv7-M Architecture Reference Manual B3.2.6)
235    ///
236    /// [`Aircr::vectkey`] must be called before this register can effectively be written!
237    #[derive(Copy, Clone)]
238    pub struct Aircr(u32);
239    impl Debug;
240    /// Vector Key. The value 0x05FA must be written to this register, otherwise
241    /// the register write is UNPREDICTABLE.
242    get_vectkeystat, set_vectkey: 31,16;
243    /// Indicates the memory system data endianness:
244    ///
245    /// `0`: little endian.\
246    /// `1`: big endian.
247    ///
248    /// See Endian support on page A3-44 for more information.
249    pub endianness, set_endianness: 15;
250    /// Priority grouping, indicates the binary point position.
251    ///
252    /// For information about the use of this field see Priority grouping on page B1-527.
253    ///
254    /// This field resets to `0b000`.
255    pub prigroup, set_prigroup: 10,8;
256    /// System Reset Request:
257    ///
258    /// `0`: do not request a reset.\
259    /// `1`: request reset.
260    ///
261    /// Writing `1` to this bit asserts a signal to request a reset by the external
262    /// system. The system components that are reset by this request are
263    /// IMPLEMENTATION DEFINED. A Local reset is required as part of a system
264    /// reset request.
265    ///
266    /// A Local reset clears this bit to `0`.
267    ///
268    /// See Reset management on page B1-208 for more information
269    pub sysresetreq, set_sysresetreq: 2;
270    /// Clears all active state information for fixed and configurable exceptions:
271    ///
272    /// `0`: do not clear state information.\
273    /// `1`: clear state information.
274    ///
275    /// The effect of writing a `1` to this bit if the processor is not halted in Debug
276    /// state is UNPREDICTABLE.
277    pub vectclractive, set_vectclractive: 1;
278    /// Writing `1` to this bit causes a local system reset, see Reset management on page B1-559 for
279    /// more information. This bit self-clears.
280    ///
281    /// The effect of writing a `1` to this bit if the processor is not halted in Debug state is UNPREDICTABLE.
282    ///
283    /// When the processor is halted in Debug state, if a write to the register writes a `1` to both
284    /// VECTRESET and SYSRESETREQ, the behavior is UNPREDICTABLE.
285    ///
286    /// This bit is write only.
287    pub vectreset, set_vectreset: 0;
288}
289
290impl From<u32> for Aircr {
291    fn from(value: u32) -> Self {
292        Self(value)
293    }
294}
295
296impl From<Aircr> for u32 {
297    fn from(value: Aircr) -> Self {
298        value.0
299    }
300}
301
302impl Aircr {
303    /// Must be called before writing the register.
304    pub fn vectkey(&mut self) {
305        self.set_vectkey(0x05FA);
306    }
307
308    /// Verifies that the vector key is correct (see [`Aircr::vectkey`])
309    pub fn vectkeystat(&self) -> bool {
310        self.get_vectkeystat() == 0xFA05
311    }
312}
313
314impl MemoryMappedRegister<u32> for Aircr {
315    const ADDRESS_OFFSET: u64 = 0xE000_ED0C;
316    const NAME: &'static str = "AIRCR";
317}
318
319bitfield! {
320    /// Debug Exception and Monitor Control Register, DEMCR (see armv7-M Architecture Reference Manual C1.6.5)
321    #[derive(Copy, Clone)]
322    pub struct Demcr(u32);
323    impl Debug;
324    /// Global enable for DWT and ITM features
325    pub trcena, set_trcena: 24;
326    /// DebugMonitor semaphore bit
327    pub mon_req, set_mon_req: 19;
328    /// Step the processor?
329    pub mon_step, set_mon_step: 18;
330    /// Sets or clears the pending state of the DebugMonitor exception
331    pub mon_pend, set_mon_pend: 17;
332    /// Enable the DebugMonitor exception
333    pub mon_en, set_mon_en: 16;
334    /// Enable halting debug trap on a HardFault exception
335    pub vc_harderr, set_vc_harderr: 10;
336    /// Enable halting debug trap on a fault occurring during exception entry
337    /// or exception return
338    pub vc_interr, set_vc_interr: 9;
339    /// Enable halting debug trap on a BusFault exception
340    pub vc_buserr, set_vc_buserr: 8;
341    /// Enable halting debug trap on a UsageFault exception caused by a state
342    /// information error, for example an Undefined Instruction exception
343    pub vc_staterr, set_vc_staterr: 7;
344    /// Enable halting debug trap on a UsageFault exception caused by a
345    /// checking error, for example an alignment check error
346    pub vc_chkerr, set_vc_chkerr: 6;
347    /// Enable halting debug trap on a UsageFault caused by an access to a
348    /// Coprocessor
349    pub vc_nocperr, set_vc_nocperr: 5;
350    /// Enable halting debug trap on a MemManage exception.
351    pub vc_mmerr, set_vc_mmerr: 4;
352    /// Enable Reset Vector Catch
353    pub vc_corereset, set_vc_corereset: 0;
354}
355
356impl From<u32> for Demcr {
357    fn from(value: u32) -> Self {
358        Self(value)
359    }
360}
361
362impl From<Demcr> for u32 {
363    fn from(value: Demcr) -> Self {
364        value.0
365    }
366}
367
368impl MemoryMappedRegister<u32> for Demcr {
369    const ADDRESS_OFFSET: u64 = 0xe000_edfc;
370    const NAME: &'static str = "DEMCR";
371}
372
373bitfield! {
374    /// MPU Control Register, MPU_CTRL (see armv7-M Architecture Reference Manual B3.5.3)
375    #[derive(Copy, Clone)]
376    pub struct MpuCtrl(u32);
377    impl Debug;
378    /// When the ENABLE bit is set to `1`, controls whether privileged software
379    /// access to the default memory map is enabled:
380    ///
381    /// `0`: Disabled. Any privileged access to an address not covered by an
382    /// enabled MPU region generates a fault.\
383    /// `1`: Enabled. Privileged accesses to addresses not covered by an
384    /// enabled MPU region use the default memory map.
385    pub privdefena, set_privdefena: 2;
386    /// Controls whether handlers executing with priority less than 0 access
387    /// memory with the MPU enabled or disabled (HardFault, NMI, and
388    /// FAULTMASK escalated handlers):
389    ///
390    /// `0`: MPU disabled for these handlers.\
391    /// `1`: MPU enabled for these handlers.
392    pub hfnmiena, set_hfnmiena: 1;
393    /// Enables the MPU:
394    ///
395    /// `0`: MPU disabled.\
396    /// `1`: MPU enabled.
397    pub enable, set_enable: 0;
398}
399
400impl From<u32> for MpuCtrl {
401    fn from(value: u32) -> Self {
402        Self(value)
403    }
404}
405
406impl From<MpuCtrl> for u32 {
407    fn from(value: MpuCtrl) -> Self {
408        value.0
409    }
410}
411
412impl MemoryMappedRegister<u32> for MpuCtrl {
413    const ADDRESS_OFFSET: u64 = 0xE000_ED94;
414    const NAME: &'static str = "MPU_CTRL";
415}
416
417bitfield! {
418    /// Flash Patch Control Register, FP_CTRL (see armv7-M Architecture Reference Manual C1.11.3)
419    #[derive(Copy,Clone)]
420    pub struct FpCtrl(u32);
421    impl Debug;
422    /// Flash Patch breakpoint architecture revision:
423    ///
424    /// `0b0000` Flash Patch breakpoint version 1.\
425    /// `0b0001` Flash Patch breakpoint version 2. Supports breakpoints on any location in the 4GB address range.
426    pub rev, _: 31, 28;
427    num_code_1, _: 14, 12;
428    /// The number of literal address comparators supported, starting from NUM_CODE upwards.
429    /// UNK/SBZP if Flash Patch is not implemented. Flash Patch is not implemented if `FP_REMAP[29]` is `0`.
430    ///
431    /// If this field is zero, the implementation does not support literal comparators.
432    pub num_lit, _: 11, 8;
433    num_code_0, _: 7, 4;
434    /// On any write to FP_CTRL, this bit must be `1`. A write to the register with this bit set to zero
435    /// is ignored. The Flash Patch Breakpoint unit ignores the write unless this bit is `1`.
436    pub _, set_key: 1;
437    /// Enable bit for the FPB:
438    ///
439    /// `0`: Flash Patch breakpoint disabled.\
440    /// `1`: Flash Patch breakpoint enabled.
441    ///
442    /// A power-on reset clears this bit to `0`.
443    pub enable, set_enable: 0;
444}
445
446impl FpCtrl {
447    /// The number of instruction address comparators.
448    /// If NUM_CODE is zero, the implementation does not support any instruction address comparators.
449    pub fn num_code(&self) -> u32 {
450        (self.num_code_1() << 4) | self.num_code_0()
451    }
452}
453
454impl MemoryMappedRegister<u32> for FpCtrl {
455    const ADDRESS_OFFSET: u64 = 0xE000_2000;
456    const NAME: &'static str = "FP_CTRL";
457}
458
459impl From<u32> for FpCtrl {
460    fn from(value: u32) -> Self {
461        FpCtrl(value)
462    }
463}
464
465impl From<FpCtrl> for u32 {
466    fn from(value: FpCtrl) -> Self {
467        value.0
468    }
469}
470
471bitfield! {
472    /// Flash Patch Comparator register, FP_COMPn (see armv7-M Architecture Reference Manual C1.11.5)
473    #[derive(Copy,Clone)]
474    pub struct FpRev1CompX(u32);
475    impl Debug;
476    /// For an instruction address comparator:
477    ///
478    /// Defines the behavior when the COMP address is matched:
479    ///
480    /// `00` Remap to remap address, see Flash Patch Remap register,
481    /// FP_REMAP on page C1-758.
482    ///
483    /// When the comparators are enabled in the FP_CTRL register, if the
484    /// implementation does not support remapping, the effect of an
485    /// instruction address match with an enabled comparator with
486    /// REPLACE programmed to 0b00 is UNPREDICTABLE.
487    ///
488    /// `01`: Breakpoint on instruction at `'000':COMP:'00'`.\
489    /// `10`: Breakpoint on instruction at `'000':COMP:'10'`.\
490    /// `11`: Breakpoint on both instructions at `'000':COMP:'00'` and `'000':COMP:'10'`.
491    ///
492    /// The reset value of this field is UNKNOWN.
493    ///
494    /// For a literal address comparator:
495    ///
496    /// Field is UNK/SBZP
497    pub replace, set_replace: 31, 30;
498    /// Bits `[28:2]` of the address to compare with addresses from the Code memory region,
499    /// see The system address map on page B3-592. Bits `[31:29]` of the address for comparison are zero.
500    ///
501    /// For a literal address or instruction address remap, bits `[1:0]` of the comparison are also zero.
502    ///
503    /// For an instruction address breakpoint, bits `[1:0]` of the comparison are encoded by the REPLACE field.
504    ///
505    /// If a match occurs:
506    ///
507    /// - For an instruction address comparator, the REPLACE field defines the required action.
508    /// - For a literal address comparator, the FPB remaps the access, see Flash Patch Remap register, FP_REMAP on page C1-758.
509    ///
510    /// The reset value of this field is UNKNOWN.
511    pub comp, set_comp: 28, 2;
512    /// Enable bit for this comparator:
513    ///
514    /// `0`: Comparator disabled.\
515    /// `1`: Comparator enabled.
516    ///
517    /// A power-on reset clears this bit to `0`.
518    pub enable, set_enable: 0;
519}
520
521impl MemoryMappedRegister<u32> for FpRev1CompX {
522    const ADDRESS_OFFSET: u64 = 0xE000_2008;
523    const NAME: &'static str = "FP_CTRL";
524}
525
526impl From<u32> for FpRev1CompX {
527    fn from(value: u32) -> Self {
528        FpRev1CompX(value)
529    }
530}
531
532impl From<FpRev1CompX> for u32 {
533    fn from(value: FpRev1CompX) -> Self {
534        value.0
535    }
536}
537
538impl FpRev1CompX {
539    /// Get the correct comparator value stored at the given address
540    /// This will adjust the `FpRev1CompX.comp() result based on the `FpRev1CompX.replace()` specification
541    /// NOTE: Does not support a `replace value of '11'
542    fn get_breakpoint_comparator(register_value: u32) -> Result<u32, Error> {
543        let fp1_val = FpRev1CompX::from(register_value);
544        if fp1_val.replace() == 0b01 {
545            Ok(fp1_val.comp() << 2)
546        } else if fp1_val.replace() == 0b10 {
547            Ok((fp1_val.comp() << 2) | 0x2)
548        } else {
549            Err(Error::Arm(ArmError::Other(format!(
550                "Unsupported breakpoint comparator value {:#08x} for HW breakpoint. Breakpoint must be on half-word boundaries",
551                fp1_val.0
552            ))))
553        }
554    }
555    /// Get the correct register configuration which enables
556    /// a hardware breakpoint at the given address.
557    /// NOTE: Does not support a `replace` value of '11'
558    pub(crate) fn breakpoint_configuration(address: u32) -> Result<Self, ArmError> {
559        let mut reg = FpRev1CompX::from(0);
560
561        // The highest 3 bits of the address have to be zero, otherwise the breakpoint cannot
562        // be set at the address.
563        if address >= 0x2000_0000 {
564            return Err(ArmError::UnsupportedBreakpointAddress(address));
565        }
566
567        let comp_val = (address & 0x1f_ff_ff_fc) >> 2;
568
569        // the replace value decides if the upper or lower half
570        // word is matched for the break point
571        let replace_val = if (address & 0x3) == 0 {
572            0b01 // lower half word
573        } else {
574            0b10 // upper half word
575        };
576
577        reg.set_replace(replace_val);
578        reg.set_comp(comp_val);
579        reg.set_enable(true);
580
581        Ok(reg)
582    }
583}
584
585bitfield! {
586    /// The FP_COMPn register bit assignments for FPB Version 2 where the Flash Patch is not implemented (see [`FpRev1CompX`]).
587    #[derive(Copy,Clone)]
588    pub struct FpRev2CompX(u32);
589    impl Debug;
590    /// BPADDR, `bits[31:1]` Breakpoint address. Specifies `bits[31:1]` of the breakpoint instruction address.
591    ///
592    /// If `BE == 0`, this field is Reserved, UNK/SBZP.
593    ///
594    /// The reset value of this field is UNKNOWN.
595    pub bpaddr, set_bpaddr: 31, 1;
596    /// Enable bit for breakpoint:
597    ///
598    /// `0`: Breakpoint disabled.\
599    /// `1`: Breakpoint enabled.
600    ///
601    /// The reset value of this bit is UNKNOWN.
602    pub enable, set_enable: 0;
603}
604
605impl MemoryMappedRegister<u32> for FpRev2CompX {
606    const ADDRESS_OFFSET: u64 = 0xE000_2008;
607    const NAME: &'static str = "FP_CTRL";
608}
609
610impl From<u32> for FpRev2CompX {
611    fn from(value: u32) -> Self {
612        FpRev2CompX(value)
613    }
614}
615
616impl From<FpRev2CompX> for u32 {
617    fn from(value: FpRev2CompX) -> Self {
618        value.0
619    }
620}
621
622impl FpRev2CompX {
623    /// Get the correct register configuration which enables
624    /// a hardware breakpoint at the given address.
625    pub(crate) fn breakpoint_configuration(address: u32) -> Self {
626        let mut reg = FpRev2CompX::from(0);
627
628        reg.set_bpaddr(address >> 1);
629        reg.set_enable(true);
630
631        reg
632    }
633}
634
635/// The state of a core that can be used to persist core state across calls to multiple different cores.
636pub struct Armv7m<'probe> {
637    memory: Box<dyn ArmMemoryInterface + 'probe>,
638
639    state: &'probe mut CortexMState,
640
641    sequence: Arc<dyn ArmDebugSequence>,
642}
643
644impl<'probe> Armv7m<'probe> {
645    pub(crate) fn new(
646        mut memory: Box<dyn ArmMemoryInterface + 'probe>,
647        state: &'probe mut CortexMState,
648        sequence: Arc<dyn ArmDebugSequence>,
649    ) -> Result<Self, Error> {
650        if !state.initialized() {
651            // determine current state
652            let dhcsr = Dhcsr(memory.read_word_32(Dhcsr::get_mmio_address())?);
653
654            let core_state = if dhcsr.s_sleep() {
655                CoreStatus::Sleeping
656            } else if dhcsr.s_halt() {
657                let dfsr = Dfsr(memory.read_word_32(Dfsr::get_mmio_address())?);
658
659                let reason = dfsr.halt_reason();
660
661                tracing::debug!("Core was halted when connecting, reason: {:?}", reason);
662
663                CoreStatus::Halted(reason)
664            } else {
665                CoreStatus::Running
666            };
667
668            // Clear DFSR register. The bits in the register are sticky,
669            // so we clear them here to ensure that that none are set.
670            let dfsr_clear = Dfsr::clear_all();
671
672            memory.write_word_32(Dfsr::get_mmio_address(), dfsr_clear.into())?;
673
674            state.current_state = core_state;
675            state.fp_present = Mvfr0(memory.read_word_32(Mvfr0::get_mmio_address())?).fp_present();
676
677            state.initialize();
678        }
679
680        Ok(Self {
681            memory,
682            state,
683            sequence,
684        })
685    }
686
687    fn set_core_status(&mut self, new_status: CoreStatus) {
688        super::update_core_status(&mut self.memory, &mut self.state.current_state, new_status);
689    }
690
691    fn wait_for_status(
692        &mut self,
693        timeout: Duration,
694        predicate: impl Fn(CoreStatus) -> bool,
695    ) -> Result<(), Error> {
696        let start = Instant::now();
697
698        while !predicate(self.status()?) {
699            if start.elapsed() >= timeout {
700                return Err(Error::Arm(ArmError::Timeout));
701            }
702            // Wait a bit before polling again.
703            std::thread::sleep(Duration::from_millis(1));
704        }
705
706        Ok(())
707    }
708}
709
710impl CoreInterface for Armv7m<'_> {
711    fn wait_for_core_halted(&mut self, timeout: Duration) -> Result<(), Error> {
712        // Wait until halted state is active again.
713        self.wait_for_status(timeout, |s| s.is_halted())
714    }
715
716    fn core_halted(&mut self) -> Result<bool, Error> {
717        Ok(self.status()?.is_halted())
718    }
719
720    fn status(&mut self) -> Result<CoreStatus, Error> {
721        let dhcsr = Dhcsr(self.memory.read_word_32(Dhcsr::get_mmio_address())?);
722
723        if dhcsr.s_lockup() {
724            tracing::debug!(
725                "The core is in locked up status as a result of an unrecoverable exception"
726            );
727
728            self.state.clear_pending_step();
729            self.set_core_status(CoreStatus::LockedUp);
730
731            return Ok(CoreStatus::LockedUp);
732        }
733
734        if dhcsr.s_sleep() {
735            // Check if we assumed the core to be halted
736            if self.state.current_state.is_halted() {
737                tracing::warn!("Expected core to be halted, but core is running");
738            }
739
740            self.set_core_status(CoreStatus::Sleeping);
741
742            return Ok(CoreStatus::Sleeping);
743        }
744
745        if dhcsr.s_halt() {
746            let dfsr = Dfsr(self.memory.read_word_32(Dfsr::get_mmio_address())?);
747
748            let mut reason = dfsr.halt_reason();
749            reason = self.state.resolve_halt_reason(reason);
750
751            // Clear bits from Dfsr register
752            self.memory
753                .write_word_32(Dfsr::get_mmio_address(), Dfsr::clear_all().into())?;
754
755            // If the core was halted before, we cannot read the halt reason from the chip,
756            // because we clear it directly after reading.
757            if self.state.current_state.is_halted() {
758                // There shouldn't be any bits set, otherwise it means
759                // that the reason for the halt has changed. No bits set
760                // means that we have an unknown HaltReason.
761                if reason == HaltReason::Unknown {
762                    tracing::debug!("Cached halt reason: {:?}", self.state.current_state);
763                    return Ok(self.state.current_state);
764                }
765
766                tracing::debug!(
767                    "Reason for halt has changed, old reason was {:?}, new reason is {:?}",
768                    &self.state.current_state,
769                    &reason
770                );
771            }
772
773            // Set the status so any semihosting operations will know we're halted
774            self.set_core_status(CoreStatus::Halted(reason));
775
776            if let HaltReason::Breakpoint(_) = reason {
777                self.state.semihosting_command = super::cortex_m::check_for_semihosting(
778                    self.state.semihosting_command.take(),
779                    self,
780                )?;
781                if let Some(command) = self.state.semihosting_command {
782                    reason = HaltReason::Breakpoint(BreakpointCause::Semihosting(command));
783                }
784
785                // Set it again if it's changed
786                self.set_core_status(CoreStatus::Halted(reason));
787            }
788
789            return Ok(CoreStatus::Halted(reason));
790        }
791
792        // Core is neither halted nor sleeping, so we assume it is running.
793        if self.state.current_state.is_halted() {
794            tracing::warn!("Core is running, but we expected it to be halted");
795        }
796
797        self.set_core_status(CoreStatus::Running);
798
799        Ok(CoreStatus::Running)
800    }
801
802    fn halt(&mut self, timeout: Duration) -> Result<CoreInformation, Error> {
803        // TODO: Generic halt support
804        self.state.clear_pending_step();
805
806        let mut value = Dhcsr(0);
807        value.set_c_halt(true);
808        value.set_c_debugen(true);
809        value.enable_write();
810
811        self.memory
812            .write_word_32(Dhcsr::get_mmio_address(), value.into())?;
813
814        self.wait_for_core_halted(timeout)?;
815
816        // try to read the program counter
817        let pc_value = self.read_core_reg(self.program_counter().into())?;
818
819        // get pc
820        Ok(CoreInformation {
821            pc: pc_value.try_into()?,
822        })
823    }
824
825    fn run(&mut self) -> Result<(), Error> {
826        // Before we run, we always perform a single instruction step, to account for possible breakpoints that might get us stuck on the current instruction.
827        self.step()?;
828        self.state.clear_pending_step();
829
830        let mut dhcsr = Dhcsr(self.memory.read_word_32(Dhcsr::get_mmio_address())?);
831
832        // First disable the DHCSR->C_MASKINTS.
833        if dhcsr.c_maskints() {
834            dhcsr.set_c_maskints(false);
835            dhcsr.enable_write();
836            self.memory
837                .write_word_32(Dhcsr::get_mmio_address(), dhcsr.into())?;
838            self.memory.flush()?;
839        }
840
841        // Exit halt state ..
842        dhcsr.set_c_step(false);
843        dhcsr.set_c_halt(false);
844        dhcsr.enable_write();
845        self.memory
846            .write_word_32(Dhcsr::get_mmio_address(), dhcsr.into())?;
847        self.memory.flush()?;
848
849        // We assume that the core is running now
850        self.set_core_status(CoreStatus::Running);
851
852        Ok(())
853    }
854
855    fn reset(&mut self) -> Result<(), Error> {
856        self.state.semihosting_command = None;
857        self.state.clear_pending_step();
858
859        self.sequence
860            .reset_system(&mut *self.memory, crate::CoreType::Armv7m, None)?;
861        // Invalidate cached state: chip reset clears FP_CTRL and core status
862        self.set_core_status(CoreStatus::Unknown);
863        self.state.hw_breakpoints_enabled = false;
864        Ok(())
865    }
866
867    fn reset_and_halt(&mut self, _timeout: Duration) -> Result<CoreInformation, Error> {
868        // Set the vc_corereset bit in the DEMCR register.
869        // This will halt the core after reset.
870        self.reset_catch_set()?;
871        self.state.clear_pending_step();
872
873        self.sequence
874            .reset_system(&mut *self.memory, crate::CoreType::Armv7m, None)?;
875
876        // Invalidate cached state: chip reset clears FP_CTRL and core status
877        self.set_core_status(CoreStatus::Unknown);
878        self.state.hw_breakpoints_enabled = false;
879
880        // Some processors may not enter the halt state immediately after clearing the reset state.
881        // Particularly: on PSOC 6, vector catch takes effect after the core's boot ROM finishes
882        // executing, when jumping to the reset vector of the user application.
883        match self.wait_for_core_halted(Duration::from_millis(100)) {
884            Ok(()) => (),
885            Err(Error::Arm(ArmError::Timeout)) if self.status()? == CoreStatus::Sleeping => {
886                // On PSOC 6, if no application is loaded in flash, or if this core is waiting for
887                // another core to boot it, the boot ROM sleeps and vector catch is not triggered.
888                tracing::warn!(
889                    "reset_and_halt timed out and core is sleeping; assuming core is quiescent"
890                );
891                self.halt(Duration::from_millis(100))?;
892            }
893            Err(e) => return Err(e),
894        }
895
896        const XPSR_THUMB: u32 = 1 << 24;
897
898        let xpsr_value: u32 = self.read_core_reg(XPSR.id())?.try_into()?;
899        if xpsr_value & XPSR_THUMB == 0 {
900            self.write_core_reg(XPSR.id(), (xpsr_value | XPSR_THUMB).into())?;
901        }
902
903        self.reset_catch_clear()?;
904
905        // try to read the program counter
906        let pc_value = self.read_core_reg(self.program_counter().into())?;
907
908        // get pc
909        Ok(CoreInformation {
910            pc: pc_value.try_into()?,
911        })
912    }
913
914    fn step(&mut self) -> Result<CoreInformation, Error> {
915        // First check if we stopped on a breakpoint, because this requires special handling before we can continue.
916        let breakpoint_at_pc = if matches!(
917            self.state.current_state,
918            CoreStatus::Halted(HaltReason::Breakpoint(_))
919        ) {
920            let pc_before_step = self.read_core_reg(self.program_counter().into())?;
921            self.enable_breakpoints(false)?;
922            Some(pc_before_step)
923        } else {
924            None
925        };
926
927        let mut dhcsr = Dhcsr(self.memory.read_word_32(Dhcsr::get_mmio_address())?);
928
929        // Follow the rules of the ... ARMv7-M Architecture reference, C1.6 Debug System Registers - DHCSR, with respect to setting maskints
930        if !dhcsr.c_debugen() {
931            tracing::warn!("Attempting to STEP while DHCSR->C_DEBUGEN is false");
932        }
933        if !dhcsr.c_maskints() {
934            dhcsr.set_c_maskints(true); // This must be reset to false when we run() again.
935            dhcsr.enable_write();
936            self.memory
937                .write_word_32(Dhcsr::get_mmio_address(), dhcsr.into())?;
938            self.memory.flush()?;
939        }
940
941        // Leave halted state.
942        // Step one instruction.
943        self.state.begin_step();
944        dhcsr.set_c_step(true);
945        dhcsr.set_c_halt(false);
946        dhcsr.enable_write();
947        self.memory
948            .write_word_32(Dhcsr::get_mmio_address(), dhcsr.into())?;
949        self.memory.flush()?;
950
951        // The single-step might put the core in lockup state. Lockup isn't considered "halted"
952        // so we can't use `wait_for_core_halted` here.
953        // So we wait for halted OR lockup, and if we entered lockup we halt.
954        if let Err(err) = self.wait_for_status(Duration::from_millis(100), |s| {
955            matches!(s, CoreStatus::Halted(_) | CoreStatus::LockedUp)
956        }) {
957            self.state.clear_pending_step();
958            return Err(err);
959        }
960        if self.status()? == CoreStatus::LockedUp {
961            self.halt(Duration::from_millis(100))?;
962        }
963
964        // Try to read the new program counter.
965        let mut pc_after_step = self.read_core_reg(self.program_counter().into())?;
966
967        // Re-enable breakpoints before we continue.
968        if let Some(pc_before_step) = breakpoint_at_pc {
969            // If we were stopped on a software breakpoint, then we need to manually advance the PC, or else we will be stuck here forever.
970            if pc_before_step == pc_after_step
971                && !self
972                    .hw_breakpoints()?
973                    .contains(&pc_before_step.try_into().ok())
974            {
975                tracing::debug!(
976                    "Encountered a breakpoint instruction @ {}. We need to manually advance the program counter to the next instruction.",
977                    pc_after_step
978                );
979                // Advance the program counter by the architecture specific byte size of the BKPT instruction.
980                pc_after_step.increment_address(2)?;
981                self.write_core_reg(self.program_counter().into(), pc_after_step)?;
982            }
983            self.enable_breakpoints(true)?;
984        }
985
986        self.state.semihosting_command = None;
987
988        Ok(CoreInformation {
989            pc: pc_after_step.try_into()?,
990        })
991    }
992
993    fn read_core_reg(&mut self, address: RegisterId) -> Result<RegisterValue, Error> {
994        if self.state.current_state.is_halted() {
995            let val = super::cortex_m::read_core_reg(&mut *self.memory, address)?;
996            Ok(val.into())
997        } else {
998            Err(Error::Arm(ArmError::CoreNotHalted))
999        }
1000    }
1001
1002    fn write_core_reg(&mut self, address: RegisterId, value: RegisterValue) -> Result<(), Error> {
1003        if self.state.current_state.is_halted() {
1004            super::cortex_m::write_core_reg(&mut *self.memory, address, value.try_into()?)?;
1005            Ok(())
1006        } else {
1007            Err(Error::Arm(ArmError::CoreNotHalted))
1008        }
1009    }
1010
1011    fn available_breakpoint_units(&mut self) -> Result<u32, Error> {
1012        let raw_val = self.memory.read_word_32(FpCtrl::get_mmio_address())?;
1013
1014        let reg = FpCtrl::from(raw_val);
1015
1016        if reg.rev() == 0 || reg.rev() == 1 {
1017            Ok(reg.num_code())
1018        } else {
1019            tracing::warn!(
1020                "This chip uses FPBU revision {}, which is not yet supported. HW breakpoints are not available.",
1021                reg.rev()
1022            );
1023            Err(Error::Arm(ArmError::Other(format!(
1024                "This chip uses FPBU revision {}, which is not yet supported. HW breakpoints are not available.",
1025                reg.rev()
1026            ))))
1027        }
1028    }
1029
1030    /// See docs on the [`CoreInterface::hw_breakpoints`] trait.
1031    fn hw_breakpoints(&mut self) -> Result<Vec<Option<u64>>, Error> {
1032        let mut breakpoints = vec![];
1033        let num_hw_breakpoints = self.available_breakpoint_units()? as usize;
1034        { 0..num_hw_breakpoints }.try_for_each(|bp_unit_index| {
1035            let raw_val = self.memory.read_word_32(FpCtrl::get_mmio_address())?;
1036            let ctrl_reg = FpCtrl::from(raw_val);
1037            // FpRev1 and FpRev2 needs different decoding of the register value, but the location where we read from is the same ...
1038            let reg_addr = FpRev1CompX::get_mmio_address() + (bp_unit_index * size_of::<u32>()) as u64;
1039            // The raw breakpoint address as read from memory.
1040            let register_value = self.memory.read_word_32(reg_addr)?;
1041            // The breakpoint address after it has been adjusted for FpRev 1 or 2.
1042            let breakpoint:u32;
1043            if register_value & 0b1 == 0b1 {
1044                // We only care about `enabled` breakpoints.
1045                if ctrl_reg.rev() == 0 {
1046                    breakpoint = FpRev1CompX::get_breakpoint_comparator(register_value)?;
1047                } else if ctrl_reg.rev() == 1 {
1048                    breakpoint = FpRev2CompX::from(register_value).bpaddr() << 1;
1049                } else {
1050                    tracing::warn!("This chip uses FPBU revision {}, which is not yet supported. HW breakpoints are not available.", ctrl_reg.rev());
1051                    return Err(Error::Other(format!("This chip uses FPBU revision {}, which is not yet supported. HW breakpoints are not available.", ctrl_reg.rev())));
1052                }
1053                breakpoints.push(Some(breakpoint as u64));
1054            } else {
1055                breakpoints.push(None);
1056            }
1057            Ok(())
1058        })?;
1059        Ok(breakpoints)
1060    }
1061
1062    fn enable_breakpoints(&mut self, state: bool) -> Result<(), Error> {
1063        let mut val = FpCtrl::from(0);
1064        val.set_key(true);
1065        val.set_enable(state);
1066
1067        self.memory
1068            .write_word_32(FpCtrl::get_mmio_address(), val.into())?;
1069        self.memory.flush()?;
1070
1071        self.state.hw_breakpoints_enabled = state;
1072
1073        Ok(())
1074    }
1075
1076    fn set_hw_breakpoint(&mut self, bp_unit_index: usize, addr: u64) -> Result<(), Error> {
1077        let addr = valid_32bit_address(addr)?;
1078
1079        // First make sure they are asking for a breakpoint on a half-word boundary.
1080        if (addr & 0x1) > 0 {
1081            return Err(Error::Other(format!(
1082                "The requested breakpoint address 0x{addr:08x} is not on a half-word boundary"
1083            )));
1084        }
1085
1086        let raw_val = self.memory.read_word_32(FpCtrl::get_mmio_address())?;
1087        let ctrl_reg = FpCtrl::from(raw_val);
1088
1089        let val: u32;
1090        if ctrl_reg.rev() == 0 {
1091            val = FpRev1CompX::breakpoint_configuration(addr)?.into();
1092        } else if ctrl_reg.rev() == 1 {
1093            val = FpRev2CompX::breakpoint_configuration(addr).into();
1094        } else {
1095            tracing::warn!(
1096                "This chip uses FPBU revision {}, which is not yet supported. HW breakpoints are not available.",
1097                ctrl_reg.rev()
1098            );
1099            return Err(Error::Other(format!(
1100                "This chip uses FPBU revision {}, which is not yet supported. HW breakpoints are not available.",
1101                ctrl_reg.rev()
1102            )));
1103        }
1104
1105        // This is fine as FpRev1CompX and Rev2CompX are just two different
1106        // interpretations of the same memory region as Rev2 can handle bigger
1107        // address spaces than Rev1.
1108        let reg_addr = FpRev1CompX::get_mmio_address() + (bp_unit_index * size_of::<u32>()) as u64;
1109
1110        self.memory.write_word_32(reg_addr, val)?;
1111
1112        Ok(())
1113    }
1114
1115    fn clear_hw_breakpoint(&mut self, bp_unit_index: usize) -> Result<(), Error> {
1116        let mut val = FpRev1CompX::from(0);
1117        val.set_enable(false);
1118
1119        let reg_addr = FpRev1CompX::get_mmio_address() + (bp_unit_index * size_of::<u32>()) as u64;
1120
1121        self.memory.write_word_32(reg_addr, val.into())?;
1122
1123        Ok(())
1124    }
1125
1126    fn registers(&self) -> &'static CoreRegisters {
1127        if self.state.fp_present {
1128            &CORTEX_M_WITH_FP_CORE_REGISTERS
1129        } else {
1130            &CORTEX_M_CORE_REGISTERS
1131        }
1132    }
1133
1134    fn program_counter(&self) -> &'static CoreRegister {
1135        &PC
1136    }
1137
1138    fn frame_pointer(&self) -> &'static CoreRegister {
1139        &FP
1140    }
1141
1142    fn stack_pointer(&self) -> &'static CoreRegister {
1143        &SP
1144    }
1145
1146    fn return_address(&self) -> &'static CoreRegister {
1147        &RA
1148    }
1149
1150    fn hw_breakpoints_enabled(&self) -> bool {
1151        self.state.hw_breakpoints_enabled
1152    }
1153
1154    fn architecture(&self) -> Architecture {
1155        Architecture::Arm
1156    }
1157
1158    fn core_type(&self) -> CoreType {
1159        CoreType::Armv7m
1160    }
1161
1162    fn instruction_set(&mut self) -> Result<InstructionSet, Error> {
1163        Ok(InstructionSet::Thumb2)
1164    }
1165
1166    fn fpu_support(&mut self) -> Result<bool, Error> {
1167        Ok(self.state.fp_present)
1168    }
1169
1170    fn floating_point_register_count(&mut self) -> Result<usize, Error> {
1171        Ok(32)
1172    }
1173
1174    #[tracing::instrument(skip(self))]
1175    fn reset_catch_set(&mut self) -> Result<(), Error> {
1176        self.sequence
1177            .reset_catch_set(&mut *self.memory, CoreType::Armv7m, None)?;
1178
1179        Ok(())
1180    }
1181
1182    #[tracing::instrument(skip(self))]
1183    fn reset_catch_clear(&mut self) -> Result<(), Error> {
1184        self.sequence
1185            .reset_catch_clear(&mut *self.memory, CoreType::Armv7m, None)?;
1186
1187        Ok(())
1188    }
1189
1190    #[tracing::instrument(skip(self))]
1191    fn debug_core_stop(&mut self) -> Result<(), Error> {
1192        self.sequence
1193            .debug_core_stop(&mut *self.memory, CoreType::Armv7m)?;
1194        Ok(())
1195    }
1196
1197    #[tracing::instrument(skip(self))]
1198    fn enable_vector_catch(&mut self, condition: VectorCatchCondition) -> Result<(), Error> {
1199        let mut dhcsr = Dhcsr(self.memory.read_word_32(Dhcsr::get_mmio_address())?);
1200        dhcsr.set_c_debugen(true);
1201        self.memory
1202            .write_word_32(Dhcsr::get_mmio_address(), dhcsr.into())?;
1203
1204        let mut demcr = Demcr(self.memory.read_word_32(Demcr::get_mmio_address())?);
1205        match condition {
1206            VectorCatchCondition::HardFault => demcr.set_vc_harderr(true),
1207            VectorCatchCondition::CoreReset => demcr.set_vc_corereset(true),
1208            VectorCatchCondition::SecureFault => {
1209                return Err(Error::Arm(ArmError::ArchitectureRequired(&["ARMv8"])));
1210            }
1211            VectorCatchCondition::All => {
1212                demcr.set_vc_harderr(true);
1213                demcr.set_vc_corereset(true);
1214            }
1215            VectorCatchCondition::Svc | VectorCatchCondition::Hlt => {
1216                return Err(Error::NotImplemented("vector catch condition Svc/Hlt"));
1217            }
1218        };
1219
1220        self.memory
1221            .write_word_32(Demcr::get_mmio_address(), demcr.into())?;
1222        Ok(())
1223    }
1224
1225    fn disable_vector_catch(&mut self, condition: VectorCatchCondition) -> Result<(), Error> {
1226        let mut demcr = Demcr(self.memory.read_word_32(Demcr::get_mmio_address())?);
1227        match condition {
1228            VectorCatchCondition::HardFault => demcr.set_vc_harderr(false),
1229            VectorCatchCondition::CoreReset => demcr.set_vc_corereset(false),
1230            VectorCatchCondition::SecureFault => {
1231                return Err(Error::Arm(ArmError::ArchitectureRequired(&["ARMv8"])));
1232            }
1233            VectorCatchCondition::All => {
1234                demcr.set_vc_harderr(false);
1235                demcr.set_vc_corereset(false);
1236            }
1237            VectorCatchCondition::Svc | VectorCatchCondition::Hlt => {
1238                return Err(Error::NotImplemented("vector catch condition Svc/Hlt"));
1239            }
1240        };
1241
1242        self.memory
1243            .write_word_32(Demcr::get_mmio_address(), demcr.into())?;
1244        Ok(())
1245    }
1246}
1247
1248impl CoreMemoryInterface for Armv7m<'_> {
1249    type ErrorType = ArmError;
1250
1251    fn memory(&self) -> &dyn MemoryInterface<Self::ErrorType> {
1252        self.memory.as_ref()
1253    }
1254    fn memory_mut(&mut self) -> &mut dyn MemoryInterface<Self::ErrorType> {
1255        self.memory.as_mut()
1256    }
1257}
1258
1259#[test]
1260fn breakpoint_register_value() {
1261    // Check that the register configuration for the FPBU is
1262    // calculated correctly.
1263    //
1264    // See ARMv7 Architecture Reference Manual, Section C1.11.5
1265    let address: u32 = 0x0800_09A4;
1266
1267    let reg = FpRev1CompX::breakpoint_configuration(address).unwrap();
1268    let reg_val: u32 = reg.into();
1269
1270    assert_eq!(0x4800_09A5, reg_val);
1271}
1272
1273#[test]
1274fn unsupported_breakpoint_address() {
1275    // Revision 1 of the FPBU only supports breakpoints for address < 0x2000_0000.
1276    let address: u32 = 0x2000_0000;
1277
1278    FpRev1CompX::breakpoint_configuration(address).unwrap_err();
1279}