Skip to content

Commit 3ae551f

Browse files
committed
GPIO configuration display is adapted to the actual chip being used.
- Updated documentation to reflect GPIO configuration output specific to chip configuration. - Non-existant pins are not printed. - Added tests for the new behavior.
1 parent 2d95254 commit 3ae551f

File tree

5 files changed

+1497
-284
lines changed

5 files changed

+1497
-284
lines changed

cmd/gpio/src/config_cache.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
5+
//! Generic cache for GPIO configuration register blocks.
6+
//!
7+
//! This module provides a chip-independent way to cache raw GPIO register
8+
//! block data from device memory. The cache stores raw bytes indexed by
9+
//! GPIO group identifiers, avoiding repeated memory reads for the same
10+
//! GPIO port.
11+
12+
use anyhow::Result;
13+
use humility_cli::ExecutionContext;
14+
use std::collections::BTreeMap;
15+
16+
/// A generic cache for GPIO configuration register blocks.
17+
///
18+
/// This cache stores raw byte data for GPIO register blocks, indexed by
19+
/// a group identifier (typically a character like 'A', 'B', etc.). The
20+
/// cache is chip-independent and works with any architecture that organizes
21+
/// GPIO pins into groups/ports with contiguous register blocks.
22+
pub struct ConfigCache {
23+
cache: BTreeMap<char, Vec<u8>>,
24+
}
25+
26+
impl ConfigCache {
27+
/// Creates a new empty configuration cache.
28+
pub fn new() -> ConfigCache {
29+
ConfigCache { cache: BTreeMap::new() }
30+
}
31+
32+
/// Gets or fetches the raw register block data for a GPIO group.
33+
///
34+
/// If the data for the specified group is already cached, returns it
35+
/// directly. Otherwise, calls the provided `fetch_fn` to read the data
36+
/// from device memory, caches it, and returns it.
37+
///
38+
/// # Arguments
39+
///
40+
/// * `context` - Execution context with access to the device core
41+
/// * `group` - GPIO group identifier (e.g., 'A', 'B', 'C')
42+
/// * `fetch_fn` - Function that fetches the register block from device memory
43+
///
44+
/// # Returns
45+
///
46+
/// A reference to the cached raw register block data
47+
pub fn get_or_fetch<F>(
48+
&mut self,
49+
context: &mut ExecutionContext,
50+
group: char,
51+
fetch_fn: F,
52+
) -> Result<&[u8]>
53+
where
54+
F: FnOnce(&mut ExecutionContext, char) -> Result<Vec<u8>>,
55+
{
56+
use std::collections::btree_map::Entry;
57+
if let Entry::Vacant(e) = self.cache.entry(group) {
58+
let data = fetch_fn(context, group)?;
59+
e.insert(data);
60+
}
61+
Ok(self.cache.get(&group).unwrap().as_slice())
62+
}
63+
}

cmd/gpio/src/lib.rs

Lines changed: 135 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
//! ```console
6060
//! $ humility gpio --input
6161
//! humility: attached via ST-Link V3
62+
//! Chip: STM32H753ZITx
6263
//! Pin 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
6364
//! -----------------------------------------------------------------------
6465
//! Port A 0 0 1 0 0 0 0 0 0 0 0 0 0 1 1 1
@@ -68,24 +69,52 @@
6869
//! Port E 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
6970
//! Port F 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0
7071
//! Port G 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
71-
//! Port H 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
72-
//! Port I 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
73-
//! Port J 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
74-
//! Port K 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
72+
//! Port H 0 0 . . . . . . . . . . . . . .
7573
//! ```
76-
//! To get input values with STM32H753 GPIO configuration settings,
77-
//! use the `--with-config` or `-w` flag with --input:
74+
//!
75+
//! Note that the chip name is displayed, and GPIO groups are filtered based on
76+
//! the specific chip variant. For partially populated GPIO groups (like Port H
77+
//! above with only 2 pins), non-existent pins are shown as `.`
78+
//!
79+
//! If a chip name is not in the internal database of chip names, then the
80+
//! old behavior of this program is used; no GPIO config info will be shown
81+
//! and it will be assumed that the part has 176 GPIO pins in groups A to K.
82+
//!
83+
//! To print input values with STM32H7 series GPIO configuration settings,
84+
//! use the `--with-config` or `-w` flag with `--input`:
7885
//!
7986
//! ```console
80-
//! $ humility gpio --input -with-config --pins B:0,B:14,E:1
87+
//! $ humility gpio --input --with-config --pins B:0,B:14,E:1
8188
//! humility: attached to 0483:3754:002F00174741500820383733 via ST-Link V3
89+
//! Chip: STM32H753ZITx
8290
//! B:0 = 0 Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero
8391
//! B:14 = 1 Input:OutPushPull:HighSpeed:NoPull:AF5:Unlocked:InOne:OutZero
8492
//! E:1 = 0 Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InZero:OutZero
8593
//! ```
8694
//!
87-
//! If no pins are specified, then the pin names, values and config settings
88-
//! are printed one per line for all GPIO pins.
95+
//! If you query pins that don't exist on the chip variant, they will show as `None`:
96+
//!
97+
//! ```console
98+
//! $ humility gpio --input --pins J:4,K:9
99+
//! humility: attached via ST-Link V3
100+
//! Chip: STM32H753ZITx
101+
//! J:4 = None
102+
//! K:9 = None
103+
//! ```
104+
//!
105+
//! With `--with-config`, non-existent pins or groups show `None` for configuration:
106+
//!
107+
//! ```console
108+
//! $ humility gpio --input --with-config --pins H:5,J:4
109+
//! humility: attached via ST-Link V3
110+
//! Chip: STM32H753ZITx
111+
//! H:5 = 0 None
112+
//! J:4 = 0 None
113+
//! ```
114+
//!
115+
//! If no pins are specified with `--with-config`, then the pin names, values
116+
//! and config settings are printed one per line for all available GPIO pins on
117+
//! the chip variant.
89118
//!
90119
//! ### Configure
91120
//!
@@ -105,7 +134,11 @@
105134
//! ```
106135
//!
107136
137+
mod config_cache;
108138
mod stm32h7;
139+
mod stm32h7_parts;
140+
#[cfg(test)]
141+
mod stm32h7_testdata;
109142

