Skip to main content

tagprobe/
fieldsafe.rs

1//! A probe-rs probe driver for our tag bases, using only the ST-LINK commands
2//! every base in the field supports.
3//!
4//! The bases present as ST-LINK/V2s, but there is no record of which base
5//! firmware is in the field, and some of it is old. probe-rs's own ST-LINK
6//! driver relies on two things those bases get wrong: `READ_DAP_REG` (bases
7//! flashed before 773db79a return stale data) and the 2-byte reply to a
8//! 1-byte `READMEM_8BIT` (no base sends it). This driver never depends on
9//! either. It speaks only the subset tagcore's [`LinkAdapt`] already uses on
10//! every base:
11//!
12//! - USB open, mode query, DFU exit, SWD enter/exit, nRST drive, voltage;
13//! - `READMEM_32BIT` / `WRITEMEM_32BIT` with the last-R/W-status check,
14//!   in chunks of at most [`CHUNK`] bytes;
15//! - `READDEBUGREG` for the chip ID, which tells L4 from U3.
16//!
17//! Everything else probe-rs asks for is answered here:
18//!
19//! - DAP register reads are answered from a table of the part's real values
20//!   (DPIDR, the AP0 IDR, BASE and CSW, "no AP" past AP0, and 0 for any
21//!   other register, CFG included); DAP writes
22//!   are accepted and ignored, because memory never goes through DRW/TAR --
23//!   it goes through the base's own memory commands, which set up the AP
24//!   themselves.
25//! - 8- and 16-bit memory accesses are done with 32-bit ones: reads take the
26//!   bytes they need; writes read, patch and write back whole words. That is
27//!   safe for SRAM and flash-algorithm buffers, which is what probe-rs uses
28//!   them for, but would not be for a peripheral register with side effects.
29//!
30//! Reset and SWD entry happen in tagcore's order: nRST is asserted before
31//! SWD is entered, because a tag asleep with its debug domain off answers SWD
32//! only from reset.
33//!
34//! The layering is [`FieldSafeProbe`] (a probe-rs [`DebugProbe`]) ->
35//! [`FieldSafeArm`] (its [`ArmDebugInterface`]) -> [`FieldSafeMemory`] (AP0's
36//! [`ArmMemoryInterface`]). None of them is thread-safe; probe-rs drives a
37//! session from one thread at a time. The C++ host tools have no counterpart:
38//! they call STM32_Programmer_CLI for programming. See also
39//! `host/libraries/tagprobe-rs/design/architecture.md`.
40
41// The byte/word repacking below is as tested on the bench; keep it as is.
42#![allow(clippy::chunks_exact_to_as_chunks)]
43
44use std::collections::BTreeSet;
45use std::fmt;
46use std::sync::Arc;
47use std::time::Duration;
48
49use probe_rs::MemoryInterface;
50use probe_rs::architecture::arm::ap::CSW;
51use probe_rs::architecture::arm::dp::{DpAddress, DpRegisterAddress};
52use probe_rs::architecture::arm::memory::ArmMemoryInterface;
53use probe_rs::architecture::arm::sequences::ArmDebugSequence;
54use probe_rs::architecture::arm::{
55    ArmDebugInterface, ArmError, DapAccess, DapProbe, FullyQualifiedApAddress, SwdSequence,
56    SwoAccess, SwoConfig,
57};
58use probe_rs::probe::{DebugProbe, DebugProbeError, Probe, ProbeError, WireProtocol};
59use tagcore::{LinkAdapt, UsbDev};
60
61/// Largest single memory transfer, in bytes, as tagcore's `TagMonitor` uses.
62///
63/// Every base firmware handles transfers of this size; larger reads and
64/// writes are split into chunks of at most this many bytes.
65pub const CHUNK: usize = 1024;
66
67/// nRESET in the `swj_pins` bit map (the CMSIS-DAP `DAP_SWJ_Pins` layout
68/// probe-rs uses).
69const PIN_NRESET: u32 = 1 << 7;
70
71/// A link failure, reported to probe-rs as a probe-specific error.
72///
73/// Holds the name of the step that failed ("open", "memory read", ...);
74/// tagcore has already logged the underlying reason.
75#[derive(Debug)]
76pub struct LinkError(&'static str);
77
78impl fmt::Display for LinkError {
79    /// Formats as `tag base: <step> failed`.
80    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        write!(f, "tag base: {} failed", self.0)
82    }
83}
84
85impl std::error::Error for LinkError {}
86impl ProbeError for LinkError {}
87
88/// Wraps a failed step, named by `what`, as a probe-rs
89/// [`DebugProbeError::ProbeSpecific`].
90fn link_err(what: &'static str) -> DebugProbeError {
91    DebugProbeError::ProbeSpecific(LinkError(what).into())
92}
93
94/// What the driver answers for the target's DAP registers.
95///
96/// probe-rs reads these while it identifies the debug port and access port;
97/// the values are the ones the real part returns, so its discovery takes the
98/// same path it would through a working probe.
99#[derive(Debug, Clone, Copy)]
100struct DapTable {
101    /// DP `DPIDR` (DP register 0x0).
102    dpidr: u32,
103    /// AP0 `IDR` (AP register 0xFC); identifies the MEM-AP type.
104    ap0_idr: u32,
105    /// AP0 `BASE` (AP register 0xF8); the ROM table address, with its
106    /// format/present bits.
107    ap0_base: u32,
108    /// AP0 `CSW` (AP register 0x00) as the part resets it.
109    ap0_csw: u32,
110}
111
112/// STM32L4 (Cortex-M4): SW-DP v1 and an AHB-AP.
113const L4_DAP: DapTable = DapTable {
114    dpidr: 0x2BA0_1477,
115    ap0_idr: 0x2477_0011,
116    ap0_base: 0xE00F_F003,
117    ap0_csw: 0x2300_0052,
118};
119
120/// STM32U3 (Cortex-M33): SW-DP v2 and an AHB5-AP. AP values read from an
121/// STM32U375 through a base whose `READ_DAP_REG` works.
122const U3_DAP: DapTable = DapTable {
123    dpidr: 0x0BE1_2477,
124    ap0_idr: 0x1477_0015,
125    ap0_base: 0xE00F_E003,
126    ap0_csw: 0x0380_0052,
127};
128
129/// Address of `DBGMCU_IDCODE` on the STM32L4.
130const L4_DBGMCU_IDCODE: u32 = 0xE004_2000;
131/// Address of `DBGMCU_IDCODE` on the STM32U3.
132const U3_DBGMCU_IDCODE: u32 = 0xE004_4000;
133
134/// Reports whether an `IDCODE` read looks real: a device ID (low 12 bits)
135/// of all zeros or all ones is what a wrong address or a failed bus returns.
136fn plausible(idcode: u32) -> bool {
137    let dev = idcode & 0xfff;
138    dev != 0 && dev != 0xfff
139}
140
141/// The probe: a tagcore [`LinkAdapt`] opened on one base.
142///
143/// Created by [`FieldSafeProbe::open`]. probe-rs then calls
144/// [`DebugProbe::attach`] to enter SWD, after asserting reset for an attach
145/// under reset. Dropping it (or [`DebugProbe::detach`]) leaves SWD mode and
146/// closes the USB device.
147pub struct FieldSafeProbe {
148    /// The open base; every USB exchange goes through it.
149    link: LinkAdapt,
150    /// Whether SWD has been entered since the last detach (for `Debug` only).
151    attached: bool,
152}
153
154impl fmt::Debug for FieldSafeProbe {
155    /// Shows only `attached`; [`LinkAdapt`] has no `Debug` form.
156    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157        f.debug_struct("FieldSafeProbe")
158            .field("attached", &self.attached)
159            .finish()
160    }
161}
162
163impl FieldSafeProbe {
164    /// Opens the base `usbdev` selects, without touching nRST or SWD, and
165    /// wraps it as a probe-rs [`Probe`].
166    ///
167    /// The tag is not reset until probe-rs attaches. Leaves DFU mode if the
168    /// base is in it.
169    ///
170    /// # Errors
171    ///
172    /// A [`LinkError`] for "open" if no base matches `usbdev` or it cannot be
173    /// claimed (for example because another process holds it).
174    pub fn open(usbdev: &UsbDev) -> Result<Probe, DebugProbeError> {
175        let mut link = LinkAdapt::new();
176        if !link.open(usbdev) {
177            return Err(link_err("open"));
178        }
179        Ok(Probe::from_specific_probe(Box::new(Self {
180            link,
181            attached: false,
182        })))
183    }
184}
185
186impl DebugProbe for FieldSafeProbe {
187    /// Returns the name probe-rs prints for this probe.
188    fn get_name(&self) -> &str {
189        "Tag base (ST-LINK/V2 field subset)"
190    }
191
192    /// Reports a nominal 1000 kHz; the base sets its own SWD clock.
193    fn speed_khz(&self) -> u32 {
194        // The base runs SWD at its own fixed rate.
195        1000
196    }
197
198    /// Accepts any speed and reports it back; nothing is sent to the base.
199    fn set_speed(&mut self, speed_khz: u32) -> Result<u32, DebugProbeError> {
200        Ok(speed_khz)
201    }
202
203    /// Enters SWD mode. Does not touch nRST: for an attach under reset,
204    /// probe-rs has already asserted it through
205    /// [`target_reset_assert`](DebugProbe::target_reset_assert).
206    fn attach(&mut self) -> Result<(), DebugProbeError> {
207        if !self.link.enter_swd() {
208            return Err(link_err("SWD entry"));
209        }
210        self.attached = true;
211        Ok(())
212    }
213
214    /// Leaves SWD mode and closes the base; always succeeds.
215    fn detach(&mut self) -> Result<(), probe_rs::Error> {
216        self.link.detach();
217        self.attached = false;
218        Ok(())
219    }
220
221    /// Pulses nRST: asserts it, waits 10 ms and releases it. The tag reboots.
222    fn target_reset(&mut self) -> Result<(), DebugProbeError> {
223        self.target_reset_assert()?;
224        std::thread::sleep(Duration::from_millis(10));
225        self.target_reset_deassert()
226    }
227
228    /// Drives nRST low, holding the tag in reset and ending whatever it was
229    /// doing.
230    fn target_reset_assert(&mut self) -> Result<(), DebugProbeError> {
231        // LinkAdapt::assert_reset(high): false drives nRST low.
232        if self.link.assert_reset(false) {
233            Ok(())
234        } else {
235            Err(link_err("nRST assert"))
236        }
237    }
238
239    /// Releases nRST, letting the tag boot (or stop at the reset vector if
240    /// vector catch is armed).
241    fn target_reset_deassert(&mut self) -> Result<(), DebugProbeError> {
242        if self.link.assert_reset(true) {
243            Ok(())
244        } else {
245            Err(link_err("nRST release"))
246        }
247    }
248
249    /// Accepts SWD only; the bases have no JTAG.
250    fn select_protocol(&mut self, protocol: WireProtocol) -> Result<(), DebugProbeError> {
251        match protocol {
252            WireProtocol::Swd => Ok(()),
253            other => Err(DebugProbeError::UnsupportedProtocol(other)),
254        }
255    }
256
257    /// Always SWD.
258    fn active_protocol(&self) -> Option<WireProtocol> {
259        Some(WireProtocol::Swd)
260    }
261
262    /// Always true: the tags are Arm Cortex-M parts.
263    fn has_arm_interface(&self) -> bool {
264        true
265    }
266
267    /// Wraps the probe in a [`FieldSafeArm`]; never fails.
268    ///
269    /// The debug sequence is ignored here: the sequence's own steps run
270    /// through the returned interface, and the DAP power-up handshake it
271    /// performs is answered from the `DapTable`.
272    fn try_get_arm_debug_interface<'probe>(
273        self: Box<Self>,
274        _sequence: Arc<dyn ArmDebugSequence>,
275    ) -> Result<Box<dyn ArmDebugInterface + 'probe>, (Box<dyn DebugProbe>, ArmError)> {
276        Ok(Box::new(FieldSafeArm {
277            probe: self,
278            table: None,
279        }))
280    }
281
282    /// Returns the probe itself as a boxed [`DebugProbe`].
283    fn into_probe(self: Box<Self>) -> Box<dyn DebugProbe> {
284        self
285    }
286
287    /// Returns the target supply voltage the base measures, in volts; `None`
288    /// if the voltage command fails.
289    fn get_target_voltage(&mut self) -> Result<Option<f32>, DebugProbeError> {
290        Ok(self.link.voltage())
291    }
292}
293
294/// The ARM debug interface over a [`FieldSafeProbe`].
295///
296/// Serves DP/AP register traffic from a `DapTable` and memory through the
297/// base's 32-bit memory commands (via [`FieldSafeMemory`]). Only the default
298/// debug port and AP0 exist.
299#[derive(Debug)]
300pub struct FieldSafeArm {
301    /// The probe, owned while the interface is in use and handed back by
302    /// [`ArmDebugInterface::close`].
303    probe: Box<FieldSafeProbe>,
304    /// The DAP answers for the part, chosen on first use from its chip ID.
305    table: Option<DapTable>,
306}
307
308impl FieldSafeArm {
309    /// The DAP table for the connected part: L4 or U3 by DBGMCU IDCODE, read
310    /// with the debug-register command every base supports.
311    ///
312    /// Cached after the first success, so the part is identified once per
313    /// session.
314    ///
315    /// # Errors
316    ///
317    /// [`ArmError::Probe`] with "chip identification" if neither IDCODE
318    /// address reads plausibly (no tag, an unpowered tag, or another part).
319    fn table(&mut self) -> Result<DapTable, ArmError> {
320        if let Some(t) = self.table {
321            return Ok(t);
322        }
323        let link = &mut self.probe.link;
324        let table = if link.read_debug32(L4_DBGMCU_IDCODE).is_some_and(plausible) {
325            L4_DAP
326        } else if link.read_debug32(U3_DBGMCU_IDCODE).is_some_and(plausible) {
327            U3_DAP
328        } else {
329            return Err(ArmError::Probe(link_err("chip identification")));
330        };
331        tracing::debug!("tag base: DAP table {table:x?}");
332        self.table = Some(table);
333        Ok(table)
334    }
335
336    /// Reads `data.len()` words at `address` with `READMEM_32BIT`, in chunks
337    /// of at most [`CHUNK`] bytes.
338    ///
339    /// `address` must be word-aligned, as probe-rs guarantees for 32-bit
340    /// accesses. The core is not halted for the read.
341    ///
342    /// # Errors
343    ///
344    /// [`ArmError::Probe`] ("memory read") if any chunk fails, e.g. on a bus
345    /// fault; `data` is then left unchanged.
346    fn read_words(&mut self, address: u32, data: &mut [u32]) -> Result<(), ArmError> {
347        let mut bytes = vec![0u8; data.len() * 4];
348        for (i, chunk) in bytes.chunks_mut(CHUNK).enumerate() {
349            let at = address + (i * CHUNK) as u32;
350            if !self.probe.link.read_mem32(at, chunk) {
351                return Err(ArmError::Probe(link_err("memory read")));
352            }
353        }
354        for (word, b) in data.iter_mut().zip(bytes.chunks_exact(4)) {
355            *word = u32::from_le_bytes(b.try_into().unwrap());
356        }
357        Ok(())
358    }
359
360    /// Writes `data` at `address` with `WRITEMEM_32BIT`, in chunks of at most
361    /// [`CHUNK`] bytes.
362    ///
363    /// `address` must be word-aligned.
364    ///
365    /// # Errors
366    ///
367    /// [`ArmError::Probe`] ("memory write") if any chunk fails; earlier
368    /// chunks have then already been written.
369    fn write_words(&mut self, address: u32, data: &[u32]) -> Result<(), ArmError> {
370        let bytes: Vec<u8> = data.iter().flat_map(|w| w.to_le_bytes()).collect();
371        for (i, chunk) in bytes.chunks(CHUNK).enumerate() {
372            let at = address + (i * CHUNK) as u32;
373            if !self.probe.link.write_mem32(at, chunk) {
374                return Err(ArmError::Probe(link_err("memory write")));
375            }
376        }
377        Ok(())
378    }
379
380    /// Reads `data.len()` bytes at any alignment with whole-word reads.
381    ///
382    /// Reads the enclosing aligned words and copies out the bytes asked for,
383    /// so up to 3 bytes either side are also read from the target.
384    ///
385    /// # Errors
386    ///
387    /// As [`read_words`](Self::read_words).
388    fn read_bytes(&mut self, address: u32, data: &mut [u8]) -> Result<(), ArmError> {
389        if data.is_empty() {
390            return Ok(());
391        }
392        let start = address & !3;
393        let end = (address as u64 + data.len() as u64 + 3) & !3;
394        let mut words = vec![0u32; ((end - start as u64) / 4) as usize];
395        self.read_words(start, &mut words)?;
396        let bytes: Vec<u8> = words.iter().flat_map(|w| w.to_le_bytes()).collect();
397        let offset = (address - start) as usize;
398        data.copy_from_slice(&bytes[offset..offset + data.len()]);
399        Ok(())
400    }
401
402    /// Writes bytes at any alignment by read-modify-write of whole words.
403    ///
404    /// The bytes either side of `data` within the edge words are written back
405    /// with the values just read. That is not atomic: anything the target
406    /// changes in those bytes between the read and the write is lost, so this
407    /// is only safe for memory the core is not using (probe-rs uses it for
408    /// halted-core SRAM and flash-algorithm buffers), never for peripheral
409    /// registers.
410    ///
411    /// # Errors
412    ///
413    /// As [`read_words`](Self::read_words) and
414    /// [`write_words`](Self::write_words).
415    fn write_bytes(&mut self, address: u32, data: &[u8]) -> Result<(), ArmError> {
416        if data.is_empty() {
417            return Ok(());
418        }
419        let start = address & !3;
420        let end = (address as u64 + data.len() as u64 + 3) & !3;
421        let mut words = vec![0u32; ((end - start as u64) / 4) as usize];
422        self.read_words(start, &mut words)?;
423        let mut bytes: Vec<u8> = words.iter().flat_map(|w| w.to_le_bytes()).collect();
424        let offset = (address - start) as usize;
425        bytes[offset..offset + data.len()].copy_from_slice(data);
426        let words: Vec<u32> = bytes
427            .chunks_exact(4)
428            .map(|b| u32::from_le_bytes(b.try_into().unwrap()))
429            .collect();
430        self.write_words(start, &words)
431    }
432}
433
434impl DapAccess for FieldSafeArm {
435    /// Answers DP register reads from the `DapTable` without touching the
436    /// wire.
437    ///
438    /// `DPIDR` gets the part's value; `CTRL/STAT` (bank 0) reports debug and
439    /// system power-up requested and acknowledged, which ends probe-rs's
440    /// power-up handshake; everything else reads 0. The bases' own
441    /// `READ_DAP_REG` cannot be used: old firmware returns stale data for it.
442    fn read_raw_dp_register(
443        &mut self,
444        _dp: DpAddress,
445        address: DpRegisterAddress,
446    ) -> Result<u32, ArmError> {
447        let t = self.table()?;
448        Ok(match (address.address, address.bank.unwrap_or(0)) {
449            (0x0, _) => t.dpidr,
450            // CTRL/STAT: debug and system power-up requested and acknowledged.
451            (0x4, 0) => 0xF000_0000,
452            _ => 0,
453        })
454    }
455
456    /// Accepts and ignores DP register writes; the base powers up and selects
457    /// the DAP itself on SWD entry.
458    fn write_raw_dp_register(
459        &mut self,
460        _dp: DpAddress,
461        _address: DpRegisterAddress,
462        _value: u32,
463    ) -> Result<(), ArmError> {
464        Ok(())
465    }
466
467    /// Answers AP register reads from the `DapTable` without touching the
468    /// wire.
469    ///
470    /// AP0 reports its `CSW`, `BASE` and `IDR`; other registers read 0. Any AP
471    /// other than AP0 reads 0 throughout, which probe-rs takes as "no AP".
472    fn read_raw_ap_register(
473        &mut self,
474        ap: &FullyQualifiedApAddress,
475        address: u64,
476    ) -> Result<u32, ArmError> {
477        if ap.ap_v1()? != 0 {
478            // No AP past AP0: an IDR of 0 ends probe-rs's AP scan.
479            return Ok(0);
480        }
481        let t = self.table()?;
482        Ok(match address & 0xFF {
483            0x00 => t.ap0_csw,
484            0xF8 => t.ap0_base,
485            0xFC => t.ap0_idr,
486            _ => 0,
487        })
488    }
489
490    /// Accepts and ignores AP register writes: memory never goes through
491    /// `TAR`/`DRW`, and the base's memory commands set `CSW` themselves.
492    fn write_raw_ap_register(
493        &mut self,
494        _ap: &FullyQualifiedApAddress,
495        _address: u64,
496        _value: u32,
497    ) -> Result<(), ArmError> {
498        Ok(())
499    }
500
501    /// Returns `None`: there is no raw DAP access to offer.
502    fn try_dap_probe(&self) -> Option<&dyn DapProbe> {
503        None
504    }
505
506    /// Returns `None`: there is no raw DAP access to offer.
507    fn try_dap_probe_mut(&mut self) -> Option<&mut dyn DapProbe> {
508        None
509    }
510}
511
512impl SwdSequence for FieldSafeArm {
513    /// Refuses raw SWJ bit sequences (line resets, JTAG-to-SWD switches); the
514    /// base performs those itself on SWD entry.
515    fn swj_sequence(&mut self, _bit_len: u8, _bits: u64) -> Result<(), DebugProbeError> {
516        Err(DebugProbeError::CommandNotSupportedByProbe {
517            command_name: "swj_sequence",
518        })
519    }
520
521    /// Drives nRST when that is the only pin selected; refuses any other pin.
522    ///
523    /// probe-rs's reset sequences use this to assert and release reset.
524    /// Returns `pin_out` as the pin state read back; `pin_wait` is ignored.
525    fn swj_pins(
526        &mut self,
527        pin_out: u32,
528        pin_select: u32,
529        _pin_wait: u32,
530    ) -> Result<u32, DebugProbeError> {
531        if pin_select != PIN_NRESET {
532            return Err(DebugProbeError::CommandNotSupportedByProbe {
533                command_name: "swj_pins",
534            });
535        }
536        if pin_out & PIN_NRESET == 0 {
537            self.probe.target_reset_assert()?;
538        } else {
539            self.probe.target_reset_deassert()?;
540        }
541        Ok(pin_out)
542    }
543}
544
545impl SwoAccess for FieldSafeArm {
546    /// Refuses: the bases do not capture SWO.
547    fn enable_swo(&mut self, _config: &SwoConfig) -> Result<(), ArmError> {
548        Err(ArmError::NotImplemented("SWO on a tag base"))
549    }
550
551    /// Does nothing; SWO is never enabled.
552    fn disable_swo(&mut self) -> Result<(), ArmError> {
553        Ok(())
554    }
555
556    /// Refuses: the bases do not capture SWO.
557    fn read_swo_timeout(&mut self, _timeout: Duration) -> Result<Vec<u8>, ArmError> {
558        Err(ArmError::NotImplemented("SWO on a tag base"))
559    }
560}
561
562impl ArmDebugInterface for FieldSafeArm {
563    /// Does nothing: the DAP state is the table's, and the base keeps the
564    /// link up itself.
565    fn reinitialize(&mut self) -> Result<(), ArmError> {
566        Ok(())
567    }
568
569    /// Returns AP0 on the default DP, the only access port these parts use.
570    fn access_ports(
571        &mut self,
572        _dp: DpAddress,
573    ) -> Result<BTreeSet<FullyQualifiedApAddress>, ArmError> {
574        Ok(BTreeSet::from([
575            FullyQualifiedApAddress::v1_with_default_dp(0),
576        ]))
577    }
578
579    /// Hands the [`FieldSafeProbe`] back as an unattached probe-rs [`Probe`].
580    ///
581    /// The base stays in SWD mode; the probe is only ever dropped afterwards,
582    /// which detaches it.
583    fn close(self: Box<Self>) -> Probe {
584        // from_attached_probe() is crate-private; the spike never reuses a
585        // closed probe, so an unattached Probe is enough.
586        Probe::from_specific_probe(self.probe)
587    }
588
589    /// Accepts only the default DP; multidrop SWD is not supported.
590    fn select_debug_port(&mut self, dp: DpAddress) -> Result<(), ArmError> {
591        if dp == DpAddress::Default {
592            Ok(())
593        } else {
594            Err(ArmError::NotImplemented("multidrop on a tag base"))
595        }
596    }
597
598    /// Always the default DP.
599    fn current_debug_port(&self) -> Option<DpAddress> {
600        Some(DpAddress::Default)
601    }
602
603    /// Returns a [`FieldSafeMemory`] for AP0; any other AP is refused.
604    fn memory_interface(
605        &mut self,
606        access_port: &FullyQualifiedApAddress,
607    ) -> Result<Box<dyn ArmMemoryInterface + '_>, ArmError> {
608        if access_port.ap_v1()? != 0 {
609            return Err(ArmError::NotImplemented("APs other than AP0 on a tag base"));
610        }
611        Ok(Box::new(FieldSafeMemory {
612            arm: self,
613            ap: access_port.clone(),
614        }))
615    }
616}
617
618/// AP0 memory through the base's 32-bit memory commands.
619///
620/// Every access width is carried by 32-bit transfers: 64-bit as word pairs,
621/// 8- and 16-bit through [`FieldSafeArm`]'s byte helpers (read-modify-write
622/// for writes). Addresses above 4 GB are refused with
623/// [`ArmError::OutOfBounds`].
624#[derive(Debug)]
625pub struct FieldSafeMemory<'a> {
626    /// The interface whose link carries the transfers.
627    arm: &'a mut FieldSafeArm,
628    /// The access port this interface was opened for (always AP0).
629    ap: FullyQualifiedApAddress,
630}
631
632/// Narrows a probe-rs 64-bit address to the 32-bit address the base takes.
633///
634/// # Errors
635///
636/// [`ArmError::OutOfBounds`] if `address` does not fit in 32 bits.
637fn addr32(address: u64) -> Result<u32, ArmError> {
638    u32::try_from(address).map_err(|_| ArmError::OutOfBounds)
639}
640
641impl SwdSequence for FieldSafeMemory<'_> {
642    /// Forwards to [`FieldSafeArm`], which refuses it.
643    fn swj_sequence(&mut self, bit_len: u8, bits: u64) -> Result<(), DebugProbeError> {
644        self.arm.swj_sequence(bit_len, bits)
645    }
646
647    /// Forwards to [`FieldSafeArm`]: drives nRST only.
648    fn swj_pins(
649        &mut self,
650        pin_out: u32,
651        pin_select: u32,
652        pin_wait: u32,
653    ) -> Result<u32, DebugProbeError> {
654        self.arm.swj_pins(pin_out, pin_select, pin_wait)
655    }
656}
657
658impl MemoryInterface<ArmError> for FieldSafeMemory<'_> {
659    /// Returns false: 64-bit accesses are split into word pairs.
660    fn supports_native_64bit_access(&mut self) -> bool {
661        false
662    }
663
664    /// Reads double words as pairs of little-endian words.
665    fn read_64(&mut self, address: u64, data: &mut [u64]) -> Result<(), ArmError> {
666        let mut words = vec![0u32; data.len() * 2];
667        self.arm.read_words(addr32(address)?, &mut words)?;
668        for (d, w) in data.iter_mut().zip(words.chunks_exact(2)) {
669            *d = u64::from(w[0]) | (u64::from(w[1]) << 32);
670        }
671        Ok(())
672    }
673
674    /// Reads words directly with `READMEM_32BIT`.
675    fn read_32(&mut self, address: u64, data: &mut [u32]) -> Result<(), ArmError> {
676        self.arm.read_words(addr32(address)?, data)
677    }
678
679    /// Reads half-words through whole-word reads.
680    fn read_16(&mut self, address: u64, data: &mut [u16]) -> Result<(), ArmError> {
681        let mut bytes = vec![0u8; data.len() * 2];
682        self.arm.read_bytes(addr32(address)?, &mut bytes)?;
683        for (d, b) in data.iter_mut().zip(bytes.chunks_exact(2)) {
684            *d = u16::from_le_bytes([b[0], b[1]]);
685        }
686        Ok(())
687    }
688
689    /// Reads bytes through whole-word reads, never `READMEM_8BIT`.
690    fn read_8(&mut self, address: u64, data: &mut [u8]) -> Result<(), ArmError> {
691        self.arm.read_bytes(addr32(address)?, data)
692    }
693
694    /// Writes double words as pairs of little-endian words.
695    fn write_64(&mut self, address: u64, data: &[u64]) -> Result<(), ArmError> {
696        let words: Vec<u32> = data
697            .iter()
698            .flat_map(|d| [*d as u32, (*d >> 32) as u32])
699            .collect();
700        self.arm.write_words(addr32(address)?, &words)
701    }
702
703    /// Writes words directly with `WRITEMEM_32BIT`.
704    fn write_32(&mut self, address: u64, data: &[u32]) -> Result<(), ArmError> {
705        self.arm.write_words(addr32(address)?, data)
706    }
707
708    /// Writes half-words by read-modify-write of whole words; see
709    /// [`FieldSafeArm`]'s byte-write caveats.
710    fn write_16(&mut self, address: u64, data: &[u16]) -> Result<(), ArmError> {
711        let bytes: Vec<u8> = data.iter().flat_map(|h| h.to_le_bytes()).collect();
712        self.arm.write_bytes(addr32(address)?, &bytes)
713    }
714
715    /// Writes bytes by read-modify-write of whole words; see
716    /// [`FieldSafeArm`]'s byte-write caveats.
717    fn write_8(&mut self, address: u64, data: &[u8]) -> Result<(), ArmError> {
718        self.arm.write_bytes(addr32(address)?, data)
719    }
720
721    /// Returns true: byte transfers are emulated, so probe-rs may use them.
722    fn supports_8bit_transfers(&self) -> Result<bool, ArmError> {
723        Ok(true)
724    }
725
726    /// Does nothing: every transfer completes before its call returns.
727    fn flush(&mut self) -> Result<(), ArmError> {
728        Ok(())
729    }
730}
731
732impl ArmMemoryInterface for FieldSafeMemory<'_> {
733    /// Returns the ROM table address from the `DapTable`'s AP0 `BASE`,
734    /// with its format/present bits masked off.
735    fn base_address(&mut self) -> Result<u64, ArmError> {
736        Ok(u64::from(self.arm.table()?.ap0_base & !0xFFF))
737    }
738
739    /// Returns the access port this interface was opened for (AP0).
740    fn fully_qualified_address(&self) -> FullyQualifiedApAddress {
741        self.ap.clone()
742    }
743
744    /// Returns the [`FieldSafeArm`] this memory interface belongs to.
745    fn get_arm_debug_interface(&mut self) -> Result<&mut dyn ArmDebugInterface, DebugProbeError> {
746        Ok(self.arm)
747    }
748
749    /// Returns AP0's `CSW` from the `DapTable`, as the part resets it.
750    fn generic_status(&mut self) -> Result<CSW, ArmError> {
751        Ok(CSW::from_raw(self.arm.table()?.ap0_csw))
752    }
753}