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}