110143
use humility_cli::{ExecutionContext, Subcommand};
111144
use humility_cmd::{Archive, Attach, Command, CommandKind, Validate};
@@ -310,33 +343,98 @@ fn gpio(context: &mut ExecutionContext) -> Result<()> {
310343

311344
let results = hiffy_context.run(core, ops.as_slice(), None)?;
312345

346+
// Get chip info for filtering and display
347+
let chip_name = hubris.chip();
348+
let (max_group, chip_part) = if let Some(chip) = &chip_name {
349+
if chip.to_ascii_lowercase().starts_with("stm32h7") {
350+
use crate::stm32h7_parts::Stm32H7Series;
351+
let part = Stm32H7Series::find_part(chip, true);
352+
353+
// Warn about unknown chips
354+
if !part.is_known() {
355+
eprintln!(
356+
"Warning: Unknown STM32H7 chip '{}'. Using default GPIO layout (groups A-K).",
357+
chip
358+
);
359+
eprintln!(
360+
" Configuration display (--with-config) is not available for unknown chips."
361+
);
362+
}
363+
364+
(Some(part.max_group()), Some(part))
365+
} else {
366+
(None, None)
367+
}
368+
} else {
369+
(None, None)
370+
};
371+
313372
if subargs.input {
314373
let mut header = false;
315374

316375
if subargs.with_config {
376+
// Print chip name
377+
if let Some(chip) = &chip_name {
378+
println!("Chip: {}", chip);
379+
}
317380
show_gpio_with_config(context, &gpio_input, &args, &results)?;
318381
} else {
382+
// Print chip name for grid display too
383+
if let Some(chip) = &chip_name {
384+
println!("Chip: {}", chip);
385+
}
386+
319387
for (ndx, arg) in args.iter().enumerate() {
388+
// Check if this group exists on the chip
389+
let group = arg.2.chars().next();
390+
let group_exists =
391+
if let (Some(g), Some(max_g)) = (group, max_group) {
392+
g >= 'A' && g <= max_g
393+
} else {
394+
true // If we can't determine, assume it exists
395+
};
396+
320397
match arg.1 {
321398
Some(pin) => {
322-
println!(
323-
"{}:{:<2} = {}",
324-
arg.2,
325-
pin,
326-
match results[ndx] {
327-
Err(code) => {
328-
gpio_input.strerror(code)
329-
}
330-
Ok(ref val) => {
331-
let arr: &[u8; 2] = val[0..2].try_into()?;
332-
let v = u16::from_le_bytes(*arr);
333-
format!("{}", (v >> pin) & 1)
399+
// Check if pin exists (group exists and pin within range)
400+
let pin_exists =
401+
if let (Some(g), Some(part)) = (group, chip_part) {
402+
g >= 'A'
403+
&& g <= part.max_group()
404+
&& pin < part.pins_in_group(g)
405+
} else {
406+
group_exists // Fall back to group-level check
407+
};
408+
409+
if !pin_exists {
410+
// Pin doesn't exist on this chip
411+
println!("{}:{:<2} = None", arg.2, pin);
412+
} else {
413+
println!(
414+
"{}:{:<2} = {}",
415+
arg.2,
416+
pin,
417+
match results[ndx] {
418+
Err(code) => {
419+
gpio_input.strerror(code)
420+
}
421+
Ok(ref val) => {
422+
let arr: &[u8; 2] =
423+
val[0..2].try_into()?;
424+
let v = u16::from_le_bytes(*arr);
425+
format!("{}", (v >> pin) & 1)
426+
}
334427
}
335-
}
336-
);
428+
);
429+
}
337430
}
338431

339432
None => {
433+
// Skip ports that don't exist on this chip variant
434+
if !group_exists {
435+
continue;
436+
}
437+
340438
if !header {
341439
print!("{:7}", "Pin");
342440

@@ -365,8 +463,21 @@ fn gpio(context: &mut ExecutionContext) -> Result<()> {
365463
let arr: &[u8; 2] = val[0..2].try_into()?;
366464
let v = u16::from_le_bytes(*arr);
367465

466+
// Determine how many pins exist in this group
467+
let max_pin = if let (Some(g), Some(part)) =
468+
(group, chip_part)
469+
{
470+
part.pins_in_group(g)
471+
} else {
472+
16 // Default to all 16 if we can't determine
473+
};
474+
368475
for i in 0..16 {
369-
print!("{:4}", (v >> i) & 1)
476+
if i < max_pin {
477+
print!("{:4}", (v >> i) & 1)
478+
} else {
479+
print!(" .")
480+
}
370481
}
371482
println!();
372483
}

0 commit comments

Comments
 (0)