Skip to content

Commit

Permalink
stm32xx-sys: configure EXTI IRQs in app.toml
Browse files Browse the repository at this point in the history
This commit adds build-time code generation for the EXTI IRQ dispatch
table. Now, an app.toml can define a section like this:

```toml
[config.sys.gpio-irq.button]
port = "C"
pin = 13
owner = { name = "mytask", notification = "button" }
```

To assign PC13's EXTI IRQ to the `button` notification for task
`mytask`. The dispatch table is now generated by `build/stm32xx-sys` and
will include the EXTI interrupt configurations in the config.
  • Loading branch information
hawkw committed Mar 28, 2024
1 parent 8eadfde commit d8fdfe7
Show file tree
Hide file tree
Showing 7 changed files with 223 additions and 31 deletions.
15 changes: 15 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 19 additions & 2 deletions app/demo-stm32h7-nucleo/app-h753.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,22 @@ request_reset = ["hiffy"]

[tasks.sys]
name = "drv-stm32xx-sys"
features = ["h753"]
features = ["h753", "exti"]
priority = 1
uses = ["rcc", "gpios", "system_flash"]
max-sizes = {flash = 4096, ram = 2048}
uses = ["rcc", "gpios", "system_flash", "syscfg", "exti"]
start = true
task-slots = ["jefe"]
notifications = ["exti-wildcard-irq"]

[tasks.sys.interrupts]
"exti.exti0" = "exti-wildcard-irq"
"exti.exti1" = "exti-wildcard-irq"
"exti.exti2" = "exti-wildcard-irq"
"exti.exti3" = "exti-wildcard-irq"
"exti.exti4" = "exti-wildcard-irq"
"exti.exti9_5" = "exti-wildcard-irq"
"exti.exti15_10" = "exti-wildcard-irq"

[tasks.i2c_driver]
name = "drv-stm32xx-i2c-server"
Expand Down Expand Up @@ -145,6 +156,7 @@ max-sizes = {flash = 32768, ram = 32768 }
stacksize = 2048
start = true
task-slots = ["sys", "i2c_driver", "hf", "hash_driver"]
notifications = ["button"]

[tasks.hf]
name = "drv-gimlet-hf-server"
Expand Down Expand Up @@ -259,3 +271,8 @@ owner = {name = "udprpc", notification = "socket"}
port = 998
tx = { packets = 3, bytes = 1024 }
rx = { packets = 3, bytes = 1024 }

[config.sys.gpio-irqs.button]
port = "C"
pin = 13
owner = {name = "hiffy", notification = "button"}
17 changes: 17 additions & 0 deletions build/stm32xx-sys/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "build-stm32xx-sys"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anyhow = { workspace = true }
cfg-if = { workspace = true }
convert_case = { workspace = true }
proc-macro2 = { workspace = true }
quote = { workspace = true }
serde = { workspace = true }
syn = { workspace = true }

build-util = { path = "../util" }
148 changes: 148 additions & 0 deletions build/stm32xx-sys/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
use quote::{quote, TokenStreamExt};
use serde::Deserialize;
use std::collections::BTreeMap;

/// This represents our _subset_ of global config and _must not_ be marked with
/// `deny_unknown_fields`!
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
struct GlobalConfig {
sys: SysConfig,
}

#[derive(Deserialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct SysConfig {
/// EXTI interrupts
gpio_irqs: BTreeMap<String, GpioIrqConfig>,
}

#[derive(Deserialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
struct GpioIrqConfig {
port: ConfigPort,
pin: u8,
owner: GpioIrqOwner,
}

#[derive(Deserialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
struct GpioIrqOwner {
name: String,
notification: String,
}

#[derive(Copy, Clone, Debug, Deserialize)]
enum ConfigPort {
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
}

impl quote::ToTokens for ConfigPort {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
use proc_macro2::{Ident, Punct, Spacing, Span};
// i don't like typing
macro_rules! table {
($($variant:ident),*) => {
match self {
$(ConfigPort::$variant => {
tokens.append(Ident::new("Port", Span::call_site()));
tokens.append(Punct::new(':', Spacing::Joint));
tokens.append(Punct::new(':', Spacing::Alone));
tokens.append(Ident::new(stringify!($variant), Span::call_site()));
}),*
}
}
}
table! { A, B, C, D, E, F, G, H, I, J, K }
}
}

