stm32-data/stm32-data-gen/src/interrupts.rs
2024-01-03 18:17:17 +08:00

526 lines
19 KiB
Rust

use std::collections::{HashMap, HashSet};
use log::*;
use crate::chips::ChipGroup;
use crate::regex;
mod xml {
use serde::Deserialize;
#[derive(Debug, Deserialize, PartialEq)]
pub struct Ip {
#[serde(rename = "Name")]
pub name: String,
#[serde(rename = "Version")]
pub version: String,
#[serde(rename = "RefParameter")]
pub ref_parameters: Vec<RefParameter>,
}
#[derive(Debug, Deserialize, PartialEq)]
pub struct RefParameter {
#[serde(rename = "Name")]
pub name: String,
#[serde(rename = "PossibleValue", default)]
pub possible_values: Vec<PossibleValue>,
}
#[derive(Debug, Deserialize, PartialEq)]
pub struct PossibleValue {
#[serde(rename = "Comment")]
pub comment: String,
#[serde(rename = "Value")]
pub value: String,
}
}
#[derive(Debug)]
pub struct ChipInterrupts {
// (nvic name, nvic version) => [cursed unparsed interrupt string]
irqs: HashMap<(String, String), Vec<String>>,
}
impl ChipInterrupts {
pub fn parse() -> anyhow::Result<Self> {
let mut irqs = HashMap::new();
let mut files: Vec<_> = glob::glob("sources/cubedb/mcu/IP/NVIC*_Modes.xml")?
.map(Result::unwrap)
.filter(|file| !file.to_string_lossy().contains("STM32MP1"))
.collect();
files.sort();
for f in files {
trace!("parsing {f:?}");
let file = std::fs::read_to_string(&f)?;
let parsed: xml::Ip = quick_xml::de::from_str(&file)?;
let strings: Vec<_> = parsed
.ref_parameters
.into_iter()
.filter(|param| param.name == "IRQn")
.flat_map(|param| param.possible_values)
// F3 can remap USB IRQs, ignore them.
.filter(|irq| !parsed.version.starts_with("STM32F3") || !irq.comment.contains("remap"))
.map(|irq| irq.value)
.collect();
irqs.insert((parsed.name, parsed.version), strings);
}
Ok(Self { irqs })
}
pub(crate) fn process(
&self,
core: &mut stm32_data_serde::chip::Core,
chip_name: &str,
h: &crate::header::ParsedHeader,
group: &ChipGroup,
) {
trace!("parsing interrupts for chip {} core {}", chip_name, core.name);
// =================== Populate nvic_priority_bits
// With the current data sources, this value is always either 2 or 4, and never resolves to None
let header_defines = h.get_defines(&core.name);
core.nvic_priority_bits = header_defines.0.get("__NVIC_PRIO_BITS").map(|bits| *bits as u8);
// =================== Populate core interrupts
let mut header_irqs = h.get_interrupts(&core.name).clone();
// F100xE MISC_REMAP remaps some DMA IRQs, so ST decided to give two names
// to the same IRQ number.
if chip_name.starts_with("STM32F100") {
header_irqs.remove("DMA2_Channel4_5");
}
core.interrupts = header_irqs
.iter()
.map(|(k, v)| stm32_data_serde::chip::core::Interrupt {
name: k.clone(),
number: *v,
})
.collect();
core.interrupts.sort_unstable_by_key(|x| x.number);
// =================== Populate peripheral interrupts
let want_nvic_name = pick_nvic(chip_name, &core.name);
let chip_nvic = group
.ips
.values()
.find(|x| x.name == want_nvic_name)
.ok_or_else(|| format!("couldn't find nvic. chip_name={chip_name} want_nvic_name={want_nvic_name}"))
.unwrap();
let nvic_strings = self
.irqs
.get(&(chip_nvic.name.clone(), chip_nvic.version.clone()))
.unwrap();
// peripheral -> signal -> interrupts
let mut chip_signals = HashMap::<String, HashMap<String, HashSet<String>>>::new();
let exists_irq: HashSet<String> = core.interrupts.iter().map(|i| i.name.clone()).collect();
for i in &exists_irq {
trace!(" irq in header: {i}");
}
for nvic_string in nvic_strings {
trace!(" irq={nvic_string:?}");
let parts = {
let mut iter = nvic_string.split(':');
let parts = [(); 5].map(|_| iter.next().unwrap());
assert!(iter.next().is_none());
parts
};
let mut name = parts[0].strip_suffix("_IRQn").unwrap().to_string();
// Fix typo in STM32Lxx and L083 devices
let contains_rng = || parts[2..].iter().flat_map(|x| x.split(',')).any(|x| x == "RNG");
if name == "AES_RNG_LPUART1" && !contains_rng() {
name = "AES_LPUART1".to_string()
}
// More typos
let name = name.replace("USAR11", "USART11");
trace!(" name={name}");
// Skip interrupts that don't exist.
// This is needed because NVIC files are shared between many chips.
static EQUIVALENT_IRQS: &[(&str, &[&str])] = &[
("HASH_RNG", &["RNG"]),
("USB_HP_CAN_TX", &["CAN_TX"]),
("USB_LP_CAN_RX0", &["CAN_RX0"]),
("TIM6_DAC", &["TIM6", "DAC"]),
];
let mut header_name = name.clone();
if !exists_irq.contains(&name) {
let &(_, eq_irqs) = EQUIVALENT_IRQS
.iter()
.find(|(irq, _)| irq == &name)
.unwrap_or(&("", &[]));
let Some(new_name) = eq_irqs.iter().find(|i| exists_irq.contains(**i)) else {
trace!(" irq missing in C header, ignoring");
continue;
};
header_name = new_name.to_string();
}
// Flags.
// Y
// unknown, it's in all of them
// H3, nHS
// ???
// 2V, 3V, nV, 2V1
// unknown, it has to do with the fact the irq is shared among N peripehrals
// DMA, DMAL0, DMAF0, DMAL0_DMAMUX, DMAF0_DMAMUX
// special format for DMA
// DFSDM
// special format for DFSDM
// EXTI
// special format for EXTI
let flags: Vec<_> = parts[1].split(',').collect();
// F100xE MISC_REMAP remaps some DMA IRQs, so ST decided to give two names
// to the same IRQ number.
if chip_nvic.version == "STM32F100E" && name == "DMA2_Channel4_5" {
continue;
}
//not supported
if name == "LSECSSD" {
continue;
}
let mut interrupt_signals = HashSet::<(String, String)>::new();
if [
"NonMaskableInt",
"HardFault",
"MemoryManagement",
"BusFault",
"UsageFault",
"SVCall",
"DebugMonitor",
"PendSV",
"SysTick",
]
.contains(&name.as_str())
{
// pass
} else if flags
.iter()
.any(|flag| ["DMA", "DMAL0", "DMAF0", "DMAL0_DMAMUX", "DMAF0_DMAMUX"].contains(flag))
{
let mut dmas_iter = parts[3].split(',');
let mut chans_iter = parts[4].split(';');
for (dma, chan) in std::iter::zip(&mut dmas_iter, &mut chans_iter) {
let range = {
let mut ch = chan.split(',');
let ch_from: usize = ch.next().unwrap().parse().unwrap();
let ch_to = match ch.next() {
Some(ch_to) => ch_to.parse().unwrap(),
None => ch_from,
};
assert!(ch.next().is_none());
ch_from..=ch_to
};
for ch in range {
interrupt_signals.insert((dma.to_string(), format!("CH{ch}")));
}
}
assert!(dmas_iter.next().is_none());
assert!(chans_iter.next().is_none());
} else if name == "DMAMUX1" || name == "DMAMUX1_S" || name == "DMAMUX_OVR" || name == "DMAMUX1_OVR" {
interrupt_signals.insert(("DMAMUX1".to_string(), "OVR".to_string()));
} else if name == "DMAMUX2_OVR" {
interrupt_signals.insert(("DMAMUX2".to_string(), "OVR".to_string()));
} else if flags.contains(&"DMAMUX") {
panic!("should've been handled above");
} else if flags.contains(&"EXTI") {
for signal in parts[2].split(',') {
interrupt_signals.insert(("EXTI".to_string(), signal.to_string()));
}
} else if name == "FLASH" {
interrupt_signals.insert(("FLASH".to_string(), "GLOBAL".to_string()));
} else if name == "CRS" {
interrupt_signals.insert(("RCC".to_string(), "CRS".to_string()));
} else if name == "RCC" {
interrupt_signals.insert(("RCC".to_string(), "GLOBAL".to_string()));
} else {
if parts[2].is_empty() {
trace!(" skipping because parts[2].is_empty()");
continue;
}
let peri_names: Vec<_> = parts[2]
.split(',')
.map(|x| if x == "USB_DRD_FS" { "USB" } else { x })
.map(ToString::to_string)
.collect();
trace!(" peri_names: {peri_names:?}");
let name2 = {
if name == "USBWakeUp" || name == "USBWakeUp_RMP" {
"USB_WKUP"
} else {
name.strip_suffix("_S").unwrap_or(&name)
}
};
let mut peri_signals: HashMap<_, _> = peri_names
.iter()
.map(|name| (name.clone(), Vec::<String>::new()))
.collect();
let mut curr_peris = Vec::new();
if peri_names.len() == 1 {
curr_peris = peri_names.clone();
}
// Parse IRQ interrupt_signals from the IRQ name.
for part in tokenize_name(name2) {
trace!(" part={part}");
let part = {
if part == "TAMPER" {
"TAMP".to_string()
} else {
part
}
};
if part == "LSECSS" {
interrupt_signals.insert(("RCC".to_string(), "LSECSS".to_string()));
} else if part == "CSS" {
interrupt_signals.insert(("RCC".to_string(), "CSS".to_string()));
} else if part == "LSE" {
interrupt_signals.insert(("RCC".to_string(), "LSE".to_string()));
} else if part == "CRS" {
interrupt_signals.insert(("RCC".to_string(), "CRS".to_string()));
} else {
let pp = match_peris(&peri_names, &part);
trace!(" part={part}, pp={pp:?}");
if !pp.is_empty() {
curr_peris = pp;
} else {
assert!(!curr_peris.is_empty());
for p in &curr_peris {
peri_signals.entry(p.clone()).or_default().push(part.clone());
}
}
}
}
for (p, mut ss) in peri_signals.into_iter() {
let known = valid_signals(&p);
// If we have no interrupt_signals for the peri, assume it's "global" so assign it all known ones
if ss.is_empty() {
if p.starts_with("COMP") {
ss = vec!["WKUP".to_string()];
} else {
ss = known.clone();
}
}
for s in ss {
if !known.contains(&s.clone()) {
panic!("Unknown signal {s} for peri {p}, known={known:?}, parts={parts:?}");
}
trace!(" signal: {} {}", p, s);
interrupt_signals.insert((p.clone(), s));
}
}
}
for (p, s) in interrupt_signals {
let signals = chip_signals.entry(p).or_default();
let irqs = signals.entry(s).or_default();
irqs.insert(header_name.clone());
}
}
for p in &mut core.peripherals {
if let Some(signals) = chip_signals.get(&p.name) {
let mut all_irqs: Vec<stm32_data_serde::chip::core::peripheral::Interrupt> = Vec::new();
// remove duplicates
let globals = signals.get("GLOBAL").cloned().unwrap_or_default();
for (signal, irqs) in signals {
let mut irqs = irqs.clone();
// If there's a duplicate irqs in a signal other than "global", keep the non-global one.
if irqs.len() != 1 && signal != "GLOBAL" {
irqs.retain(|irq| !globals.contains(irq));
}
// If there's still duplicate irqs, keep the one that doesn't match the peri name.
if irqs.len() != 1 && signal != "GLOBAL" {
irqs.retain(|irq| irq != &p.name);
}
if irqs.len() != 1 {
panic!(
"dup irqs on chip {:?} nvic {:?} peri {} signal {}: {:?}",
chip_name, chip_nvic.version, p.name, signal, irqs
);
}
for irq in irqs {
all_irqs.push(stm32_data_serde::chip::core::peripheral::Interrupt {
signal: signal.clone(),
interrupt: irq,
})
}
}
all_irqs.sort_by_key(|x| (x.signal.clone(), x.interrupt.clone()));
all_irqs.dedup_by_key(|x| (x.signal.clone(), x.interrupt.clone()));
p.interrupts = Some(all_irqs);
}
}
}
}
fn tokenize_name(name: &str) -> Vec<String> {
// Treat IRQ names are "tokens" separated by `_`, except some tokens
// contain `_` themselves, such as `C1_RX`.
let r =
regex!(r"(SPDIF_RX|EP\d+_(IN|OUT)|OTG_FS|OTG_HS|USB_DRD_FS|USB_FS|C1_RX|C1_TX|C2_RX|C2_TX|[A-Z0-9]+(_\d+)*)_*");
let name = name.to_ascii_uppercase();
r.captures_iter(&name)
.map(|cap| cap.get(1).unwrap().as_str().to_string())
.collect()
}
fn match_peris(peris: &[String], name: &str) -> Vec<String> {
const PERI_OVERRIDE: &[(&str, &[&str])] = &[
("USB_FS", &["USB"]),
("USB_DRD_FS", &["USB"]),
("OTG_HS", &["USB_OTG_HS"]),
("OTG_FS", &["USB_OTG_FS"]),
("USB", &["USB_DRD_FS"]),
("USB_DRD_FS", &["USB"]),
("UCPD1_2", &["UCPD1", "UCPD2"]),
("ADC1", &["ADC"]),
("CEC", &["HDMI_CEC"]),
("SPDIF_RX", &["SPDIFRX1", "SPDIFRX"]),
("CAN1", &["CAN"]),
("TEMP", &["TEMPSENS"]),
("DSI", &["DSIHOST"]),
("HRTIM1", &["HRTIM"]),
("GTZC", &["GTZC_S"]),
("TZIC", &["GTZC_S"]),
];
let peri_override: HashMap<_, _> = PERI_OVERRIDE.iter().copied().collect();
if let Some(over) = peri_override.get(name) {
let mut res = Vec::new();
for p in *over {
if peris.contains(&p.to_string()) {
res.push(p.to_string());
}
}
if !res.is_empty() {
return res;
}
}
let mut name = name;
let mut res = Vec::new();
if let Some(m) = regex!(r"^(I2C|[A-Z]+)(\d+(_\d+)*)$").captures(name) {
name = m.get(1).unwrap().as_str();
for n in m.get(2).unwrap().as_str().split('_') {
let p = format!("{name}{n}");
if !peris.contains(&p) {
return Vec::new();
}
res.push(p);
}
} else {
for p in peris {
if p == name || { p.starts_with(name) && regex!(r"^\d+$").is_match(p.strip_prefix(name).unwrap_or(p)) } {
res.push(p.to_string());
}
}
}
res
}
fn valid_signals(peri: &str) -> Vec<String> {
const IRQ_SIGNALS_MAP: &[(&str, &[&str])] = &[
("CAN", &["TX", "RX0", "RX1", "SCE"]),
("FDCAN", &["IT0", "IT1", "CAL"]),
("I2C", &["ER", "EV"]),
("I3C", &["ER", "EV"]),
("FMPI2C", &["ER", "EV"]),
("TIM", &["BRK", "UP", "TRG", "COM", "CC"]),
// ("HRTIM", &["Master", "TIMA", "TIMB", "TIMC", "TIMD", "TIME", "TIMF"]),
("RTC", &["ALARM", "WKUP", "TAMP", "STAMP", "SSRU"]),
("SUBGHZ", &["RADIO"]),
("IPCC", &["C1_RX", "C1_TX", "C2_RX", "C2_TX"]),
(
"HRTIM",
&["MASTER", "TIMA", "TIMB", "TIMC", "TIMD", "TIME", "TIMF", "FLT"],
),
("COMP", &["WKUP", "ACQ"]),
("RCC", &["RCC", "CRS"]),
("MDIOS", &["GLOBAL", "WKUP"]),
("ETH", &["GLOBAL", "WKUP"]),
("LTDC", &["GLOBAL", "ER"]),
(
"DFSDM",
&["FLT0", "FLT1", "FLT2", "FLT3", "FLT4", "FLT5", "FLT6", "FLT7"],
),
("MDF", &["FLT0", "FLT1", "FLT2", "FLT3", "FLT4", "FLT5", "FLT6", "FLT7"]),
("PWR", &["S3WU", "WKUP"]),
("GTZC", &["GLOBAL", "ILA"]),
("WWDG", &["GLOBAL", "RST"]),
("USB_OTG_FS", &["GLOBAL", "EP1_OUT", "EP1_IN", "WKUP"]),
("USB_OTG_HS", &["GLOBAL", "EP1_OUT", "EP1_IN", "WKUP"]),
("USB", &["LP", "HP", "WKUP"]),
("GPU2D", &["ER"]),
];
for (prefix, signals) in IRQ_SIGNALS_MAP {
if peri.starts_with(prefix) {
return signals.iter().map(ToString::to_string).collect();
}
}
vec!["GLOBAL".to_string()]
}
fn pick_nvic(chip_name: &str, core_name: &str) -> String {
// Most chips have a single NVIC, named "NVIC"
let mut res = "NVIC";
// Exception 1: Multicore: NVIC1 is the first core, NVIC2 is the second. We have to pick the right one.
if ["H745", "H747", "H755", "H757", "WL54", "WL55"].contains(&&chip_name[5..9]) {
if core_name == "cm7" {
res = "NVIC1";
} else {
res = "NVIC2"
}
}
if &chip_name[5..8] == "WL5" {
if core_name == "cm4" {
res = "NVIC1";
} else {
res = "NVIC2"
}
}
// Exception 2: TrustZone: NVIC1 is Secure mode, NVIC2 is NonSecure mode. For now, we pick the NonSecure one.
if ["L5", "U5"].contains(&&chip_name[5..7]) {
res = "NVIC2"
}
if ["H56", "H57", "WBA"].contains(&&chip_name[5..8]) {
res = "NVIC2"
}
res.to_string()
}