Skip to main content

tag_peek/
tag-peek.rs

1//! `tag-peek`: read words of live tag memory or peripheral space.
2//!
3//! Port of `host/commandline/tag-peek.cc`. A diagnostic tool: reads one or
4//! more words in a single attach, between monitor RPCs, without halting the
5//! core (unlike `STM32_Programmer_CLI -r32`, which needs connect-under-reset).
6//! Uses [`Tag::read_memory`], so addresses must be word aligned. Not for
7//! shipping.
8//!
9//! "Live" means the running monitor firmware answers the read: the core is
10//! not halted. It still follows the attach, which resets the tag, so what is
11//! read is the state after that reset and the monitor's start-up, not the
12//! state of a run in progress.
13//!
14//! Usage: `tag-peek [-d] [-b BUS:DEVICE] [--addr ADDR...] [ADDR...]`. Each
15//! address prints as `0xAAAAAAAA: 0xVVVVVVVV`, the word read
16//! little-endian.
17//!
18//! # Exit status
19//!
20//! 0 if every address was read; 1 if no address was given, no base matches,
21//! the attach fails, or any address does not parse or fails to read (the
22//! others are still read). Option errors exit 2 (clap).
23
24use std::process::ExitCode;
25
26use clap::Parser;
27use tag_tools::{BaseArgs, init_logging, select_base};
28use tagcore::{MemAccess, Tag};
29
30/// read one or more words of live tag memory/peripheral space in a single
31/// attach (one attach/detach disturbs the tag less than several separate
32/// invocations)
33#[derive(Parser)]
34#[command(name = "tag-peek")]
35struct Cli {
36    /// The shared `--debug` and `--base` options.
37    #[command(flatten)]
38    base: BaseArgs,
39
40    /// address(es) to read, hex (e.g. 0xE000EDFC) or decimal
41    #[arg(long = "addr", value_name = "ADDR", num_args = 1..)]
42    addr_opt: Vec<String>,
43
44    /// address(es) to read, as positional arguments
45    #[arg(value_name = "ADDR")]
46    addr: Vec<String>,
47}
48
49/// Parses an address as `strtoull(text, &end, 0)` would, but only when the
50/// whole argument is a number that fits in 32 bits: `0x` hex, leading-`0`
51/// octal or decimal, no sign. `None` for anything else, rather than the
52/// address 0 that `strtoul` returns for text it cannot parse.
53fn parse_address(text: &str) -> Option<u32> {
54    let (radix, digits) = match text.strip_prefix("0x").or_else(|| text.strip_prefix("0X")) {
55        Some(hex) => (16, hex),
56        None if text.len() > 1 && text.starts_with('0') => (8, &text[1..]),
57        None => (10, text),
58    };
59    if digits.is_empty() || digits.starts_with(['+', '-']) {
60        return None;
61    }
62    u32::from_str_radix(digits, radix).ok()
63}
64
65/// Attaches once and reads every address given, in order (`--addr` values
66/// first, then positional ones).
67fn main() -> ExitCode {
68    let cli = Cli::parse();
69    init_logging(&cli.base);
70
71    let tag = Tag::new();
72    let dev = select_base::<Cli>(&tag, &cli.base);
73    let addrs: Vec<&String> = cli.addr_opt.iter().chain(&cli.addr).collect();
74    if addrs.is_empty() {
75        eprintln!("usage: tag-peek <addr> [addr...]");
76        return ExitCode::FAILURE;
77    }
78
79    if !tag.attach(&dev) {
80        eprintln!("Attach failed");
81        return ExitCode::FAILURE;
82    }
83
84    let mut rc = ExitCode::SUCCESS;
85    for addr_str in addrs {
86        let Some(addr) = parse_address(addr_str) else {
87            eprintln!("not an address: {addr_str}");
88            rc = ExitCode::FAILURE;
89            continue;
90        };
91        let mut value = [0u8; 4];
92        let result = tag.read_memory(addr, &mut value);
93        if result != MemAccess::Ok {
94            eprintln!("read failed for {addr_str}: {}", result.name());
95            rc = ExitCode::FAILURE;
96            continue;
97        }
98        println!("0x{addr:08X}: 0x{:08X}", u32::from_le_bytes(value));
99    }
100    rc
101}
102
103#[cfg(test)]
104/// Unit tests for address parsing.
105mod tests {
106    use super::parse_address;
107
108    #[test]
109    /// Checks hex, decimal and octal forms, and that partial, signed, empty and
110    /// over-wide input is rejected.
111    fn addresses_must_be_whole_32_bit_numbers() {
112        assert_eq!(parse_address("0xE000EDFC"), Some(0xE000_EDFC));
113        assert_eq!(parse_address("536870912"), Some(0x2000_0000));
114        assert_eq!(parse_address("010"), Some(8));
115        assert_eq!(parse_address("0"), Some(0));
116        assert_eq!(parse_address("12abc"), None);
117        assert_eq!(parse_address("zzz"), None);
118        assert_eq!(parse_address("-4"), None);
119        assert_eq!(parse_address("0x"), None);
120        assert_eq!(parse_address("0x100000000"), None);
121        assert_eq!(parse_address(""), None);
122    }
123}