From cb144522aee78a89ecb7984d1a0492629c5c8c1a Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Fri, 5 Apr 2024 15:37:19 -0700 Subject: [PATCH] stm32xx-sys: generate constants for EXTI pins (#1734) Currently, pins used for EXTI IRQs are defined in the `sys` task's config in `app.toml`. When a task wishes to actually use those pins, though, it must _also_ define them separately in order to call `sys.gpio_configure_input` (and if it wants to perform its own GPIO reads on that pin). This is generally done either by hard-coding the port and pin number in the task's source code, or by defining its own config for configuring which pin to use. This is unfortunate, as it results in a duplicated pin definition, either between the source code and the config if hard-coded, or twice in the config, if the task also gets the definition from config. And, if the task configures the pin in its own config, the author of that task must re-implement the config handling. This commit fixes this by adding new code generation to `drv-stm32xx-sys-api` to generate a module called `gpio_irq_pins` containing `PinSet` constants with the name of the IRQ pin and the pin number and port defined in the `sys` task's config section. This way, the same pin set used by `sys` when configuring the EXTI interrupt can be referenced by the task that consumes the interrupt, removing the need to duplicate the pin definition. I've edited the `nucleo-user-button` demo task to demonstrate using this. Signed-off-by: Eliza Weisman --- Cargo.lock | 1 + build/stm32xx-sys/src/lib.rs | 73 ++++++++++++++++++++++++++-- build/util/src/lib.rs | 9 ++-- drv/stm32h7-sprot-server/src/main.rs | 17 ++----- drv/stm32xx-sys-api/Cargo.toml | 1 + drv/stm32xx-sys-api/build.rs | 1 + drv/stm32xx-sys-api/src/lib.rs | 1 + task/nucleo-user-button/src/main.rs | 7 +-- 8 files changed, 86 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8bfc35eb4..a580ead37 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2038,6 +2038,7 @@ dependencies = [ name = "drv-stm32xx-sys-api" version = "0.1.0" dependencies = [ + "build-stm32xx-sys", "byteorder", "cfg-if", "counters", diff --git a/build/stm32xx-sys/src/lib.rs b/build/stm32xx-sys/src/lib.rs index b5498a091..0018f734d 100644 --- a/build/stm32xx-sys/src/lib.rs +++ b/build/stm32xx-sys/src/lib.rs @@ -2,14 +2,16 @@ // 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::Context; use quote::{quote, TokenStreamExt}; use serde::Deserialize; -use std::collections::BTreeMap; +use std::{collections::BTreeMap, io::Write}; #[derive(Deserialize, Default)] -#[serde(rename_all = "kebab-case", deny_unknown_fields)] +#[serde(rename_all = "kebab-case")] pub struct SysConfig { /// EXTI interrupts + #[serde(default)] gpio_irqs: BTreeMap, } @@ -17,7 +19,7 @@ pub struct SysConfig { #[serde(rename_all = "kebab-case", deny_unknown_fields)] struct GpioIrqConfig { port: Port, - pin: u8, + pin: usize, owner: GpioIrqOwner, } @@ -68,6 +70,62 @@ to_tokens_enum! { } } +pub fn build_gpio_irq_pins() -> anyhow::Result<()> { + let out_dir = build_util::out_dir(); + let dest_path = out_dir.join("gpio_irq_pins.rs"); + let mut out = std::fs::File::create(&dest_path).with_context(|| { + format!("failed to create file '{}'", dest_path.display()) + })?; + + let Some(sys_config) = + build_util::other_task_full_config::("sys")?.config + else { + // No GPIO IRQs are configured; nothing left to do here! + return Ok(()); + }; + + let task = build_util::task_name(); + let pins = sys_config + .gpio_irqs + .iter() + .filter_map(|(name, cfg)| { + let &GpioIrqConfig { + pin, + port, + ref owner, + } = cfg; + // Only generate constants for pins owned by the current task. + if owner.name != task { + return None; + } + + let name = match to_const_name(name.clone()) { + Ok(name) => name, + Err(e) => return Some(Err(e)), + }; + + Some(Ok(quote! { + pub const #name: PinSet = #port.pin(#pin); + })) + }) + .collect::>>()?; + + // Don't generate an empty module if there are no pins. + if pins.is_empty() { + return Ok(()); + } + + let tokens = quote! { + pub mod gpio_irq_pins { + use drv_stm32xx_gpio_common::{PinSet, Port}; + #( #pins )* + } + }; + writeln!(out, "{tokens}")?; + + Ok(()) +} + impl SysConfig { pub fn load() -> anyhow::Result { Ok(build_util::task_maybe_config::()?.unwrap_or_default()) @@ -111,7 +169,7 @@ impl SysConfig { let task = syn::parse_str(&owner.name)?; let note = quote::format_ident!( "{}_MASK", - owner.notification.to_uppercase().replace('-', "_") + to_const_name(owner.notification.clone())? ); let name = quote::format_ident!( @@ -181,3 +239,10 @@ impl SysConfig { }) } } + +fn to_const_name(mut s: String) -> anyhow::Result { + s.make_ascii_uppercase(); + let s = s.replace("-", "_"); + syn::parse_str::(&s) + .with_context(|| format!("`{s}` is not a valid Rust identifier")) +} diff --git a/build/util/src/lib.rs b/build/util/src/lib.rs index c4f062675..5054e4767 100644 --- a/build/util/src/lib.rs +++ b/build/util/src/lib.rs @@ -42,6 +42,11 @@ pub fn target_os() -> String { std::env::var("CARGO_CFG_TARGET_OS").unwrap() } +/// Reads the `HUBRIS_TASK_NAME` env var. +pub fn task_name() -> String { + crate::env_var("HUBRIS_TASK_NAME").expect("missing HUBRIS_TASK_NAME") +} + /// Checks to see whether the given feature is enabled pub fn has_feature(s: &str) -> bool { std::env::var(format!( @@ -104,12 +109,10 @@ pub fn config() -> Result { /// Pulls the task configuration. See `config` for more details. pub fn task_config() -> Result { - let task_name = - crate::env_var("HUBRIS_TASK_NAME").expect("missing HUBRIS_TASK_NAME"); task_maybe_config()?.ok_or_else(|| { anyhow!( "app.toml missing task config section [tasks.{}.config]", - task_name + task_name() ) }) } diff --git a/drv/stm32h7-sprot-server/src/main.rs b/drv/stm32h7-sprot-server/src/main.rs index efcc639ea..d694e6037 100644 --- a/drv/stm32h7-sprot-server/src/main.rs +++ b/drv/stm32h7-sprot-server/src/main.rs @@ -97,7 +97,10 @@ const MAX_UPDATE_ATTEMPTS: u16 = 3; // On the flipside, we have learned via unintended experiment that 5ms is too short! const DUMP_TIMEOUT: u32 = 1000; -// ROT_IRQ comes from app.toml +// On Gemini, the STM32H753 is in a LQFP176 package with ROT_IRQ +// on pin2/PE3 +use drv_stm32xx_sys_api::gpio_irq_pins::ROT_IRQ; + // We use spi3 on gimletlet and spi4 on gemini and gimlet. // You should be able to move the RoT board between SPI3, SPI4, and SPI6 // without much trouble even though SPI3 is the preferred connector and @@ -117,20 +120,10 @@ cfg_if::cfg_if! { target_board = "psc-c", target_board = "gemini-bu-1" ))] { - const ROT_IRQ: sys_api::PinSet = sys_api::PinSet { - // On Gemini, the STM32H753 is in a LQFP176 package with ROT_IRQ - // on pin2/PE3 - port: sys_api::Port::E, - pin_mask: 1 << 3, - }; const ROT_SPI_DEVICE: u8 = drv_spi_api::devices::ROT; fn debug_config(_sys: &sys_api::Sys) { } fn debug_set(_sys: &sys_api::Sys, _asserted: bool) { } } else if #[cfg(target_board = "gimletlet-2")] { - const ROT_IRQ: sys_api::PinSet = sys_api::PinSet { - port: sys_api::Port::D, - pin_mask: 1 << 0, - }; const DEBUG_PIN: sys_api::PinSet = sys_api::PinSet { port: sys_api::Port::E, pin_mask: 1 << 6, @@ -151,7 +144,7 @@ cfg_if::cfg_if! { } const ROT_SPI_DEVICE: u8 = drv_spi_api::devices::SPI3_HEADER; } else { - compile_error!("No configuration for ROT_IRQ"); + compile_error!("No configuration for ROT_SPI_DEVICE"); } } diff --git a/drv/stm32xx-sys-api/Cargo.toml b/drv/stm32xx-sys-api/Cargo.toml index d46974634..60fc89b84 100644 --- a/drv/stm32xx-sys-api/Cargo.toml +++ b/drv/stm32xx-sys-api/Cargo.toml @@ -18,6 +18,7 @@ userlib = { path = "../../sys/userlib" } [build-dependencies] idol.workspace = true +build-stm32xx-sys = { path = "../../build/stm32xx-sys" } [features] family-stm32h7 = ["drv-stm32xx-gpio-common/family-stm32h7"] diff --git a/drv/stm32xx-sys-api/build.rs b/drv/stm32xx-sys-api/build.rs index 41c969ce9..4bd6cdd3f 100644 --- a/drv/stm32xx-sys-api/build.rs +++ b/drv/stm32xx-sys-api/build.rs @@ -3,6 +3,7 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. fn main() -> Result<(), Box> { + build_stm32xx_sys::build_gpio_irq_pins()?; idol::client::build_client_stub( "../../idl/stm32xx-sys.idol", "client_stub.rs", diff --git a/drv/stm32xx-sys-api/src/lib.rs b/drv/stm32xx-sys-api/src/lib.rs index 5931eb230..f4fd5cc5a 100644 --- a/drv/stm32xx-sys-api/src/lib.rs +++ b/drv/stm32xx-sys-api/src/lib.rs @@ -367,3 +367,4 @@ impl From> for IrqControl { } include!(concat!(env!("OUT_DIR"), "/client_stub.rs")); +include!(concat!(env!("OUT_DIR"), "/gpio_irq_pins.rs")); diff --git a/task/nucleo-user-button/src/main.rs b/task/nucleo-user-button/src/main.rs index ae3acf667..8f85c4c5f 100644 --- a/task/nucleo-user-button/src/main.rs +++ b/task/nucleo-user-button/src/main.rs @@ -11,7 +11,7 @@ #![no_std] #![no_main] -use drv_stm32xx_sys_api::{Edge, IrqControl, PinSet, Port, Pull}; +use drv_stm32xx_sys_api::{Edge, IrqControl, Pull}; use ringbuf::ringbuf_entry; use userlib::*; @@ -72,10 +72,7 @@ pub fn main() -> ! { }); sys.gpio_configure_input( - PinSet { - port: Port::C, - pin_mask: (1 << 13), - }, + drv_stm32xx_sys_api::gpio_irq_pins::BUTTON, Pull::None, );