diff --git a/Cargo.lock b/Cargo.lock index 527dd9ca..693c6bbb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -492,7 +492,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.106", ] [[package]] @@ -859,7 +859,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.15", + "syn 2.0.106", ] [[package]] @@ -876,7 +876,7 @@ checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.106", ] [[package]] @@ -924,7 +924,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.15", + "syn 2.0.106", ] [[package]] @@ -1366,6 +1366,7 @@ dependencies = [ "indexmap 1.9.3", "indicatif", "jep106", + "lazy_static", "log", "multimap", "num-derive 0.4.2", @@ -1642,6 +1643,7 @@ dependencies = [ "humility-cmd", "humility-hiffy", "parse_int", + "zerocopy", ] [[package]] @@ -2980,7 +2982,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.106", ] [[package]] @@ -3361,9 +3363,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.26" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] @@ -3659,7 +3661,7 @@ checksum = "22fc4f90c27b57691bbaf11d8ecc7cfbfe98a4da6dbe60226115d322aa80c06e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.106", ] [[package]] @@ -3725,7 +3727,7 @@ checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.106", ] [[package]] @@ -3979,7 +3981,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.15", + "syn 2.0.106", ] [[package]] @@ -4007,9 +4009,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.15" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -4104,7 +4106,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.106", ] [[package]] @@ -4751,7 +4753,7 @@ checksum = "125139de3f6b9d625c39e2efdd73d41bdac468ccd556556440e322be0e1bbd91" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.106", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 74819425..7cbdc65c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -257,7 +257,8 @@ thiserror = "1.0" toml = "0.5" trycmd = "0.13.2" winapi = "0.3.9" -zerocopy = "0.6.1" +zerocopy = "0.6.6" +zerocopy-derive = "0.6.6" zip = "0.6.4" [profile.release] diff --git a/cmd/gpio/Cargo.toml b/cmd/gpio/Cargo.toml index 24a58509..f18336ad 100644 --- a/cmd/gpio/Cargo.toml +++ b/cmd/gpio/Cargo.toml @@ -9,6 +9,7 @@ hif.workspace = true clap.workspace = true anyhow.workspace = true parse_int.workspace = true +zerocopy.workspace = true humility-cli.workspace = true humility-cmd.workspace = true diff --git a/cmd/gpio/src/config_cache.rs b/cmd/gpio/src/config_cache.rs new file mode 100644 index 00000000..bad8314d --- /dev/null +++ b/cmd/gpio/src/config_cache.rs @@ -0,0 +1,63 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Generic cache for GPIO configuration register blocks. +//! +//! This module provides a chip-independent way to cache raw GPIO register +//! block data from device memory. The cache stores raw bytes indexed by +//! GPIO group identifiers, avoiding repeated memory reads for the same +//! GPIO port. + +use anyhow::Result; +use humility_cli::ExecutionContext; +use std::collections::BTreeMap; + +/// A generic cache for GPIO configuration register blocks. +/// +/// This cache stores raw byte data for GPIO register blocks, indexed by +/// a group identifier (typically a character like 'A', 'B', etc.). The +/// cache is chip-independent and works with any architecture that organizes +/// GPIO pins into groups/ports with contiguous register blocks. +pub struct ConfigCache { + cache: BTreeMap>, +} + +impl ConfigCache { + /// Creates a new empty configuration cache. + pub fn new() -> ConfigCache { + ConfigCache { cache: BTreeMap::new() } + } + + /// Gets or fetches the raw register block data for a GPIO group. + /// + /// If the data for the specified group is already cached, returns it + /// directly. Otherwise, calls the provided `fetch_fn` to read the data + /// from device memory, caches it, and returns it. + /// + /// # Arguments + /// + /// * `context` - Execution context with access to the device core + /// * `group` - GPIO group identifier (e.g., 'A', 'B', 'C') + /// * `fetch_fn` - Function that fetches the register block from device memory + /// + /// # Returns + /// + /// A reference to the cached raw register block data + pub fn get_or_fetch( + &mut self, + context: &mut ExecutionContext, + group: char, + fetch_fn: F, + ) -> Result<&[u8]> + where + F: FnOnce(&mut ExecutionContext, char) -> Result>, + { + use std::collections::btree_map::Entry; + if let Entry::Vacant(e) = self.cache.entry(group) { + let data = fetch_fn(context, group)?; + e.insert(data); + } + Ok(self.cache.get(&group).unwrap().as_slice()) + } +} diff --git a/cmd/gpio/src/lib.rs b/cmd/gpio/src/lib.rs index 29c580e0..2171c701 100644 --- a/cmd/gpio/src/lib.rs +++ b/cmd/gpio/src/lib.rs @@ -59,6 +59,7 @@ //! ```console //! $ humility gpio --input //! humility: attached via ST-Link V3 +//! Chip: STM32H753ZITx //! Pin 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 //! ----------------------------------------------------------------------- //! Port A 0 0 1 0 0 0 0 0 0 0 0 0 0 1 1 1 @@ -68,12 +69,53 @@ //! Port E 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 //! Port F 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 //! Port G 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -//! Port H 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -//! Port I 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -//! Port J 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -//! Port K 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +//! Port H 0 0 . . . . . . . . . . . . . . //! ``` //! +//! Note that the chip name is displayed, and GPIO groups are filtered based on +//! the specific chip variant. For partially populated GPIO groups (like Port H +//! above with only 2 pins), non-existent pins are shown as `.` +//! +//! If a chip name is not in the internal database of chip names, then the +//! old behavior of this program is used; no GPIO config info will be shown +//! and it will be assumed that the part has 176 GPIO pins in groups A to K. +//! +//! To print input values with STM32H7 series GPIO configuration settings, +//! use the `--with-config` or `-w` flag with `--input`: +//! +//! ```console +//! $ humility gpio --input --with-config --pins B:0,B:14,E:1 +//! humility: attached to 0483:3754:002F00174741500820383733 via ST-Link V3 +//! Chip: STM32H753ZITx +//! B:0 = 0 Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero +//! B:14 = 1 Input:OutPushPull:HighSpeed:NoPull:AF5:Unlocked:InOne:OutZero +//! E:1 = 0 Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InZero:OutZero +//! ``` +//! +//! If you query pins that don't exist on the chip variant, they will show as `None`: +//! +//! ```console +//! $ humility gpio --input --pins J:4,K:9 +//! humility: attached via ST-Link V3 +//! Chip: STM32H753ZITx +//! J:4 = None +//! K:9 = None +//! ``` +//! +//! With `--with-config`, non-existent pins or groups show `None` for configuration: +//! +//! ```console +//! $ humility gpio --input --with-config --pins H:5,J:4 +//! humility: attached via ST-Link V3 +//! Chip: STM32H753ZITx +//! H:5 = 0 None +//! J:4 = 0 None +//! ``` +//! +//! If no pins are specified with `--with-config`, then the pin names, values +//! and config settings are printed one per line for all available GPIO pins on +//! the chip variant. +//! //! ### Configure //! //! To configure a pin, the configuration should be specified as a @@ -92,14 +134,52 @@ //! ``` //! +mod config_cache; +mod stm32h7; +mod stm32h7_parts; +#[cfg(test)] +mod stm32h7_testdata; + use humility_cli::{ExecutionContext, Subcommand}; use humility_cmd::{Archive, Attach, Command, CommandKind, Validate}; use humility_hiffy::HiffyContext; -use std::str; -use anyhow::{Result, bail}; +use anyhow::{Result, anyhow, bail}; use clap::{CommandFactory, Parser}; use hif::*; +use humility_hiffy::IpcError; + +fn show_gpio_with_config( + context: &mut ExecutionContext, + gpio_input: &humility_hiffy::HiffyFunction, + args: &[(u16, Option, String)], + results: &[Result, IpcError>], +) -> Result<()> { + let hubris = context + .archive + .as_ref() + .ok_or_else(|| anyhow!("Cannot get GPIO config: archive not loaded"))?; + if let Some(chip) = hubris.chip() { + if chip.to_ascii_lowercase().starts_with("stm32h7") { + // Call the stm32h7-specific function + stm32h7::show_gpio_with_config( + context, + gpio_input, + chip.as_str(), + args, + results, + ) + } else { + Err(anyhow!( + "GPIO `--with-config` is not supported for chip '{chip}'" + )) + } + } else { + Err(anyhow!( + "GPIO `--with-config` is not supported when chip is not specified" + )) + } +} use std::convert::TryInto; @@ -120,6 +200,10 @@ struct GpioArgs { )] input: bool, + /// show configuration of pins along with their values + #[clap(short, long, requires = "input")] + with_config: bool, + /// toggle specified pins #[clap( long, short, requires = "pins", @@ -154,13 +238,13 @@ fn gpio(context: &mut ExecutionContext) -> Result<()> { let Subcommand::Other(subargs) = context.cli.cmd.as_ref().unwrap(); let hubris = context.archive.as_ref().unwrap(); let subargs = GpioArgs::try_parse_from(subargs)?; - let mut context = HiffyContext::new(hubris, core, subargs.timeout)?; + let mut hiffy_context = HiffyContext::new(hubris, core, subargs.timeout)?; - let gpio_toggle = context.get_function("GpioToggle", 2)?; - let gpio_set = context.get_function("GpioSet", 2)?; - let gpio_reset = context.get_function("GpioReset", 2)?; - let gpio_input = context.get_function("GpioInput", 1)?; - let gpio_configure = context.get_function("GpioConfigure", 7)?; + let gpio_toggle = hiffy_context.get_function("GpioToggle", 2)?; + let gpio_set = hiffy_context.get_function("GpioSet", 2)?; + let gpio_reset = hiffy_context.get_function("GpioReset", 2)?; + let gpio_input = hiffy_context.get_function("GpioInput", 1)?; + let gpio_configure = hiffy_context.get_function("GpioConfigure", 7)?; let mut configure_args = vec![]; let target = if subargs.toggle { @@ -257,64 +341,146 @@ fn gpio(context: &mut ExecutionContext) -> Result<()> { ops.push(Op::Done); - let results = context.run(core, ops.as_slice(), None)?; + let results = hiffy_context.run(core, ops.as_slice(), None)?; + + // Get chip info for filtering and display + let chip_name = hubris.chip(); + let (max_group, chip_part) = if let Some(chip) = &chip_name { + if chip.to_ascii_lowercase().starts_with("stm32h7") { + use crate::stm32h7_parts::Stm32H7Series; + let part = Stm32H7Series::find_part(chip, true); + + // Warn about unknown chips + if !part.is_known() { + eprintln!( + "Warning: Unknown STM32H7 chip '{}'. Using default GPIO layout (groups A-K).", + chip + ); + eprintln!( + " Configuration display (--with-config) is not available for unknown chips." + ); + } + + (Some(part.max_group()), Some(part)) + } else { + (None, None) + } + } else { + (None, None) + }; if subargs.input { let mut header = false; - for (ndx, arg) in args.iter().enumerate() { - match arg.1 { - Some(pin) => { - println!( - "{}:{:<2} = {}", - arg.2, - pin, - match results[ndx] { - Err(code) => { - gpio_input.strerror(code) - } - Ok(ref val) => { - let arr: &[u8; 2] = val[0..2].try_into()?; - let v = u16::from_le_bytes(*arr); - format!("{}", (v >> pin) & 1) - } - } - ); - } + if subargs.with_config { + // Print chip name + if let Some(chip) = &chip_name { + println!("Chip: {}", chip); + } + show_gpio_with_config(context, &gpio_input, &args, &results)?; + } else { + // Print chip name for grid display too + if let Some(chip) = &chip_name { + println!("Chip: {}", chip); + } - None => { - if !header { - print!("{:7}", "Pin"); + for (ndx, arg) in args.iter().enumerate() { + // Check if this group exists on the chip + let group = arg.2.chars().next(); + let group_exists = + if let (Some(g), Some(max_g)) = (group, max_group) { + g >= 'A' && g <= max_g + } else { + true // If we can't determine, assume it exists + }; + + match arg.1 { + Some(pin) => { + // Check if pin exists (group exists and pin within range) + let pin_exists = + if let (Some(g), Some(part)) = (group, chip_part) { + g >= 'A' + && g <= part.max_group() + && pin < part.pins_in_group(g) + } else { + group_exists // Fall back to group-level check + }; + + if !pin_exists { + // Pin doesn't exist on this chip + println!("{}:{:<2} = None", arg.2, pin); + } else { + println!( + "{}:{:<2} = {}", + arg.2, + pin, + match results[ndx] { + Err(code) => { + gpio_input.strerror(code) + } + Ok(ref val) => { + let arr: &[u8; 2] = + val[0..2].try_into()?; + let v = u16::from_le_bytes(*arr); + format!("{}", (v >> pin) & 1) + } + } + ); + } + } - for i in 0..16 { - print!("{:4}", i); + None => { + // Skip ports that don't exist on this chip variant + if !group_exists { + continue; } - println!(); + if !header { + print!("{:7}", "Pin"); - print!("-------"); + for i in 0..16 { + print!("{:4}", i); + } - for _i in 0..16 { - print!("----"); - } + println!(); - println!(); - header = true; - } + print!("-------"); + + for _i in 0..16 { + print!("----"); + } - print!("Port {} ", arg.2); - match results[ndx] { - Err(code) => { - println!("{}", gpio_input.strerror(code)) + println!(); + header = true; } - Ok(ref val) => { - let arr: &[u8; 2] = val[0..2].try_into()?; - let v = u16::from_le_bytes(*arr); - for i in 0..16 { - print!("{:4}", (v >> i) & 1) + print!("Port {} ", arg.2); + match results[ndx] { + Err(code) => { + println!("{}", gpio_input.strerror(code)) + } + Ok(ref val) => { + let arr: &[u8; 2] = val[0..2].try_into()?; + let v = u16::from_le_bytes(*arr); + + // Determine how many pins exist in this group + let max_pin = if let (Some(g), Some(part)) = + (group, chip_part) + { + part.pins_in_group(g) + } else { + 16 // Default to all 16 if we can't determine + }; + + for i in 0..16 { + if i < max_pin { + print!("{:4}", (v >> i) & 1) + } else { + print!(" .") + } + } + println!(); } - println!(); } } } diff --git a/cmd/gpio/src/stm32h7.rs b/cmd/gpio/src/stm32h7.rs new file mode 100644 index 00000000..0f1f8e09 --- /dev/null +++ b/cmd/gpio/src/stm32h7.rs @@ -0,0 +1,173 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use anyhow::{Result, anyhow}; +use humility_cli::ExecutionContext; +use humility_hiffy::HiffyFunction; +use humility_hiffy::IpcError; + +use crate::config_cache::ConfigCache; +use crate::stm32h7_parts::{Stm32H7Part, Stm32H7Series}; + +/// Read GPIO register block from device memory +fn read_gpio_register_block( + context: &mut ExecutionContext, + part: &Stm32H7Part, + group: char, +) -> Result> { + let core = context.core.as_mut().unwrap(); + let addr = part + .gpio_register_block_address(group) + .ok_or(anyhow!("no address for GPIO group {}", group))?; + let mut buffer = vec![0u8; Stm32H7Part::gpio_register_block_size()]; + core.read_8(addr, &mut buffer)?; + Ok(buffer) +} + +pub fn show_gpio_with_config( + context: &mut ExecutionContext, + gpio_input: &HiffyFunction, + chip: &str, + args: &[(u16, Option, String)], + results: &[Result, IpcError>], +) -> Result<()> { + // Look up chip in database + let part = Stm32H7Series::find_part(chip, true); + + // Reject --with-config for unknown chips (backward compatibility: only basic GPIO ops) + if !part.is_known() { + return Err(anyhow!( + "GPIO `--with-config` is not supported for unknown chip '{}'. \ + Use `--input` without `-w` for basic GPIO operations.", + chip + )); + } + + let mut config_cache = ConfigCache::new(); + + for (ndx, arg) in args.iter().enumerate() { + let group = + arg.2.chars().next().ok_or(anyhow!("invalid group '{}'", arg.2))?; + + // Check if this group exists on this chip variant + let group_available = group >= 'A' && group <= part.max_group(); + + match arg.1 { + Some(pin) => { + // Specific pin requested + if !group_available { + // Group doesn't exist on this chip - show value but no config + println!( + "{}:{:<2} = {} None", + arg.2, + pin, + match results[ndx] { + Err(code) => { + gpio_input.strerror(code) + } + Ok(ref val) => { + let arr: &[u8; 2] = val[0..2].try_into()?; + let v = u16::from_le_bytes(*arr); + format!("{}", (v >> pin) & 1) + } + } + ); + } else if pin >= part.pins_in_group(group) { + // Pin doesn't exist in this partially filled group + println!( + "{}:{:<2} = {} None", + arg.2, + pin, + match results[ndx] { + Err(code) => { + gpio_input.strerror(code) + } + Ok(ref val) => { + let arr: &[u8; 2] = val[0..2].try_into()?; + let v = u16::from_le_bytes(*arr); + format!("{}", (v >> pin) & 1) + } + } + ); + } else { + // Group exists and pin is valid - show config + let register_data = config_cache.get_or_fetch( + context, + group, + |ctx, g| read_gpio_register_block(ctx, part, g), + )?; + + let config = + Stm32H7Part::get_pin_config(register_data, pin)?; + + println!( + "{}:{:<2} = {} {}", + arg.2, + pin, + match results[ndx] { + Err(code) => { + gpio_input.strerror(code) + } + Ok(ref val) => { + let arr: &[u8; 2] = val[0..2].try_into()?; + let v = u16::from_le_bytes(*arr); + format!("{}", (v >> pin) & 1) + } + }, + config + ); + } + } + + None => { + // All pins in port requested + if !group_available { + // Skip ports that don't exist on this chip variant + continue; + } + + match results[ndx] { + Err(code) => { + println!( + "Port {}: {}", + arg.2, + gpio_input.strerror(code) + ) + } + Ok(ref val) => { + let arr: &[u8; 2] = val[0..2].try_into()?; + let v = u16::from_le_bytes(*arr); + + // Get cached register block data + let register_data = config_cache.get_or_fetch( + context, + group, + |ctx, g| read_gpio_register_block(ctx, part, g), + )?; + + let max_pin = part.pins_in_group(group); + for i in 0..16 { + if i >= max_pin { + // Pin doesn't exist in this partially filled group - skip it + continue; + } + + let config = + Stm32H7Part::get_pin_config(register_data, i)?; + + println!( + "{}:{:<2} = {} {}", + arg.2, + i, + (v >> i) & 1, + config + ); + } + } + } + } + } + } + Ok(()) +} diff --git a/cmd/gpio/src/stm32h7_parts.rs b/cmd/gpio/src/stm32h7_parts.rs new file mode 100644 index 00000000..e29070de --- /dev/null +++ b/cmd/gpio/src/stm32h7_parts.rs @@ -0,0 +1,773 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! STM32H7 parts database and GPIO configuration interpretation. +//! +//! This module is based on the STMCUFinder database and provides part lookup +//! and GPIO register interpretation for STM32H7 series microcontrollers. + +use anyhow::{Result, anyhow}; +use std::fmt; +use zerocopy::{AsBytes, FromBytes}; + +/// Represents a specific STM32H7 series microcontroller part. +#[derive(Debug, PartialEq, Clone)] +pub struct Stm32H7Part { + com_part_no: &'static str, + part_no: &'static str, + ref_no: &'static str, + gpio_count: u8, + groups: u8, + max_group: char, +} + +impl Stm32H7Part { + // --- Public Getters --- + #[cfg(test)] + pub fn com_part_no(&self) -> &'static str { + self.com_part_no + } + #[cfg(test)] + pub fn part_no(&self) -> &'static str { + self.part_no + } + #[cfg(test)] + pub fn ref_no(&self) -> &'static str { + self.ref_no + } + #[cfg(test)] + pub fn gpio_count(&self) -> u8 { + self.gpio_count + } + + #[cfg(test)] + pub fn groups(&self) -> u8 { + self.groups + } + pub fn max_group(&self) -> char { + self.max_group + } + + /// Returns true if this is a known chip from the database, false if it's + /// the fallback for backward compatibility with unknown chips. + pub fn is_known(&self) -> bool { + self.part_no != "Unknown" + } + + /// Get the number of valid pins in a specific GPIO group. + /// Returns number of pins (0-16) that actually exist in the group. + pub fn pins_in_group(&self, group: char) -> u8 { + if group < 'A' || group > self.max_group { + return 0; + } + + let group_index = (group as u8) - b'A'; + let full_groups = self.gpio_count / 16; + + if group_index < full_groups { + // This is a fully populated group + 16 + } else if group_index == full_groups { + // This is a partially populated group + let remainder = self.gpio_count % 16; + if remainder == 0 { 16 } else { remainder } + } else { + // Beyond the last group + 0 + } + } + + /// Lookup STM32H7 GPIO configuration register addresses. + /// + /// Calculates the register block address for a given GPIO group. + /// Each group is offset by 0x400 bytes from the base address. + /// This method validates against the chip's maximum supported group. + pub fn gpio_register_block_address(&self, group: char) -> Option { + // Section 11.4: GPIO registers + // Base address for GPIO Port A + const BASE_ADDR: u32 = 0x5802_0000; + const GROUP_OFFSET: u32 = 0x400; + + if group < 'A' || group > self.max_group { + return None; + } + + let group_index = (group as u8 - b'A') as u32; + Some(BASE_ADDR + (group_index * GROUP_OFFSET)) + } + + pub fn gpio_register_block_size() -> usize { + core::mem::size_of::() + } + + /// Get the detailed configuration of a specific pin. + pub fn get_pin_config( + register_block_data: &[u8], + pin: u8, + ) -> Result { + let rb = GpioRegisters::read_from_prefix(register_block_data).ok_or( + anyhow!("cannot convert register_block_data to GpioRegisters"), + )?; + // Note: Apart from Reserved values, all data can be mapped to valid `GpioRegisters`. + + let reg = rb.moder; + let mode = match (reg >> (pin * 2)) & 3 { + 0 => "Input", + 1 => "Output", + 2 => "Alternate", + 3 => "Analog", + _ => unreachable!(), + }; + + let reg = rb.otyper; + let otype = match (reg >> pin) & 1 { + 0 => "OutPushPull", + 1 => "OutOpenDrain", + _ => unreachable!(), + }; + + let reg = rb.ospeedr; + let speed = match (reg >> (pin * 2)) & 3 { + 0 => "LowSpeed", + 1 => "MediumSpeed", + 2 => "HighSpeed", + 3 => "VeryHighSpeed", + _ => unreachable!(), + }; + + let reg = rb.pupdr; + let pull = match (reg >> (pin * 2)) & 3 { + 0 => "NoPull", + 1 => "PullUp", + 2 => "PullDown", + 3 => "ReservedPull", + _ => unreachable!(), + }; + + let reg = rb.idr; + let input = match (reg >> pin) & 1 { + 0 => "InZero", + 1 => "InOne", + _ => unreachable!(), + }; + + let reg = rb.odr; + let output = match (reg >> pin) & 1 { + 0 => "OutZero", + 1 => "OutOne", + _ => unreachable!(), + }; + + // Note: Bit 16 indicates that the register is locked or unlocked. + let reg = rb.lckr; + let lock = match (reg >> pin) & 1 { + 0 => "Unlocked", + 1 => "Locked", + _ => unreachable!(), + }; + + let af_value: &[&'static str] = &[ + "AF0", "AF1", "AF2", "AF3", "AF4", "AF5", "AF6", "AF7", "AF8", + "AF9", "AF10", "AF11", "AF12", "AF13", "AF14", "AF15", + ]; + + let alternate = af_value[if pin < 8 { + let reg = rb.afrl; + (reg >> (pin * 4)) & 15 + } else { + let reg = rb.afrh; + (reg >> ((pin - 8) * 4)) & 15 + } as usize]; + + Ok(PinConfig { + mode, + otype, + speed, + pull, + input, + output, + lock, + alternate, + }) + } +} + +/// Fallback part definition for unknown chips. +/// Provides backward compatibility with old behavior that assumed all chips +/// have GPIO groups A-K (11 groups, 176 pins total). +/// This allows basic GPIO operations without full chip knowledge. +const UNKNOWN_PART: Stm32H7Part = Stm32H7Part { + com_part_no: "Unknown", + part_no: "Unknown", + ref_no: "Unknown", + gpio_count: 176, // 11 groups * 16 pins + groups: 11, + max_group: 'K', +}; + +/// The main, read-only database of STM32H7 parts for normal (non-test) builds. +const PARTS: &[Stm32H7Part] = &[ + // Commercial Part No,Part No,Reference,I/O,Groups,MaxGroup + Stm32H7Part { + com_part_no: "STM32H753ZIT6", + part_no: "STM32H753ZI", + ref_no: "STM32H753ZITx", + gpio_count: 114, + groups: 8, + max_group: 'H', + }, + Stm32H7Part { + com_part_no: "STM32H753XIH6TR", + part_no: "STM32H753XI", + ref_no: "STM32H753XIHx", + gpio_count: 172, + groups: 11, + max_group: 'K', + }, + Stm32H7Part { + com_part_no: "STM32H753XIH6", + part_no: "STM32H753XI", + ref_no: "STM32H753XIHx", + gpio_count: 172, + groups: 11, + max_group: 'K', + }, + Stm32H7Part { + com_part_no: "STM32H753VIT6", + part_no: "STM32H753VI", + ref_no: "STM32H753VITx", + gpio_count: 82, + groups: 6, + max_group: 'F', + }, + Stm32H7Part { + com_part_no: "STM32H753VIH6", + part_no: "STM32H753VI", + ref_no: "STM32H753VIHx", + gpio_count: 82, + groups: 6, + max_group: 'F', + }, + Stm32H7Part { + com_part_no: "STM32H753IIT6", + part_no: "STM32H753II", + ref_no: "STM32H753IITx", + gpio_count: 140, + groups: 9, + max_group: 'I', + }, + Stm32H7Part { + com_part_no: "STM32H753IIK6", + part_no: "STM32H753II", + ref_no: "STM32H753IIKx", + gpio_count: 140, + groups: 9, + max_group: 'I', + }, + Stm32H7Part { + com_part_no: "STM32H753BIT6", + part_no: "STM32H753BI", + ref_no: "STM32H753BITx", + gpio_count: 168, + groups: 11, + max_group: 'K', + }, + Stm32H7Part { + com_part_no: "STM32H753AII6", + part_no: "STM32H753AI", + ref_no: "STM32H753AIIx", + gpio_count: 132, + groups: 9, + max_group: 'I', + }, + Stm32H7Part { + com_part_no: "STM32H743ZIT6TR", + part_no: "STM32H743ZI", + ref_no: "STM32H743ZITx", + gpio_count: 114, + groups: 8, + max_group: 'H', + }, + Stm32H7Part { + com_part_no: "STM32H743ZIT6", + part_no: "STM32H743ZI", + ref_no: "STM32H743ZITx", + gpio_count: 114, + groups: 8, + max_group: 'H', + }, + Stm32H7Part { + com_part_no: "STM32H743ZGT6", + part_no: "STM32H743ZG", + ref_no: "STM32H743ZGTx", + gpio_count: 114, + groups: 8, + max_group: 'H', + }, + Stm32H7Part { + com_part_no: "STM32H743XIH6", + part_no: "STM32H743XI", + ref_no: "STM32H743XIHx", + gpio_count: 172, + groups: 11, + max_group: 'K', + }, + Stm32H7Part { + com_part_no: "STM32H743XGH6", + part_no: "STM32H743XG", + ref_no: "STM32H743XGHx", + gpio_count: 172, + groups: 11, + max_group: 'K', + }, + Stm32H7Part { + com_part_no: "STM32H743VIT6TR", + part_no: "STM32H743VI", + ref_no: "STM32H743VITx", + gpio_count: 82, + groups: 6, + max_group: 'F', + }, + Stm32H7Part { + com_part_no: "STM32H743VIT6", + part_no: "STM32H743VI", + ref_no: "STM32H743VITx", + gpio_count: 82, + groups: 6, + max_group: 'F', + }, + Stm32H7Part { + com_part_no: "STM32H743VIH6TR", + part_no: "STM32H743VI", + ref_no: "STM32H743VIHx", + gpio_count: 82, + groups: 6, + max_group: 'F', + }, + Stm32H7Part { + com_part_no: "STM32H743VIH6", + part_no: "STM32H743VI", + ref_no: "STM32H743VIHx", + gpio_count: 82, + groups: 6, + max_group: 'F', + }, + Stm32H7Part { + com_part_no: "STM32H743VGT6", + part_no: "STM32H743VG", + ref_no: "STM32H743VGTx", + gpio_count: 82, + groups: 6, + max_group: 'F', + }, + Stm32H7Part { + com_part_no: "STM32H743VGH6", + part_no: "STM32H743VG", + ref_no: "STM32H743VGHx", + gpio_count: 82, + groups: 6, + max_group: 'F', + }, + Stm32H7Part { + com_part_no: "STM32H743IIT6", + part_no: "STM32H743II", + ref_no: "STM32H743IITx", + gpio_count: 140, + groups: 9, + max_group: 'I', + }, + Stm32H7Part { + com_part_no: "STM32H743IIK6TR", + part_no: "STM32H743II", + ref_no: "STM32H743IIKx", + gpio_count: 140, + groups: 9, + max_group: 'I', + }, + Stm32H7Part { + com_part_no: "STM32H743IIK6", + part_no: "STM32H743II", + ref_no: "STM32H743IIKx", + gpio_count: 140, + groups: 9, + max_group: 'I', + }, + Stm32H7Part { + com_part_no: "STM32H743IGT6", + part_no: "STM32H743IG", + ref_no: "STM32H743IGTx", + gpio_count: 140, + groups: 9, + max_group: 'I', + }, + Stm32H7Part { + com_part_no: "STM32H743IGK6", + part_no: "STM32H743IG", + ref_no: "STM32H743IGKx", + gpio_count: 140, + groups: 9, + max_group: 'I', + }, + Stm32H7Part { + com_part_no: "STM32H743BIT6", + part_no: "STM32H743BI", + ref_no: "STM32H743BITx", + gpio_count: 168, + groups: 11, + max_group: 'K', + }, + Stm32H7Part { + com_part_no: "STM32H743BGT6", + part_no: "STM32H743BG", + ref_no: "STM32H743BGTx", + gpio_count: 168, + groups: 11, + max_group: 'K', + }, + Stm32H7Part { + com_part_no: "STM32H743AII6TR", + part_no: "STM32H743AI", + ref_no: "STM32H743AIIx", + gpio_count: 132, + groups: 9, + max_group: 'I', + }, + Stm32H7Part { + com_part_no: "STM32H743AII6", + part_no: "STM32H743AI", + ref_no: "STM32H743AIIx", + gpio_count: 132, + groups: 9, + max_group: 'I', + }, + Stm32H7Part { + com_part_no: "STM32H743AGI6", + part_no: "STM32H743AG", + ref_no: "STM32H743AGIx", + gpio_count: 132, + groups: 9, + max_group: 'I', + }, +]; + +/// A static, read-only database of parts that only exist for testing purposes. +/// This is conditionally compiled and is only available when running `cargo test`. +#[cfg(test)] +const TEST_ONLY_PARTS: &[Stm32H7Part] = &[ + // Entry to create an ambiguous match on `part_no` for testing. + Stm32H7Part { + com_part_no: "STM32H753BI-TEST", + part_no: "STM32H753BI", + ref_no: "STM32H753BITx-TEST", + gpio_count: 50, + groups: 4, + max_group: 'D', + }, +]; + +/// STM32H7 series GPIO Registers +/// +/// Pulling these in from any of the available public crates is +/// problematic. The "stm32h7" crate will prevent us from linking +/// on Windows and the "stm32-metapac" crate forces us to calculate +/// offsets for all of the fields in the GPIO Register blocks. +/// In this case, the code is more readable and easier to maintain +/// if we just get our information from the ST Micro datasheet: "RM0433". +#[derive(FromBytes, AsBytes, Copy, Clone)] +#[repr(C)] +pub struct GpioRegisters { + /// Mode Register + pub moder: u32, // 0x00 + /// Output Type Register + pub otyper: u32, // 0x04 + /// Output Speed Register + pub ospeedr: u32, // 0x08 + /// Pull-Up/Pull-Down Register + pub pupdr: u32, // 0x0C + /// Input Data Register + pub idr: u32, // 0x10 + /// Output Data Register + pub odr: u32, // 0x14 + /// Bit Set/Reset Register (write-only) + pub bsrr: u32, // 0x18 + /// Configuration Lock Register + pub lckr: u32, // 0x1C + /// Alternate Function Low Register + pub afrl: u32, // 0x20 + /// Alternate Function High Register + pub afrh: u32, // 0x24 +} + +/// Pin configuration details +#[derive(Debug)] +pub struct PinConfig { + pub mode: &'static str, + pub otype: &'static str, + pub speed: &'static str, + pub pull: &'static str, + pub input: &'static str, + pub output: &'static str, + pub lock: &'static str, + pub alternate: &'static str, +} + +impl fmt::Display for PinConfig { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Use the write! macro to format the output to the formatter 'f' + write!( + f, + "{}:{}:{}:{}:{}:{}:{}:{}", + self.mode, + self.otype, + self.speed, + self.pull, + self.alternate, + self.lock, + self.input, + self.output + ) + } +} + +/// Represents the STM32H7 series of microcontrollers. +/// +/// This struct acts as a namespace for functions and data specific to the H7 series. +/// It can be expanded to implement a more generic `ChipSeries` trait in the future. +pub struct Stm32H7Series; + +impl Stm32H7Series { + /// Checks a list of matched parts for uniqueness based on gpio_count. + fn check_uniqueness( + matches: Vec<&'static Stm32H7Part>, + prefer_max_pins: bool, + ) -> Result<&'static Stm32H7Part, &'static str> { + if matches.len() == 1 { + return Ok(matches[0]); + } + + let first_gpio_count = matches[0].gpio_count; + if matches.iter().all(|p| p.gpio_count == first_gpio_count) { + Ok(matches[0]) + } else if prefer_max_pins { + matches + .into_iter() + .max_by_key(|p| p.gpio_count) + .ok_or("Failed to find part with max pins") + } else { + Err( + "Ambiguous match: Multiple parts found with different GPIO counts", + ) + } + } + + /// Finds a part by an identifier, checking fields in a specific order. + /// + /// Returns a known part from the database if found, or returns the UNKNOWN_PART + /// fallback for backward compatibility. The caller can use `is_known()` to + /// determine if the part is in the database. + pub fn find_part( + identifier: &str, + prefer_max_pins: bool, + ) -> &'static Stm32H7Part { + // Collect parts into a Vec to easily create new iterators. + let all_parts: Vec<&'static Stm32H7Part> = { + #[cfg(test)] + { + PARTS.iter().chain(TEST_ONLY_PARTS.iter()).collect() + } + #[cfg(not(test))] + { + PARTS.iter().collect() + } + }; + + // Level 1: Match by `ref_no` + let matches_ref_no: Vec<_> = all_parts + .iter() + .filter(|p| p.ref_no.eq_ignore_ascii_case(identifier)) + .cloned() + .collect(); + if !matches_ref_no.is_empty() + && let Ok(part) = + Self::check_uniqueness(matches_ref_no, prefer_max_pins) + { + return part; + } + + // Level 2: Match by `com_part_no` + let matches_com_part_no: Vec<_> = all_parts + .iter() + .filter(|p| p.com_part_no.eq_ignore_ascii_case(identifier)) + .cloned() + .collect(); + if !matches_com_part_no.is_empty() + && let Ok(part) = + Self::check_uniqueness(matches_com_part_no, prefer_max_pins) + { + return part; + } + + // Level 3: Match by `part_no` + let matches_part_no: Vec<_> = all_parts + .iter() + .filter(|p| p.part_no.eq_ignore_ascii_case(identifier)) + .cloned() + .collect(); + if !matches_part_no.is_empty() + && let Ok(part) = + Self::check_uniqueness(matches_part_no, prefer_max_pins) + { + return part; + } + + // Not found in database - return unknown part fallback for backward compatibility + &UNKNOWN_PART + } +} + +#[cfg(test)] +mod tests { + use super::*; + use zerocopy::AsBytes; + + #[test] + fn test_find_part_success() { + // Unique match on `ref_no` + let part = Stm32H7Series::find_part("STM32H753AIIx", false); + assert!(part.is_known()); + assert_eq!(part.ref_no(), "STM32H753AIIx"); + + // Unique match on `com_part_no` + let part = Stm32H7Series::find_part("STM32H743BGT6", false); + assert!(part.is_known()); + assert_eq!(part.com_part_no(), "STM32H743BGT6"); + + // Multiple matches, but same gpio_count + let part = Stm32H7Series::find_part("STM32H753XI", false); + assert!(part.is_known()); + assert_eq!(part.part_no(), "STM32H753XI"); + } + + #[test] + fn test_find_part_case_insensitive() { + // Test with lowercase identifier + let part = Stm32H7Series::find_part("stm32h753aiix", false); + assert!(part.is_known()); + assert_eq!(part.ref_no(), "STM32H753AIIx"); + + // Test with mixed case identifier + let part = Stm32H7Series::find_part("Stm32H743BGT6", false); + assert!(part.is_known()); + assert_eq!(part.com_part_no(), "STM32H743BGT6"); + } + + #[test] + fn test_part_not_found() { + // Returns unknown fallback for backward compatibility + let part = Stm32H7Series::find_part("STM32F407VGT6", false); + assert!(!part.is_known()); + assert_eq!(part.max_group(), 'K'); + assert_eq!(part.gpio_count(), 176); + } + + #[test] + fn test_ambiguous_match_with_max_pins_flag() { + // When ambiguous, prefer_max_pins flag selects highest pin count + let part = Stm32H7Series::find_part("STM32H753BI", true); + assert!(part.is_known()); + assert_eq!(part.gpio_count, 168); + } + + #[test] + fn test_pins_in_group() { + // Test STM32H753ZITx: 114 pins, groups A-H (8 groups) + // 114 / 16 = 7 full groups (A-G = 112 pins), group H has 2 pins + let part = Stm32H7Series::find_part("STM32H753ZITx", false); + assert_eq!(part.gpio_count(), 114); + assert_eq!(part.max_group(), 'H'); + + // Groups A-G should have all 16 pins + for group in 'A'..='G' { + assert_eq!( + part.pins_in_group(group), + 16, + "Group {} should have 16 pins", + group + ); + } + // Group H should have 2 pins (114 - 112 = 2) + assert_eq!(part.pins_in_group('H'), 2); + + // Groups beyond H should have 0 pins + assert_eq!(part.pins_in_group('I'), 0); + assert_eq!(part.pins_in_group('J'), 0); + + // Test STM32H753VIT6: 82 pins, groups A-F (6 groups) + // 82 / 16 = 5 full groups (A-E = 80 pins), group F has 2 pins + let part = Stm32H7Series::find_part("STM32H753VIT6", false); + assert_eq!(part.gpio_count(), 82); + assert_eq!(part.max_group(), 'F'); + + // Groups A-E should have all 16 pins + for group in 'A'..='E' { + assert_eq!( + part.pins_in_group(group), + 16, + "Group {} should have 16 pins", + group + ); + } + // Group F should have 2 pins (82 - 80 = 2) + assert_eq!(part.pins_in_group('F'), 2); + + // Groups beyond F should have 0 pins + assert_eq!(part.pins_in_group('G'), 0); + } + + #[test] + fn interpret_config_data() { + use crate::stm32h7_testdata::GPIO_TEST_DATA; + + let partname = "STM32H753XI"; + let part = Stm32H7Series::find_part(partname, false); + assert!(part.is_known(), "Lookup failed for {}", partname); + assert_eq!(part.gpio_count(), 172); + assert_eq!(part.groups(), 11); + assert_eq!(part.max_group(), 'K'); + + for test_data in GPIO_TEST_DATA.iter() { + let result = part.gpio_register_block_address(test_data.group); + assert!( + result.is_some(), + "gpio_register_block_address failed for {} group {}", + partname, + test_data.group + ); + let addr = result.unwrap(); + assert_eq!(addr, test_data.addr); + + for pin in 0u8..16u8 { + let result = Stm32H7Part::get_pin_config( + test_data.config_data.as_bytes(), + pin, + ); + assert!( + result.is_ok(), + "get_pin_config failed for {}:{}:{}", + partname, + test_data.group, + pin + ); + let pin_config = result.unwrap(); + assert_eq!( + pin_config.to_string(), + test_data.expected_outputs[pin as usize], + "Group:{} Pin:{}", + test_data.group, + pin + ); + } + } + } +} diff --git a/cmd/gpio/src/stm32h7_testdata.rs b/cmd/gpio/src/stm32h7_testdata.rs new file mode 100644 index 00000000..3f56bca0 --- /dev/null +++ b/cmd/gpio/src/stm32h7_testdata.rs @@ -0,0 +1,394 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Test data for STM32H7 GPIO configuration tests. +//! This file contains GPIO register block test data. + +#![cfg(test)] + +use super::stm32h7_parts::GpioRegisters; + +pub struct TestData { + pub group: char, + pub addr: u32, + pub config_data: &'static GpioRegisters, + pub expected_outputs: [&'static str; 16], +} + +pub const GPIO_TEST_DATA: &[TestData] = &[ + TestData { + group: 'A', + addr: 0x58020000, + config_data: &GpioRegisters { + moder: 0x6aabbffb, + otyper: 0x00008000, + ospeedr: 0x0c00c00c, + pupdr: 0x24000000, + idr: 0x0000c600, + odr: 0x00008000, + bsrr: 0x00000000, + lckr: 0x00000000, + afrl: 0xb00000b0, + afrh: 0x00077770, + }, + expected_outputs: [ + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF11:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF11:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:LowSpeed:NoPull:AF7:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:LowSpeed:NoPull:AF7:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:LowSpeed:NoPull:AF7:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:LowSpeed:NoPull:AF7:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:PullUp:AF0:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:LowSpeed:PullDown:AF0:Unlocked:InOne:OutZero", + "Output:OutOpenDrain:LowSpeed:NoPull:AF0:Unlocked:InOne:OutOne", + ], + }, + TestData { + group: 'B', + addr: 0x58020400, + config_data: &GpioRegisters { + moder: 0x47fa90b7, + otyper: 0x00000342, + ospeedr: 0x2000cac0, + pupdr: 0x00000000, + idr: 0x000043f0, + odr: 0x00000340, + bsrr: 0x00000000, + lckr: 0x00000000, + afrl: 0xc0000000, + afrh: 0x55500044, + }, + expected_outputs: [ + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Output:OutOpenDrain:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Input:OutPushPull:HighSpeed:NoPull:AF0:Unlocked:InOne:OutZero", + "Input:OutPushPull:HighSpeed:NoPull:AF0:Unlocked:InOne:OutZero", + "Output:OutOpenDrain:LowSpeed:NoPull:AF0:Unlocked:InOne:OutOne", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Alternate:OutOpenDrain:LowSpeed:NoPull:AF4:Unlocked:InOne:OutOne", + "Alternate:OutOpenDrain:LowSpeed:NoPull:AF4:Unlocked:InOne:OutOne", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Output:OutPushPull:LowSpeed:NoPull:AF5:Unlocked:InZero:OutZero", + "Input:OutPushPull:HighSpeed:NoPull:AF5:Unlocked:InOne:OutZero", + "Output:OutPushPull:LowSpeed:NoPull:AF5:Unlocked:InZero:OutZero", + ], + }, + TestData { + group: 'C', + addr: 0x58020800, + config_data: &GpioRegisters { + moder: 0xffffdadf, + otyper: 0x00000000, + ospeedr: 0x00002f00, + pupdr: 0x00000000, + idr: 0x00000004, + odr: 0x00000004, + bsrr: 0x00000000, + lckr: 0x00000000, + afrl: 0x00bb0000, + afrh: 0x00000000, + }, + expected_outputs: [ + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Output:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InOne:OutOne", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF11:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF11:Unlocked:InZero:OutZero", + "Output:OutPushPull:HighSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + ], + }, + TestData { + group: 'D', + addr: 0x58020c00, + config_data: &GpioRegisters { + moder: 0xaaaaaaba, + otyper: 0x00000000, + ospeedr: 0xffffffcf, + pupdr: 0x00001000, + idr: 0x0000c7bb, + odr: 0x00000000, + bsrr: 0x00000000, + lckr: 0x00000000, + afrl: 0xccccc0cc, + afrh: 0xcccccccc, + }, + expected_outputs: [ + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:PullUp:AF12:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + ], + }, + TestData { + group: 'E', + addr: 0x58021000, + config_data: &GpioRegisters { + moder: 0xaaaaa9aa, + otyper: 0x00000000, + ospeedr: 0xffffc8cf, + pupdr: 0x00000000, + idr: 0x0000ffb0, + odr: 0x00000010, + bsrr: 0x00000000, + lckr: 0x00000000, + afrl: 0xc550c5cc, + afrh: 0xcccccccc, + }, + expected_outputs: [ + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:LowSpeed:NoPull:AF5:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InZero:OutZero", + "Output:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InOne:OutOne", + "Alternate:OutPushPull:HighSpeed:NoPull:AF5:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:LowSpeed:NoPull:AF5:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF12:Unlocked:InOne:OutZero", + ], + }, + TestData { + group: 'F', + addr: 0x58021400, + config_data: &GpioRegisters { + moder: 0xffeaafcf, + otyper: 0x00000000, + ospeedr: 0x00155020, + pupdr: 0x00000010, + idr: 0x00000044, + odr: 0x00000000, + bsrr: 0x00000000, + lckr: 0x00000000, + afrl: 0x99000000, + afrh: 0x000009aa, + }, + expected_outputs: [ + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Input:OutPushPull:HighSpeed:PullUp:AF0:Unlocked:InOne:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:MediumSpeed:NoPull:AF9:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:MediumSpeed:NoPull:AF9:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:MediumSpeed:NoPull:AF10:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:MediumSpeed:NoPull:AF10:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:MediumSpeed:NoPull:AF9:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + ], + }, + TestData { + group: 'G', + addr: 0x58021800, + config_data: &GpioRegisters { + moder: 0xfabfefff, + otyper: 0x00000000, + ospeedr: 0x0fc01000, + pupdr: 0x00000000, + idr: 0x00000040, + odr: 0x00000000, + bsrr: 0x00000000, + lckr: 0x00000000, + afrl: 0x0a000000, + afrh: 0x00bbb000, + }, + expected_outputs: [ + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:MediumSpeed:NoPull:AF10:Unlocked:InOne:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF11:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF11:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:VeryHighSpeed:NoPull:AF11:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + ], + }, + TestData { + group: 'H', + addr: 0x58021c00, + config_data: &GpioRegisters { + moder: 0xffffffff, + otyper: 0x00000000, + ospeedr: 0x00000000, + pupdr: 0x00000000, + idr: 0x00000000, + odr: 0x00000000, + bsrr: 0x00000000, + lckr: 0x00000000, + afrl: 0x00000000, + afrh: 0x00000000, + }, + expected_outputs: [ + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + ], + }, + TestData { + group: 'I', + addr: 0x58022000, + config_data: &GpioRegisters { + moder: 0x7fffffa9, + otyper: 0x00000000, + ospeedr: 0x00000020, + pupdr: 0x00000000, + idr: 0x00008001, + odr: 0x00008001, + bsrr: 0x00000000, + lckr: 0x00000000, + afrl: 0x00005550, + afrh: 0x00000000, + }, + expected_outputs: [ + "Output:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InOne:OutOne", + "Alternate:OutPushPull:LowSpeed:NoPull:AF5:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:HighSpeed:NoPull:AF5:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:LowSpeed:NoPull:AF5:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Output:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InOne:OutOne", + ], + }, + TestData { + group: 'J', + addr: 0x58022400, + config_data: &GpioRegisters { + moder: 0xfffaffff, + otyper: 0x00000000, + ospeedr: 0x00000000, + pupdr: 0x00000000, + idr: 0x00000300, + odr: 0x00000000, + bsrr: 0x00000000, + lckr: 0x00000000, + afrl: 0x00000000, + afrh: 0x00000088, + }, + expected_outputs: [ + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Alternate:OutPushPull:LowSpeed:NoPull:AF8:Unlocked:InOne:OutZero", + "Alternate:OutPushPull:LowSpeed:NoPull:AF8:Unlocked:InOne:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + ], + }, + TestData { + group: 'K', + addr: 0x58022800, + config_data: &GpioRegisters { + moder: 0xffffffff, + otyper: 0x00000000, + ospeedr: 0x00000000, + pupdr: 0x00000000, + idr: 0x00000000, + odr: 0x00000000, + bsrr: 0x00000000, + lckr: 0x00000000, + afrl: 0x00000000, + afrh: 0x00000000, + }, + expected_outputs: [ + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + "Analog:OutPushPull:LowSpeed:NoPull:AF0:Unlocked:InZero:OutZero", + ], + }, +]; diff --git a/humility-bin/Cargo.toml b/humility-bin/Cargo.toml index 00887ee3..c89ca708 100644 --- a/humility-bin/Cargo.toml +++ b/humility-bin/Cargo.toml @@ -111,6 +111,7 @@ indicatif = { workspace = true } colored = { workspace = true } indexmap = { workspace = true } reedline = { workspace = true } +lazy_static.workspace = true [dev-dependencies] trycmd = { workspace = true }