impl SysConfig {
pub fn load() -> anyhow::Result<Self> {
Ok(build_util::config::<GlobalConfig>()?.sys)
}

pub fn needs_exti(&self) -> bool {
!self.gpio_irqs.is_empty()
}

pub fn generate_exti_config(
&self,
) -> anyhow::Result<proc_macro2::TokenStream> {
#[derive(Debug)]
struct DispatchEntry<'a> {
_name: &'a str,
port: ConfigPort,
task: syn::Ident,
note: syn::Ident,
}

const NUM_EXTI_IRQS: usize = 16;
let mut dispatch_table: [Option<DispatchEntry<'_>>; NUM_EXTI_IRQS] =
Default::default();

for (
_name,
&GpioIrqConfig {
port,
pin,
ref owner,
},
) in &self.gpio_irqs
{
match dispatch_table.get_mut(pin as usize) {
Some(Some(curr)) => {
anyhow::bail!("pin {pin} is already mapped to IRQ {curr:?}")
}
Some(slot) => {
let task = syn::parse_str(&owner.name)?;
let note = quote::format_ident!(
"{}_MASK",
owner.notification.to_uppercase().replace('-', "_")
);
*slot = Some(DispatchEntry {
_name,
port,
task,
note,
})
}
None => anyhow::bail!(
"GPIO IRQ pin numbers must be < {NUM_EXTI_IRQS}; {pin} is out of range"
),
}
}

let dispatches = dispatch_table.iter().map(|slot| match slot {
Some(DispatchEntry {
port, task, note, ..
}) => quote! {
Some((
#port,
userlib::TaskId::for_index_and_gen(
hubris_num_tasks::Task::#task as usize,
userlib::Generation::ZERO,
),
crate::notifications::#task::#note,
))
},
None => quote! { None },
});

Ok(quote! {
pub(crate) const EXTI_DISPATCH_TABLE: [Option<(Port, TaskId, u32)>; #NUM_EXTI_IRQS] = [
#( #dispatches ),*
];
})
}
}
1 change: 1 addition & 0 deletions drv/stm32xx-sys/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ zerocopy = { workspace = true }
[build-dependencies]
idol = { workspace = true }
build-util = { path = "../../build/util" }
build-stm32xx-sys = { path = "../../build/stm32xx-sys" }

[features]
family-stm32h7 = ["stm32h7", "drv-stm32xx-uid/family-stm32h7"]
Expand Down
20 changes: 20 additions & 0 deletions drv/stm32xx-sys/build.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// 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 std::io::Write;

fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
build_util::build_notifications()?;
Expand All @@ -15,5 +16,24 @@ fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
idol::server::ServerStyle::InOrder,
)?;

let cfg = build_stm32xx_sys::SysConfig::load()?;

const EXTI_FEATURE: &str = "exti";

if build_util::has_feature(EXTI_FEATURE) {
let out_dir = build_util::out_dir();
let dest_path = out_dir.join("exti_config.rs");

let mut out = std::fs::File::create(dest_path)?;

let generated = cfg.generate_exti_config()?;
writeln!(out, "{generated}")?;
} else if cfg.needs_exti() {
return Err(format!(
"the \"drv-stm32xx-sys/{EXTI_FEATURE}\" feature is required in order to \
configure GPIO pin interrupts"
).into());
}

Ok(())
}
32 changes: 3 additions & 29 deletions drv/stm32xx-sys/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -410,9 +410,9 @@ impl idl::InOrderSysImpl for ServerImpl<'_> {

for (i, entry) in generated::EXTI_DISPATCH_TABLE.iter().enumerate() {
// Only use populated rows in the table
if let Some((_, tid, mask)) = entry {
if let Some((_, task, mask)) = entry {
// Ignore anything assigned to another task
if tid.index() == rm.sender.index() {
if task.index() == rm.sender.index() {
// Apply disable first so that including the same
// thing in both masks is a no-op.
if mask & disable_mask != 0 {
Expand Down Expand Up @@ -870,31 +870,5 @@ mod idl {
mod generated {
use super::*;

// TODO: lol jk not really generated, written by hand
pub(super) static EXTI_DISPATCH_TABLE: [Option<(Port, TaskId, u32)>; 16] = [
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
// 14 - HACK HACK! This is PI14 on Gimlet for power system debugging
Some((
Port::I,
TaskId::for_index_and_gen(
hubris_num_tasks::Task::power as usize,
userlib::Generation::ZERO,
),
1 << 1,
)),
None,
];
include!(concat!(env!("OUT_DIR"), "/exti_config.rs"));
}

0 comments on commit d8fdfe7

Please sign in to comment.