diff --git a/.gitignore b/.gitignore index 2aac4e7..c0e25a2 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,7 @@ /tmp .idea/ transform*.yaml -__pycache__ \ No newline at end of file +__pycache__ + +target/ +Cargo.lock diff --git a/.vscode/settings.json b/.vscode/settings.json index dc200f7..60a0208 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,13 +1,5 @@ { - "rust-analyzer.assist.importMergeBehavior": "last", "editor.formatOnSave": true, - "rust-analyzer.cargo.allFeatures": false, - "rust-analyzer.checkOnSave.allFeatures": false, - "rust-analyzer.checkOnSave.allTargets": false, - "rust-analyzer.cargo.target": "thumbv7em-none-eabihf", - "rust-analyzer.checkOnSave.target": "thumbv7em-none-eabihf", - "rust-analyzer.procMacro.enable": true, - "rust-analyzer.cargo.loadOutDirsFromCheck": true, "files.watcherExclude": { "**/.git/objects/**": true, "**/.git/subtree-cache/**": true, diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..c4e791b --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "stm32-data" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +default = ["rayon"] +rayon = ["dep:rayon"] + +[dependencies] +anyhow = "1.0.66" +glob = "0.3.0" +num = "0.4.0" +quick-xml = { version = "0.26.0", features = ["serialize"] } +regex = "1.6.0" +serde = { version = "1.0.147", features = ["derive"] } +serde_yaml = "0.9.14" +chiptool = { git = "https://github.com/embassy-rs/chiptool", rev = "28ffa8a19d84914089547f52900ffb5877a5dc23" } +serde_json = "1.0.87" +rayon = { version = "1.5.3", optional = true } +stm32-data-serde = { version = "0.1.0", path = "stm32-data-serde" } +ref_thread_local = "0.1.1" + +# Optimize for dev experience: shortest "build+run" time after making a small change. +[profile.release] +debug = true +incremental = true +panic = 'abort' +opt-level = 2 \ No newline at end of file diff --git a/README.md b/README.md index d9efcd5..aee0538 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ In order to run the generator, you will need to install the following tools: ## Generating the YAMLs - Run `./d download-all` -- Run `python3 -m stm32data` +- Run `cargo run --release` This generates all the YAMLs in `data/` except those in `data/registers/`, which are manually extracted and cleaned up. @@ -134,7 +134,7 @@ such mapping assignes the `rcc_g0/RCC` register block to the `RCC` peripheral in ## Peripheral mapping (perimap) -The python scripts have a map to match peripherals to the right version in all chips, the [perimap](https://github.com/embassy-rs/stm32-data/blob/main/stm32data/__main__.py#L84). +The script has a map to match peripherals to the right version in all chips, the [perimap](https://github.com/embassy-rs/stm32-data/blob/main/src/chips.rs#L109). When parsing a chip, for each peripheral a "key" string is constructed using this format: `CHIP:PERIPHERAL_NAME:IP_NAME:IP_VERSION`, where: - `CHIP`: full chip name, for example `STM32L443CC` diff --git a/header_map.yaml b/header_map.yaml index c4b54fa..1339331 100644 --- a/header_map.yaml +++ b/header_map.yaml @@ -52,7 +52,7 @@ STM32F407xx: STM32F407VG, STM32F407VE, STM32F407ZG, STM32F407ZE, STM32F407IG, ST STM32F417xx: STM32F417VG, STM32F417VE, STM32F417ZG, STM32F417ZE, STM32F417IG, STM32F417IE STM32F427xx: STM32F427VG, STM32F427VI, STM32F427ZG, STM32F427ZI, STM32F427IG, STM32F427II STM32F437xx: STM32F437VG, STM32F437VI, STM32F437ZG, STM32F437ZI, STM32F437IG, STM32F437II -STM32F429xx: STM32F429VG, STM32F429VI, STM32F429ZG, STM32F429ZI, STM32F429BG, STM32F429BI, STM32F429NG, STM32F439NI, STM32F429IG, STM32F429II +STM32F429xx: STM32F429VG, STM32F429VI, STM32F429ZG, STM32F429ZI, STM32F429BG, STM32F429BI, STM32F429NG, STM32F429IG, STM32F429II STM32F439xx: STM32F439VG, STM32F439VI, STM32F439ZG, STM32F439ZI, STM32F439BG, STM32F439BI, STM32F439NG, STM32F439NI, STM32F439IG, STM32F439II STM32F401xC: STM32F401CB, STM32F401CC, STM32F401RB, STM32F401RC, STM32F401VB, STM32F401VC STM32F401xE: STM32F401CD, STM32F401RD, STM32F401VD, STM32F401CE, STM32F401RE, STM32F401VE @@ -69,7 +69,7 @@ STM32F412Vx: STM32F412VET, STM32F412VGT, STM32F412VEH, STM32F412VGH STM32F412Rx: STM32F412RET, STM32F412RGT, STM32F412REY, STM32F412RGY STM32F413xx: STM32F413CH, STM32F413MH, STM32F413RH, STM32F413VH, STM32F413ZH, STM32F413CG, STM32F413MG, STM32F413RG, STM32F413VG, STM32F413ZG STM32F423xx: STM32F423CH, STM32F423RH, STM32F423VH, STM32F423ZH -STM32F756xx: STM32F756VG, STM32F756ZG, STM32F756ZG, STM32F756IG, STM32F756BG, STM32F756NG +STM32F756xx: STM32F756VG, STM32F756ZG, STM32F756IG, STM32F756BG, STM32F756NG STM32F746xx: STM32F746VE, STM32F746VG, STM32F746ZE, STM32F746ZG, STM32F746IE, STM32F746IG, STM32F746BE, STM32F746BG, STM32F746NE, STM32F746NG STM32F745xx: STM32F745VE, STM32F745VG, STM32F745ZG, STM32F745ZE, STM32F745IE, STM32F745IG STM32F765xx: STM32F765BI, STM32F765BG, STM32F765NI, STM32F765NG, STM32F765II, STM32F765IG, STM32F765ZI, STM32F765ZG, STM32F765VI, STM32F765VG @@ -87,7 +87,6 @@ STM32L010x4: STM32L010K4, STM32L010F4 STM32L010x6: STM32L010C6 STM32L010x8: STM32L010K8, STM32L010R8 STM32L010xB: STM32L010RB -STM32L011xx: STM32L031C6, STM32L031E6, STM32L031F6, STM32L031G6, STM32L031K6 STM32L021xx: STM32L021D4, STM32L021F4, STM32L021G4, STM32L021K4 STM32L031xx: STM32L031C6, STM32L031E6, STM32L031F6, STM32L031G6, STM32L031K6 STM32L041xx: STM32L041C6, STM32L041K6, STM32L041G6, STM32L041F6, STM32L041E6 @@ -164,7 +163,7 @@ STM32H7A3xxQ: STM32H7A3QIY6Q, STM32H7A3IIK6Q, STM32H7A3IIT6Q, STM32H7A3LIH6Q, ST STM32H7B3xx: STM32H7B3IIK6, STM32H7B3IIT6, STM32H7B3NIH6, STM32H7B3RIT6, STM32H7B3VIH6, STM32H7B3VIT6, STM32H7B3ZIT6 STM32H7B3xxQ: STM32H7B3QIY6Q, STM32H7B3IIK6Q, STM32H7B3IIT6Q, STM32H7B3LIH6Q, STM32H7B3VIH6Q, STM32H7B3VIT6Q, STM32H7B3AII6Q, STM32H7B3ZIT6Q STM32H735xx: STM32H735AGI6, STM32H735IGK6, STM32H735RGV6, STM32H735VGT6, STM32H735VGY6, STM32H735ZGT6 -STM32H733xx: STM32H733VGH6, STM32H733VGT6, STM32H733ZGI6, STM32H733ZGT6, +STM32H733xx: STM32H733VGH6, STM32H733VGT6, STM32H733ZGI6, STM32H733ZGT6 STM32H730xx: STM32H730VBH6, STM32H730VBT6, STM32H730ZBT6, STM32H730ZBI6 STM32H730xxQ: STM32H730IBT6Q, STM32H730ABI6Q, STM32H730IBK6Q STM32H725xx: STM32H725AGI6, STM32H725IGK6, STM32H725IGT6, STM32H725RGV6, STM32H725VGT6, STM32H725VGY6, STM32H725ZGT6, STM32H725REV6, SM32H725VET6, STM32H725ZET6, STM32H725AEI6, STM32H725IET6, STM32H725IEK6 diff --git a/src/chips.rs b/src/chips.rs new file mode 100644 index 0000000..04166af --- /dev/null +++ b/src/chips.rs @@ -0,0 +1,1106 @@ +use std::collections::hash_map::Entry; +use std::collections::{HashMap, HashSet}; + +use super::*; + +mod xml { + use serde::Deserialize; + + #[derive(Clone, Debug, Deserialize, PartialEq)] + pub struct Mcu { + #[serde(rename = "Family")] + pub family: String, + #[serde(rename = "Line")] + pub line: String, + #[serde(rename = "Die")] + pub die: String, + #[serde(rename = "RefName")] + pub ref_name: String, + #[serde(rename = "Package")] + pub package: String, + #[serde(rename = "Core")] + pub cores: Vec, + #[serde(rename = "Ram")] + pub rams: Vec, + #[serde(rename = "Flash")] + pub flashs: Vec, + #[serde(rename = "IP")] + pub ips: Vec, + #[serde(rename = "Pin")] + pub pins: Vec, + } + + #[derive(Clone, Debug, Deserialize, PartialEq)] + pub struct Pin { + #[serde(rename = "Name")] + pub name: String, + #[serde(rename = "Signal", default)] + pub signals: Vec, + } + + #[derive(Clone, Debug, Deserialize, PartialEq)] + pub struct PinSignal { + #[serde(rename = "Name")] + pub name: String, + } + + #[derive(Clone, Debug, Deserialize, PartialEq)] + pub struct Ip { + #[serde(rename = "InstanceName")] + pub instance_name: String, + #[serde(rename = "Name")] + pub name: String, + #[serde(rename = "Version")] + pub version: String, + } +} + +pub struct Chip { + flash: u32, + ram: u32, + group_idx: usize, + packages: Vec, +} + +pub struct ChipGroup { + chip_names: Vec, + xml: xml::Mcu, + ips: HashMap, + pins: HashMap, + family: Option, + line: Option, + die: Option, +} + +fn chip_name_from_package_name(x: &str) -> String { + let regexes = [ + (regex!("^(STM32L1....).x([AX])$"), "$1-$2"), + (regex!("^(STM32G0....).xN$"), "$1"), + (regex!("^(STM32F412..).xP$"), "$1"), + (regex!("^(STM32L4....).xP$"), "$1"), + (regex!("^(STM32WB....).x[AE]$"), "$1"), + (regex!("^(STM32G0....).xN$"), "$1"), + (regex!("^(STM32L5....).x[PQ]$"), "$1"), + (regex!("^(STM32L0....).xS$"), "$1"), + (regex!("^(STM32H7....).xQ$"), "$1"), + (regex!("^(STM32U5....).xQ$"), "$1"), + (regex!("^(STM32......).x$"), "$1"), + ]; + + regexes + .iter() + .find_map(|(a, b)| { + a.captures(x).map(|cap| { + let mut res = String::new(); + cap.expand(b, &mut res); + res + }) + }) + .unwrap_or_else(|| panic!("bad name: {x}")) +} + +struct PeriMatcher { + regexes: Vec<(regex::Regex, (&'static str, &'static str, &'static str))>, + cached: HashMap>, +} + +impl PeriMatcher { + fn new() -> Self { + const PERIMAP: &[(&str, (&str, &str, &str))] = &[ + (".*:USART:sci2_v1_1", ("usart", "v1", "USART")), + (".*:USART:sci2_v1_2_F1", ("usart", "v1", "USART")), + (".*:USART:sci2_v1_2", ("usart", "v1", "USART")), + (".*:USART:sci2_v2_0", ("usart", "v2", "USART")), + (".*:USART:sci2_v2_1", ("usart", "v2", "USART")), + (".*:USART:sci2_v2_2", ("usart", "v2", "USART")), + (".*:USART:sci3_v1_0", ("usart", "v2", "USART")), + (".*:USART:sci3_v1_1", ("usart", "v2", "USART")), + (".*:USART:sci3_v1_2", ("usart", "v2", "USART")), + (".*:USART:sci3_v2_0", ("usart", "v2", "USART")), + (".*:USART:sci3_v2_1", ("usart", "v2", "USART")), + (".*:UART:sci2_v1_2_F4", ("usart", "v1", "USART")), + (".*:UART:sci2_v2_1", ("usart", "v2", "USART")), + (".*:UART:sci2_v3_0", ("usart", "v2", "USART")), + (".*:UART:sci2_v3_1", ("usart", "v2", "USART")), + (".*:LPUART:sci3_v1_1", ("lpuart", "v1", "LPUART")), + (".*:LPUART:sci3_v1_2", ("lpuart", "v2", "LPUART")), + (".*:LPUART:sci3_v1_3", ("lpuart", "v2", "LPUART")), + (".*:LPUART:sci3_v1_4", ("lpuart", "v2", "LPUART")), + (".*:RNG:rng1_v1_1", ("rng", "v1", "RNG")), + (".*:RNG:rng1_v2_0", ("rng", "v1", "RNG")), + (".*:RNG:rng1_v2_1", ("rng", "v1", "RNG")), + (".*:RNG:rng1_v3_1", ("rng", "v1", "RNG")), + (".*:SPI:spi2_v1_4", ("spi", "f1", "SPI")), + (".*:SPI:spi2s1_v2_1", ("spi", "v1", "SPI")), + (".*:SPI:spi2s1_v2_2", ("spi", "v1", "SPI")), + (".*:SPI:spi2s1_v3_2", ("spi", "v2", "SPI")), + (".*:SPI:spi2s1_v3_3", ("spi", "v2", "SPI")), + (".*:SPI:spi2s1_v3_5", ("spi", "v2", "SPI")), + (".*:SUBGHZSPI:.*", ("spi", "v2", "SPI")), + (".*:SPI:spi2s1_v3_1", ("spi", "v2", "SPI")), + (".*:SPI:spi2s2_v1_1", ("spi", "v3", "SPI")), + (".*:SPI:spi2s2_v1_0", ("spi", "v3", "SPI")), + (".*:SPI:spi2s3_v1_1", ("spi", "v4", "SPI")), + (".*:I2C:i2c1_v1_5", ("i2c", "v1", "I2C")), + (".*:I2C:i2c2_v1_1", ("i2c", "v2", "I2C")), + (".*:I2C:i2c2_v1_1F7", ("i2c", "v2", "I2C")), + (".*:I2C:i2c2_v1_1U5", ("i2c", "v2", "I2C")), + (".*:DAC:dacif_v1_1", ("dac", "v1", "DAC")), + (".*:DAC:dacif_v1_1F1", ("dac", "v1", "DAC")), + (".*:DAC:dacif_v2_0", ("dac", "v2", "DAC")), + (".*:DAC:dacif_v3_0", ("dac", "v2", "DAC")), + (".*:ADC:aditf_v2_5F1", ("adc", "f1", "ADC")), + (".*:ADC:aditf4_v1_1", ("adc", "v1", "ADC")), + (".*:ADC:aditf2_v1_1", ("adc", "v2", "ADC")), + (".*:ADC:aditf5_v2_0", ("adc", "v3", "ADC")), + (".*:ADC:aditf5_v3_0", ("adc", "v4", "ADC")), + ("STM32G0.*:ADC:.*", ("adc", "g0", "ADC")), + ("STM32G0.*:ADC_COMMON:.*", ("adccommon", "v3", "ADC_COMMON")), + (".*:ADC_COMMON:aditf2_v1_1", ("adccommon", "v2", "ADC_COMMON")), + (".*:ADC_COMMON:aditf5_v2_0", ("adccommon", "v3", "ADC_COMMON")), + (".*:ADC_COMMON:aditf4_v3_0_WL", ("adccommon", "v3", "ADC_COMMON")), + ("STM32H7.*:ADC_COMMON:.*", ("adccommon", "v4", "ADC_COMMON")), + ("STM32H7.*:ADC3_COMMON:.*", ("adccommon", "v4", "ADC_COMMON")), + (".*:DCMI:.*", ("dcmi", "v1", "DCMI")), + ("STM32F0.*:SYSCFG:.*", ("syscfg", "f0", "SYSCFG")), + ("STM32F2.*:SYSCFG:.*", ("syscfg", "f2", "SYSCFG")), + ("STM32F3.*:SYSCFG:.*", ("syscfg", "f3", "SYSCFG")), + ("STM32F4.*:SYSCFG:.*", ("syscfg", "f4", "SYSCFG")), + ("STM32F7.*:SYSCFG:.*", ("syscfg", "f7", "SYSCFG")), + ("STM32L0.*:SYSCFG:.*", ("syscfg", "l0", "SYSCFG")), + ("STM32L1.*:SYSCFG:.*", ("syscfg", "l1", "SYSCFG")), + ("STM32L4.*:SYSCFG:.*", ("syscfg", "l4", "SYSCFG")), + ("STM32L5.*:SYSCFG:.*", ("syscfg", "l5", "SYSCFG")), + ("STM32G0.*:SYSCFG:.*", ("syscfg", "g0", "SYSCFG")), + ("STM32G4.*:SYSCFG:.*", ("syscfg", "g4", "SYSCFG")), + ("STM32H7.*:SYSCFG:.*", ("syscfg", "h7", "SYSCFG")), + ("STM32U5.*:SYSCFG:.*", ("syscfg", "u5", "SYSCFG")), + ("STM32WB.*:SYSCFG:.*", ("syscfg", "wb", "SYSCFG")), + ("STM32WL5.*:SYSCFG:.*", ("syscfg", "wl5", "SYSCFG")), + ("STM32WLE.*:SYSCFG:.*", ("syscfg", "wle", "SYSCFG")), + (".*:IWDG:iwdg1_v1_1", ("iwdg", "v1", "IWDG")), + (".*:IWDG:iwdg1_v2_0", ("iwdg", "v2", "IWDG")), + (".*:WWDG:wwdg1_v1_0", ("wwdg", "v1", "WWDG")), + (".*:JPEG:jpeg1_v1_0", ("jpeg", "v1", "JPEG")), + (".*:LPTIM:F7_lptimer1_v1_1", ("lptim", "v1", "LPTIM")), + (".*:LTDC:lcdtft1_v1_1", ("ltdc", "v1", "LTDC")), + (".*:MDIOS:mdios1_v1_0", ("mdios", "v1", "MDIOS")), + (".*:QUADSPI:quadspi1_v1_0", ("quadspi", "v1", "QUADSPI")), + ("STM32F1.*:BKP.*", ("bkp", "v1", "BKP")), + (".*:RTC:rtc1_v1_1", ("rtc", "v1", "RTC")), + ("STM32F0.*:RTC:rtc2_.*", ("rtc", "v2f0", "RTC")), + ("STM32F2.*:RTC:rtc2_.*", ("rtc", "v2f2", "RTC")), + ("STM32F3.*:RTC:rtc2_.*", ("rtc", "v2f3", "RTC")), + ("STM32F4.*:RTC:rtc2_.*", ("rtc", "v2f4", "RTC")), + ("STM32F7.*:RTC:rtc2_.*", ("rtc", "v2f7", "RTC")), + ("STM32H7.*:RTC:rtc2_.*", ("rtc", "v2h7", "RTC")), + ("STM32L0.*:RTC:rtc2_.*", ("rtc", "v2l0", "RTC")), + ("STM32L1.*:RTC:rtc2_.*", ("rtc", "v2l1", "RTC")), + ("STM32L4.*:RTC:rtc2_.*", ("rtc", "v2l4", "RTC")), + ("STM32WB.*:RTC:rtc2_.*", ("rtc", "v2wb", "RTC")), + ("STM32U5.*:RTC:rtc2_.*", ("rtc", "v3u5", "RTC")), // Cube says v2, but it's v3 with security stuff + (".*:RTC:rtc3_v1_0", ("rtc", "v3", "RTC")), + (".*:RTC:rtc3_v1_1", ("rtc", "v3", "RTC")), + (".*:RTC:rtc3_v2_0", ("rtc", "v3", "RTC")), + (".*:RTC:rtc3_v3_0", ("rtc", "v3", "RTC")), + (".*:SAI:sai1_v1_1", ("sai", "v1", "SAI")), + (".*:SDIO:sdmmc_v1_2", ("sdmmc", "v1", "SDMMC")), + (".*:SDMMC:sdmmc_v1_3", ("sdmmc", "v1", "SDMMC")), + (".*:SPDIFRX:spdifrx1_v1_0", ("spdifrx", "v1", "SPDIFRX")), + // # USB + ("STM32F302.[BC].*:USB:.*", ("usb", "v1_x1", "USB")), + ("STM32F302.[68DE].*:USB:.*", ("usb", "v2", "USB")), + ("STM32F303.[BC].*:USB:.*", ("usb", "v1_x1", "USB")), + ("STM32F303.[DE].*:USB:.*", ("usb", "v2", "USB")), + ("STM32F373.*:USB:.*", ("usb", "v1_x2", "USB")), + ("STM32(F1|L1).*:USB:.*", ("usb", "v1_x1", "USB")), + (".*:USB:.*", ("usb", "v3", "USB")), + // # USB OTG + (".*:USB_OTG_FS:otgfs1_v1_.*", ("otgfs", "v1", "OTG_FS")), + (".*:USB_OTG_FS:otgfs1_v3_.*", ("otgfs", "v1", "OTG_FS")), + (".*:USB_OTG_HS:otghs1_v1_.*", ("otghs", "v1", "OTG_HS")), + ("STM32F0.*:RCC:.*", ("rcc", "f0", "RCC")), + ("STM32F100.*:RCC:.*", ("rcc", "f100", "RCC")), + ("STM32F10[123].*:RCC:.*", ("rcc", "f1", "RCC")), + ("STM32F10[57].*:RCC:.*", ("rcc", "f1cl", "RCC")), + ("STM32F2.*:RCC:.*", ("rcc", "f2", "RCC")), + ("STM32F3.*:RCC:.*", ("rcc", "f3", "RCC")), + ("STM32F410.*:RCC:.*", ("rcc", "f410", "RCC")), + ("STM32F4.*:RCC:.*", ("rcc", "f4", "RCC")), + ("STM32F7.*:RCC:.*", ("rcc", "f7", "RCC")), + ("STM32G0.*:RCC:.*", ("rcc", "g0", "RCC")), + ("STM32G4.*:RCC:.*", ("rcc", "g4", "RCC")), + ("STM32H7[AB].*:RCC:.*", ("rcc", "h7ab", "RCC")), + ("STM32H7.*:RCC:.*", ("rcc", "h7", "RCC")), + ("STM32L0.*:RCC:.*", ("rcc", "l0", "RCC")), + ("STM32L1.*:RCC:.*", ("rcc", "l1", "RCC")), + ("STM32L4.*:RCC:.*", ("rcc", "l4", "RCC")), + ("STM32L5.*:RCC:.*", ("rcc", "l5", "RCC")), + ("STM32U5.*:RCC:.*", ("rcc", "u5", "RCC")), + ("STM32WB.*:RCC:.*", ("rcc", "wb", "RCC")), + ("STM32WL5.*:RCC:.*", ("rcc", "wl5", "RCC")), + ("STM32WLE.*:RCC:.*", ("rcc", "wle", "RCC")), + ("STM32F1.*:SPI[1234]:.*", ("spi", "f1", "SPI")), + ("STM32F3.*:SPI[1234]:.*", ("spi", "v2", "SPI")), + ("STM32F1.*:AFIO:.*", ("afio", "f1", "AFIO")), + ("STM32L5.*:EXTI:.*", ("exti", "l5", "EXTI")), + ("STM32G0.*:EXTI:.*", ("exti", "g0", "EXTI")), + ("STM32H7.*:EXTI:.*", ("exti", "h7", "EXTI")), + ("STM32U5.*:EXTI:.*", ("exti", "u5", "EXTI")), + ("STM32WB.*:EXTI:.*", ("exti", "w", "EXTI")), + ("STM32WL5.*:EXTI:.*", ("exti", "w", "EXTI")), + ("STM32WLE.*:EXTI:.*", ("exti", "wle", "EXTI")), + (".*:EXTI:.*", ("exti", "v1", "EXTI")), + ("STM32L0.*:CRS:.*", ("crs", "l0", "CRS")), + (".*SDMMC:sdmmc2_v1_0", ("sdmmc", "v2", "SDMMC")), + ("STM32G0.*:PWR:.*", ("pwr", "g0", "PWR")), + ("STM32G4.*:PWR:.*", ("pwr", "g4", "PWR")), + ("STM32H7(42|43|53|50).*:PWR:.*", ("pwr", "h7", "PWR")), + ("STM32H7.*:PWR:.*", ("pwr", "h7smps", "PWR")), + ("STM32F2.*:PWR:.*", ("pwr", "f2", "PWR")), + ("STM32F3.*:PWR:.*", ("pwr", "f3", "PWR")), + ("STM32F4.*:PWR:.*", ("pwr", "f4", "PWR")), + ("STM32F7.*:PWR:.*", ("pwr", "f7", "PWR")), + ("STM32L1.*:PWR:.*", ("pwr", "l1", "PWR")), + ("STM32L4.*:PWR:.*", ("pwr", "l4", "PWR")), + ("STM32L5.*:PWR:.*", ("pwr", "l5", "PWR")), + ("STM32U5.*:PWR:.*", ("pwr", "u5", "PWR")), + ("STM32WL.*:PWR:.*", ("pwr", "wl5", "PWR")), + ("STM32WB.*:PWR:.*", ("pwr", "wb55", "PWR")), + ("STM32H7.*:FLASH:.*", ("flash", "h7", "FLASH")), + ("STM32F0.*:FLASH:.*", ("flash", "f0", "FLASH")), + ("STM32F1.*:FLASH:.*", ("flash", "f1", "FLASH")), + ("STM32F2.*:FLASH:.*", ("flash", "f2", "FLASH")), + ("STM32F3.*:FLASH:.*", ("flash", "f3", "FLASH")), + ("STM32F4.*:FLASH:.*", ("flash", "f4", "FLASH")), + ("STM32F7.*:FLASH:.*", ("flash", "f7", "FLASH")), + ("STM32L0[0-9]2.*:FLASH:.*", ("flash", "l0", "FLASH")), + ("STM32L1.*:FLASH:.*", ("flash", "l1", "FLASH")), + ("STM32L4.*:FLASH:.*", ("flash", "l4", "FLASH")), + ("STM32L5.*:FLASH:.*", ("flash", "l5", "FLASH")), + ("STM32U5.*:FLASH:.*", ("flash", "u5", "FLASH")), + ("STM32WB.*:FLASH:.*", ("flash", "wb", "FLASH")), + ("STM32WL.*:FLASH:.*", ("flash", "wl", "FLASH")), + ("STM32G0.*:FLASH:.*", ("flash", "g0", "FLASH")), + ("STM32F107.*:ETH:.*", ("eth", "v1a", "ETH")), + ("STM32F[24].*:ETH:.*", ("eth", "v1b", "ETH")), + ("STM32F7.*:ETH:ETH:ethermac110_v2_0", ("eth", "v1c", "ETH")), + (".*ETH:ethermac110_v3_0", ("eth", "v2", "ETH")), + (".*:FSMC:.*", ("fsmc", "v1", "FSMC")), + ("STM32H7.*:FMC:.*", ("fmc", "h7", "FMC")), + (r".*LPTIM\d.*:G0xx_lptimer1_v1_4", ("lptim", "g0", "LPTIM")), + ("STM32F1.*:TIM(1|8):.*", ("timer", "v1", "TIM_ADV")), + ("STM32F1.*:TIM(2|5):.*", ("timer", "v1", "TIM_GP16")), + ("STM32F1.*:TIM(6|7):.*", ("timer", "v1", "TIM_BASIC")), + ("STM32L0.*:TIM2:.*", ("timer", "v1", "TIM_GP16")), + ("STM32U5.*:TIM(2|3|4|5):.*", ("timer", "v1", "TIM_GP32")), + ("STM32.*:TIM(1|8|20):.*", ("timer", "v1", "TIM_ADV")), + ("STM32.*:TIM(2|5|23|24):.*", ("timer", "v1", "TIM_GP32")), + ("STM32.*:TIM(6|7|18):.*", ("timer", "v1", "TIM_BASIC")), + (r".*TIM\d.*:gptimer.*", ("timer", "v1", "TIM_GP16")), + ("STM32F0.*:DBGMCU:.*", ("dbgmcu", "f0", "DBGMCU")), + ("STM32F1.*:DBGMCU:.*", ("dbgmcu", "f1", "DBGMCU")), + ("STM32F2.*:DBGMCU:.*", ("dbgmcu", "f2", "DBGMCU")), + ("STM32F3.*:DBGMCU:.*", ("dbgmcu", "f3", "DBGMCU")), + ("STM32F4.*:DBGMCU:.*", ("dbgmcu", "f4", "DBGMCU")), + ("STM32F7.*:DBGMCU:.*", ("dbgmcu", "f7", "DBGMCU")), + ("STM32G0.*:DBGMCU:.*", ("dbgmcu", "g0", "DBGMCU")), + ("STM32G4.*:DBGMCU:.*", ("dbgmcu", "g4", "DBGMCU")), + ("STM32H7.*:DBGMCU:.*", ("dbgmcu", "h7", "DBGMCU")), + ("STM32L0.*:DBGMCU:.*", ("dbgmcu", "l0", "DBGMCU")), + ("STM32L1.*:DBGMCU:.*", ("dbgmcu", "l1", "DBGMCU")), + ("STM32L4.*:DBGMCU:.*", ("dbgmcu", "l4", "DBGMCU")), + ("STM32U5.*:DBGMCU:.*", ("dbgmcu", "u5", "DBGMCU")), + ("STM32WB.*:DBGMCU:.*", ("dbgmcu", "wb", "DBGMCU")), + ("STM32WL.*:DBGMCU:.*", ("dbgmcu", "wl", "DBGMCU")), + ("STM32F1.*:GPIO.*", ("gpio", "v1", "GPIO")), + (".*:GPIO.*", ("gpio", "v2", "GPIO")), + (".*:IPCC:v1_0", ("ipcc", "v1", "IPCC")), + (".*:DMAMUX.*", ("dmamux", "v1", "DMAMUX")), + (r".*:GPDMA\d?:.*", ("gpdma", "v1", "GPDMA")), + (r".*:BDMA\d?:.*", ("bdma", "v1", "DMA")), + ("STM32H7.*:DMA2D:DMA2D:dma2d1_v1_0", ("dma2d", "v2", "DMA2D")), + (".*:DMA2D:dma2d1_v1_0", ("dma2d", "v1", "DMA2D")), + ("STM32L4[PQRS].*:DMA.*", ("bdma", "v1", "DMA")), // L4+ + ("STM32L[04].*:DMA.*", ("bdma", "v2", "DMA")), // L0, L4 non-plus (since plus is handled above) + ("STM32F030.C.*:DMA.*", ("bdma", "v2", "DMA")), // Weird F0 + ("STM32F09.*:DMA.*", ("bdma", "v2", "DMA")), // Weird F0 + ("STM32F[247].*:DMA.*", ("dma", "v2", "DMA")), + ("STM32H7.*:DMA.*", ("dma", "v1", "DMA")), + (".*:DMA.*", ("bdma", "v1", "DMA")), + (".*:CAN:bxcan1_v1_1.*", ("can", "bxcan", "CAN")), + // # stm32F4 CRC peripheral + // # ("STM32F4*:CRC:CRC:crc_f4") + // # v1: F1, F2, F4, L1 + // # v2, adds INIT reg: F0 + // # v3, adds POL reg: F3, F7, G0, G4, H7, L0, L4, L5, WB, WL + (".*:CRC:integtest1_v1_0", ("crc", "v1", "CRC")), + ("STM32L[04].*:CRC:integtest1_v2_0", ("crc", "v3", "CRC")), + (".*:CRC:integtest1_v2_0", ("crc", "v2", "CRC")), + (".*:CRC:integtest1_v2_2", ("crc", "v3", "CRC")), + (".*:LCD:lcdc1_v1.0.*", ("lcd", "v1", "LCD")), + (".*:LCD:lcdc1_v1.2.*", ("lcd", "v2", "LCD")), + (".*:LCD:lcdc1_v1.3.*", ("lcd", "v2", "LCD")), + ]; + + Self { + regexes: PERIMAP + .iter() + .map(|(a, b)| (regex::Regex::new(&format!("^{a}$")).unwrap(), *b)) + .collect(), + cached: HashMap::new(), + } + } + + fn match_peri(&mut self, peri: &str) -> Option<(&'static str, &'static str, &'static str)> { + *self + .cached + .entry(peri.to_string()) + .or_insert_with(|| self.regexes.iter().find(|(r, _block)| r.is_match(peri)).map(|x| x.1)) + } +} + +fn corename(d: &str) -> String { + let m = regex!(r".*Cortex-M(\d+)(\+?)\s*(.*)").captures(d).unwrap(); + let cm = m.get(1).unwrap().as_str(); + let p = if m.get(2).unwrap().as_str() == "+" { "p" } else { "" }; + let s = if m.get(3).unwrap().as_str() == "secure" { + "s" + } else { + "" + }; + format!("cm{cm}{p}{s}") +} + +fn merge_periph_pins_info( + is_f1: bool, + periph_name: &str, + core_pins: &mut Vec, + af_pins: &[stm32_data_serde::chip::core::peripheral::Pin], +) { + if is_f1 { + // TODO: actually handle the F1 AFIO information when it will be extracted + return; + } + + // covert to hashmap + let af_pins: HashMap<(stm32_data_serde::chip::core::peripheral::pin::Pin, &str), Option> = + af_pins.iter().map(|v| ((v.pin, v.signal.as_str()), v.af)).collect(); + + for pin in core_pins { + let af = af_pins.get(&(pin.pin, &pin.signal)).copied().flatten(); + + // try to look for a signal with another name + let af = af.or_else(|| { + if pin.signal == "CTS" { + // for some godforsaken reason UART4's and UART5's CTS are called CTS_NSS in the GPIO xml + // so try to match with these + af_pins.get(&(pin.pin, "CTS_NSS")).copied().flatten() + } else if periph_name == "I2C1" { + // it appears that for __some__ STM32 MCUs there is no AFIO specified in GPIO file + // (notably - STM32F030C6 with it's I2C1 on PF6 and PF7) + // but the peripheral can actually be mapped to different pins + // this breaks embassy's model, so we pretend that it's AF 0 + // Reference Manual states that there's no GPIOF_AFR register + // but according to Cube-generated core it's OK to write to AFIO reg, it seems to be ignored + // TODO: are there any more signals that have this "feature" + Some(0) + } else { + None + } + }); + + if let Some(af) = af { + pin.af = Some(af); + } + } +} + +pub fn parse_groups() -> Result<(HashMap, Vec), anyhow::Error> { + // XMLs group together chips that are identical except flash/ram size. + // For example STM32L471Z(E-G)Jx.xml is STM32L471ZEJx, STM32L471ZGJx. + // However they do NOT group together identical chips with different package. + + // We want exactly the opposite: group all packages of a chip together, but + // NOT group equal-except-memory-size chips together. Yay. + + // We first read all XMLs, and fold together all packages. We don't expand + // flash/ram sizes yet, we want to do it as late as possible to avoid duplicate + // work so that generation is faster. + + let mut chips = HashMap::::new(); + let mut chip_groups = Vec::new(); + + let mut files: Vec<_> = glob::glob("sources/cubedb/mcu/STM32*.xml")? + .map(Result::unwrap) + .collect(); + files.sort(); + + for f in files { + parse_group(f, &mut chips, &mut chip_groups)?; + } + + for (chip_name, chip) in &chips { + chip_groups[chip.group_idx].chip_names.push(chip_name.clone()); + } + Ok((chips, chip_groups)) +} + +fn parse_group( + f: std::path::PathBuf, + chips: &mut HashMap, + chip_groups: &mut Vec, +) -> anyhow::Result<()> { + let ff = f.file_name().unwrap().to_string_lossy(); + + // Not supported + if ff.contains("STM32MP") { + return Ok(()); + } + + // Does not exist in ST website. No datasheet, no RM. + if ff.contains("STM32GBK") || ff.contains("STM32L485") { + return Ok(()); + } + + let parsed: xml::Mcu = quick_xml::de::from_str(&std::fs::read_to_string(f)?)?; + + let package_names = { + let name = &parsed.ref_name; + if !name.contains('(') { + vec![name.to_string()] + } else { + let (prefix, suffix) = name.split_once('(').unwrap(); + let (letters, suffix) = suffix.split_once(')').unwrap(); + letters.split('-').map(|x| format!("{prefix}{x}{suffix}")).collect() + } + }; + + let package_rams = { + if parsed.rams.len() == 1 { + vec![parsed.rams[0]; package_names.len()] + } else { + parsed.rams.clone() + } + }; + let package_flashes = { + if parsed.flashs.len() == 1 { + vec![parsed.flashs[0]; package_names.len()] + } else { + parsed.flashs.clone() + } + }; + + let group_idx = package_names.iter().find_map(|package_name| { + let chip_name = chip_name_from_package_name(package_name); + chips.get(&chip_name).map(|chip| chip.group_idx) + }); + + let group_idx = group_idx.unwrap_or_else(|| { + let group_idx = chip_groups.len(); + chip_groups.push(ChipGroup { + chip_names: Vec::new(), + xml: parsed.clone(), + ips: HashMap::new(), + pins: HashMap::new(), + family: None, + line: None, + die: None, + }); + group_idx + }); + + for (package_i, package_name) in package_names.iter().enumerate() { + let chip_name = chip_name_from_package_name(package_name); + if !chips.contains_key(&chip_name) { + chips.insert( + chip_name.clone(), + Chip { + flash: package_flashes[package_i], + ram: package_rams[package_i], + group_idx, + packages: Vec::new(), + }, + ); + } + chips + .get_mut(&chip_name) + .unwrap() + .packages + .push(stm32_data_serde::chip::Package { + name: package_name.clone(), + package: parsed.package.clone(), + }); + } + + // Some packages have some peripehrals removed because the package had to + // remove GPIOs useful for that peripheral. So we merge all peripherals from all packages. + let group = &mut chip_groups[group_idx]; + for ip in parsed.ips { + group.ips.insert(ip.instance_name.clone(), ip); + } + for pin in parsed.pins { + if let Some(pin_name) = gpio_af::clean_pin(&pin.name) { + group.pins.insert(pin_name, pin); + } + } + + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +fn process_group( + mut group: ChipGroup, + peri_matcher: &mut PeriMatcher, + headers: &header::Headers, + af: &gpio_af::Af, + chip_interrupts: &interrupts::ChipInterrupts, + peripheral_to_clock: &rcc::PeripheralToClock, + dma_channels: &dma::DmaChannels, + chips: &HashMap, + memories: &memory::Memories, + docs: &docs::Docs, +) -> Result<(), anyhow::Error> { + let chip_name = group.chip_names[0].clone(); + group.family = Some(group.xml.family.clone()); + group.line = Some(group.xml.line.clone()); + group.die = Some(group.xml.die.clone()); + let rcc_kind = group.ips.values().find(|x| x.name == "RCC").unwrap().version.clone(); + let rcc_block = peri_matcher.match_peri(&format!("{chip_name}:RCC:{rcc_kind}")).unwrap(); + let h = headers.get_for_chip(&chip_name).unwrap(); + let chip_af = &group.ips.values().find(|x| x.name == "GPIO").unwrap().version; + let chip_af = chip_af.strip_suffix("_gpio_v1_0").unwrap(); + let chip_af = af.0.get(chip_af); + let cores: Vec<_> = group + .xml + .cores + .iter() + .map(|core_xml| { + process_core( + core_xml, + h, + &chip_name, + &group, + chip_interrupts, + peri_matcher, + peripheral_to_clock, + rcc_block, + chip_af, + dma_channels, + ) + }) + .collect(); + + for chip_name in &group.chip_names { + process_chip(chips, chip_name, h, memories, docs, &group, &cores)?; + } + + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +fn process_core( + core_xml: &str, + h: &header::ParsedHeader, + chip_name: &str, + group: &ChipGroup, + chip_interrupts: &interrupts::ChipInterrupts, + peri_matcher: &mut PeriMatcher, + peripheral_to_clock: &rcc::PeripheralToClock, + rcc_block: (&str, &str, &str), + chip_af: Option<&HashMap>>, + dma_channels: &dma::DmaChannels, +) -> stm32_data_serde::chip::Core { + let real_core_name = corename(core_xml); + + let core_name = if !h.interrupts.contains_key(&real_core_name) || !h.defines.contains_key(&real_core_name) { + "all" + } else { + &real_core_name + }; + // C header defines for this core. + let defines = h.defines.get(core_name).unwrap(); + // Interrupts! + let want_nvic_name = { + // Most chips have a single NVIC, named "NVIC" + let mut want_nvic_name = "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" { + want_nvic_name = "NVIC1"; + } else { + want_nvic_name = "NVIC2" + } + } + if &chip_name[5..8] == "WL5" { + if core_name == "cm4" { + want_nvic_name = "NVIC1"; + } else { + want_nvic_name = "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]) { + want_nvic_name = "NVIC2" + } + + want_nvic_name + }; + let chip_nvic = group.ips.values().find(|x| x.name == want_nvic_name).unwrap(); + let mut header_irqs = h.interrupts.get(core_name).unwrap().clone(); + let chip_irqs = chip_interrupts + .0 + .get(&(chip_nvic.name.clone(), chip_nvic.version.clone())) + .unwrap(); + // 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"); + } + let mut interrupts: Vec<_> = header_irqs + .iter() + .map(|(k, v)| stm32_data_serde::chip::core::Interrupt { + name: k.clone(), + number: *v, + }) + .collect(); + interrupts.sort_unstable_by_key(|x| x.number); + let mut peri_kinds = HashMap::new(); + for ip in group.ips.values() { + let pname = ip.instance_name.clone(); + let pkind = format!("{}:{}", ip.name, ip.version); + let pkind = pkind.strip_suffix("_Cube").unwrap_or(&pkind); + + const FAKE_PERIPHERALS: &[&str] = &[ + // These are real peripherals but with special handling + "NVIC", + "GPIO", + "DMA", + // IRTIM is just TIM16+TIM17 + "IRTIM", + // I2S is just SPI on disguise + "I2S1", + "I2S2", + "I2S3", + "I2S4", + "I2S5", + "I2S6", + "I2S7", + "I2S8", + // We add this as ghost peri + "SYS", + // These are software libraries + "FREERTOS", + "PDM2PCM", + "FATFS", + "LIBJPEG", + "MBEDTLS", + "LWIP", + "USB_HOST", + "USB_DEVICE", + "GUI_INTERFACE", + "TRACER_EMB", + "TOUCHSENSING", + ]; + + if FAKE_PERIPHERALS.contains(&pname.as_str()) { + continue; + } + + let pname = match pname.as_str() { + "HDMI_CEC" => "CEC".to_string(), + "SUBGHZ" => "SUBGHZSPI".to_string(), + _ => pname, + }; + + if pname.starts_with("ADC") { + if let Entry::Vacant(entry) = peri_kinds.entry("ADC_COMMON".to_string()) { + entry.insert(format!("ADC_COMMON:{}", ip.version.strip_suffix("_Cube").unwrap())); + } + } + if pname.starts_with("ADC3") && chip_name.starts_with("STM32H7") { + if let Entry::Vacant(entry) = peri_kinds.entry("ADC3_COMMON".to_string()) { + entry.insert(format!("ADC3_COMMON:{}", ip.version.strip_suffix("_Cube").unwrap())); + } + } + peri_kinds.insert(pname, pkind.to_string()); + } + const GHOST_PERIS: &[&str] = &[ + "GPIOA", "GPIOB", "GPIOC", "GPIOD", "GPIOE", "GPIOF", "GPIOG", "GPIOH", "GPIOI", "GPIOJ", "GPIOK", "GPIOL", + "GPIOM", "GPION", "GPIOO", "GPIOP", "GPIOQ", "GPIOR", "GPIOS", "GPIOT", "DMA1", "DMA2", "BDMA", "DMAMUX", + "DMAMUX1", "DMAMUX2", "SYSCFG", "EXTI", "FLASH", "DBGMCU", "CRS", "PWR", "AFIO", "BKP", + ]; + for pname in GHOST_PERIS { + if let Entry::Vacant(entry) = peri_kinds.entry(pname.to_string()) { + if defines.get_peri_addr(pname).is_some() { + entry.insert("unknown".to_string()); + } + } + } + if peri_kinds.contains_key("BDMA1") { + peri_kinds.remove("BDMA"); + } + // get possible used GPIOs for each peripheral from the chip xml + // it's not the full info we would want (stuff like AFIO info which comes from GPIO xml), + // but we actually need to use it because of F1 line + // which doesn't include non-remappable peripherals in GPIO xml + // and some weird edge cases like STM32F030C6 (see merge_periph_pins_info) + let mut periph_pins = HashMap::<_, Vec<_>>::new(); + for (pin_name, pin) in &group.pins { + for signal in &pin.signals { + let mut signal = signal.name.clone(); + if signal.starts_with("DEBUG_SUBGHZSPI-") { + signal = format!("SUBGHZSPI_{}", &signal[16..(signal.len() - 3)]); + } + // TODO: What are those signals (well, GPIO is clear) Which peripheral do they belong to? + if !["GPIO", "CEC", "AUDIOCLK", "VDDTCXO"].contains(&signal.as_str()) && !signal.contains("EXTI") { + // both peripherals and signals can have underscores in their names so there is no easy way to split + // check if signal name starts with one of the peripheral names + for periph in peri_kinds.keys() { + if let Some(signal) = signal.strip_prefix(&format!("{periph}_")) { + periph_pins.entry(periph.to_string()).or_default().push( + stm32_data_serde::chip::core::peripheral::Pin { + pin: *pin_name, + signal: signal.to_string(), + af: None, + }, + ); + break; + } + } + } + } + } + for pins in periph_pins.values_mut() { + pins.sort(); + pins.dedup(); + } + let mut peripherals = Vec::new(); + for (pname, pkind) in peri_kinds { + let addr = if chip_name.starts_with("STM32F0") && pname == "ADC" { + defines.get_peri_addr("ADC1") + } else { + defines.get_peri_addr(&pname) + }; + + let addr = match addr { + Some(addr) => addr, + None => continue, + }; + + let mut p = stm32_data_serde::chip::core::Peripheral { + name: pname.clone(), + address: addr, + registers: None, + rcc: None, + interrupts: None, + dma_channels: Vec::new(), + pins: Vec::new(), + }; + + if let Some(block) = peri_matcher.match_peri(&format!("{chip_name}:{pname}:{pkind}")) { + p.registers = Some(stm32_data_serde::chip::core::peripheral::Registers { + kind: block.0.to_string(), + version: block.1.to_string(), + block: block.2.to_string(), + }); + } + + if let Some(rcc_info) = peripheral_to_clock.match_peri_clock( + ( + rcc_block.0.to_string(), + rcc_block.1.to_string(), + rcc_block.2.to_string(), + ), + &pname, + ) { + p.rcc = Some(rcc_info.clone()); + } + if let Some(pins) = periph_pins.get_mut(&pname) { + // merge the core xml info with GPIO xml info to hopefully get the full picture + // if the peripheral does not exist in the GPIO xml (one of the notable one is ADC) + // it probably doesn't need any AFIO writes to work + if let Some(af_pins) = chip_af.and_then(|x| x.get(&pname)) { + merge_periph_pins_info(chip_name.contains("STM32F1"), &pname, pins, af_pins.as_slice()); + } + p.pins = pins.clone(); + } + if let Some(peri_irqs) = chip_irqs.get(&pname) { + //filter by available, because some are conditioned on + let mut irqs: Vec<_> = peri_irqs + .iter() + .filter(|i| header_irqs.contains_key(&i.interrupt)) + .cloned() + .collect(); + irqs.sort_by_key(|x| (x.signal.clone(), x.interrupt.clone())); + p.interrupts = Some(irqs); + } + peripherals.push(p); + } + if let Ok(extra_f) = std::fs::read(format!("data/extra/family/{}.yaml", group.family.as_ref().unwrap())) { + #[derive(serde::Deserialize)] + struct Extra { + peripherals: Vec, + } + + let extra: Extra = serde_yaml::from_slice(&extra_f).unwrap(); + for p in extra.peripherals { + peripherals.push(p); + } + } + peripherals.sort_by_key(|x| x.name.clone()); + let have_peris: HashSet<_> = peripherals.iter_mut().map(|p| p.name.clone()).collect(); + // Collect DMA versions in the chip + let mut chip_dmas: Vec<_> = group + .ips + .values() + .filter_map(|ip| { + let version = &ip.version; + let sort = match ip.name.as_str() { + "DMA" => 1, + "BDMA" => 2, + "BDMA1" => 3, + "BDMA2" => 4, + "GPDMA" => 5, + _ => 0, + }; + if sort > 0 && dma_channels.0.contains_key(version) { + Some((sort, version.clone())) + } else { + None + } + }) + .collect(); + chip_dmas.sort(); + chip_dmas.dedup(); + let chip_dmas: Vec<_> = chip_dmas.into_iter().map(|(_sort, version)| version).collect(); + // Process DMA channels + let chs = chip_dmas + .iter() + .flat_map(|dma| dma_channels.0.get(dma).unwrap().channels.clone()); + // The dma_channels[xx] is generic for multiple chips. The current chip may have less DMAs, + // so we have to filter it. + let chs: Vec<_> = chs.filter(|ch| have_peris.contains(&ch.dma)).collect(); + let core_dma_channels = chs.clone(); + let have_chs: HashSet<_> = chs.into_iter().collect(); + // Process peripheral - DMA channel associations + for mut p in &mut peripherals { + let mut chs = Vec::new(); + for dma in &chip_dmas { + let mut peri_chs = dma_channels.0.get(dma).unwrap().peripherals.get(&p.name); + + // DAC1 is sometimes interchanged with DAC + if peri_chs.is_none() && p.name == "DAC1" { + peri_chs = dma_channels.0.get(dma).unwrap().peripherals.get("DAC"); + } + + if let Some(peri_chs) = peri_chs { + chs.extend( + peri_chs + .iter() + .filter(|ch| { + if let Some(ch_channel) = &ch.channel { + have_chs.iter().any(|x| &x.name == ch_channel) + } else { + true + } + }) + .cloned(), + ); + } + } + if !chs.is_empty() { + chs.sort_by_key(|ch| (ch.channel.clone(), ch.dmamux.clone(), ch.request)); + p.dma_channels = chs; + } + } + stm32_data_serde::chip::Core { + name: real_core_name.clone(), + peripherals, + interrupts, + dma_channels: core_dma_channels, + } +} + +fn process_chip( + chips: &HashMap, + chip_name: &str, + h: &header::ParsedHeader, + memories: &memory::Memories, + docs: &docs::Docs, + group: &ChipGroup, + cores: &[stm32_data_serde::chip::Core], +) -> Result<(), anyhow::Error> { + let chip = chips.get(chip_name).unwrap(); + let flash_total = chip.flash * 1024; + let ram_total = chip.ram * 1024; + let mut memory_regions = Vec::new(); + let mut found = HashSet::<&str>::new(); + for each in ["FLASH", "FLASH_BANK1", "FLASH_BANK2", "D1_AXIFLASH", "D1_AXIICP"] { + if let Some(address) = h.defines.get("all").unwrap().0.get(&format!("{each}_BASE")) { + let key = match each { + "FLASH" => "BANK_1", + "FLASH_BANK1" => "BANK_1", + "FLASH_BANK2" => "BANK_2", + each => each, + }; + + if found.contains(key) { + continue; + } + found.insert(key); + + let size = if key == "BANK_1" || key == "BANK_2" { + let size = memories.determine_flash_size(chip_name); + std::cmp::min(size, flash_total) + } else { + 0 + }; + + memory_regions.push(stm32_data_serde::chip::Memory { + name: key.to_string(), + kind: stm32_data_serde::chip::memory::Kind::Flash, + address: u32::try_from(*address).unwrap(), + size, + settings: Some(memories.determine_flash_settings(chip_name)), + }); + } + } + let mut found = HashSet::new(); + for each in [ + "SRAM", + "SRAM1", + "SRAM2", + "D1_AXISRAM", + "D1_ITCMRAM", + "D1_DTCMRAM", + "D1_AHBSRAM", + "D2_AXISRAM", + "D3_BKPSRAM", + "D3_SRAM", + ] { + if let Some(address) = h.defines.get("all").unwrap().0.get(&format!("{each}_BASE")) { + let key = match each { + "D1_AXISRAM" => "SRAM", + "SRAM1" => "SRAM", + each => each, + }; + + if found.contains(key) { + continue; + } + found.insert(key); + + let size = if key == "SRAM" { + let size = memories.determine_ram_size(chip_name); + std::cmp::min(size, ram_total) + } else { + 0 + }; + + memory_regions.push(stm32_data_serde::chip::Memory { + name: key.to_string(), + kind: stm32_data_serde::chip::memory::Kind::Ram, + address: u32::try_from(*address).unwrap(), + size, + settings: None, + }) + } + } + let docs = docs.documents_for(chip_name); + let device_id = memories.determine_device_id(chip_name); + let chip = stm32_data_serde::Chip { + name: chip_name.to_string(), + family: group.family.clone().unwrap(), + line: group.line.clone().unwrap(), + die: group.die.clone().unwrap(), + device_id, + packages: chip.packages.clone(), + memory: memory_regions, + docs, + cores: cores.to_vec(), + }; + let dump = serde_json::to_string_pretty(&chip)?; + + // TODO: delete this. + // This makes the formating match the output of the original python script, to prevent unnecessary churn + let dump = { + let mut cleaned = String::new(); + for line in dump.lines() { + let spaces = line.bytes().take_while(|b| *b == b' ').count(); + for _ in 0..spaces { + // add an extra space for every existing space + // this converts two-space indents to four-space indents + cleaned.push(' '); + } + // escape non-ascii symbols + let line = line.replace('\u{00ae}', r"\u00ae"); + let line = line.replace('\u{2122}', r"\u2122"); + cleaned.push_str(&line); + cleaned.push('\n'); + } + // remove trailing newline + cleaned.pop(); + cleaned + }; + + std::fs::write(format!("data/chips/{chip_name}.json"), dump)?; + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +pub fn dump_all_chips( + chip_groups: Vec, + headers: header::Headers, + af: gpio_af::Af, + chip_interrupts: interrupts::ChipInterrupts, + peripheral_to_clock: rcc::PeripheralToClock, + dma_channels: dma::DmaChannels, + chips: std::collections::HashMap, + memories: memory::Memories, + docs: docs::Docs, +) -> Result<(), anyhow::Error> { + std::fs::create_dir_all("data/chips")?; + + #[cfg(feature = "rayon")] + { + use rayon::prelude::*; + + chip_groups + .into_par_iter() + .try_for_each_init(PeriMatcher::new, |peri_matcher, group| { + process_group( + group, + peri_matcher, + &headers, + &af, + &chip_interrupts, + &peripheral_to_clock, + &dma_channels, + &chips, + &memories, + &docs, + ) + }) + } + #[cfg(not(feature = "rayon"))] + { + let mut peri_matcher = PeriMatcher::new(); + + chip_groups.into_iter().try_for_each(|group| { + process_group( + group, + &mut peri_matcher, + &headers, + &af, + &chip_interrupts, + &peripheral_to_clock, + &dma_channels, + &chips, + &memories, + &docs, + ) + }) + } +} diff --git a/src/dma.rs b/src/dma.rs new file mode 100644 index 0000000..4b046e5 --- /dev/null +++ b/src/dma.rs @@ -0,0 +1,337 @@ +use std::collections::HashMap; + +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, + #[serde(rename = "RefMode")] + pub ref_modes: Vec, + #[serde(rename = "ModeLogicOperator")] + pub mode_logic_operator: ModeLogicOperator, + } + + #[derive(Debug, Deserialize, PartialEq)] + pub struct RefMode { + #[serde(rename = "Name")] + pub name: String, + #[serde(rename = "BaseMode")] + pub base_mode: Option, + #[serde(rename = "Parameter")] + pub parameters: Vec, + } + + #[derive(Debug, Deserialize, PartialEq)] + pub struct Parameter { + #[serde(rename = "Name")] + pub name: String, + #[serde(rename = "PossibleValue", default)] + pub possible_values: Vec, + } + + #[derive(Debug, Deserialize, PartialEq)] + pub struct RefParameter { + #[serde(rename = "Name")] + pub name: String, + #[serde(rename = "PossibleValue", default)] + pub possible_values: Vec, + } + + #[derive(Debug, Deserialize, PartialEq)] + pub struct PossibleValue { + #[serde(rename = "Comment")] + pub comment: String, + #[serde(rename = "Value")] + pub value: String, + } + + #[derive(Debug, Deserialize, PartialEq)] + pub struct ModeLogicOperator { + #[serde(rename = "Name")] + pub name: String, + #[serde(rename = "Mode")] + pub modes: Vec, + } + + #[derive(Debug, Deserialize, PartialEq)] + pub struct Mode { + #[serde(rename = "Name")] + pub name: String, + #[serde(rename = "ModeLogicOperator", default)] + pub mode_logic_operator: Option, + } +} + +#[derive(Debug, PartialEq)] +pub struct ChipDma { + pub peripherals: HashMap>, + pub channels: Vec, +} + +#[derive(Debug)] +pub struct DmaChannels(pub HashMap); + +impl DmaChannels { + pub fn parse() -> anyhow::Result { + let mut dma_channels = HashMap::new(); + for f in glob::glob("sources/cubedb/mcu/IP/DMA*Modes.xml")? + .chain(glob::glob("sources/cubedb/mcu/IP/BDMA*Modes.xml")?) + { + let parsed: xml::Ip = quick_xml::de::from_str(&std::fs::read_to_string(f?)?)?; + + let ff = parsed.version.clone(); + let is_explicitly_bdma = match parsed.name.as_str() { + "DMA" | "DMA2D" => false, + "BDMA" | "BDMA1" | "BDMA2" => true, + name => panic!("Unrecognized DMA name: {name}"), + }; + + let mut chip_dma = ChipDma { + peripherals: HashMap::new(), + channels: Vec::new(), + }; + + for dma in parsed.mode_logic_operator.modes { + let dma_peri_name = dma.name.clone(); + if dma_peri_name.contains(" Context") { + continue; + } + let channels = dma.mode_logic_operator.unwrap().modes; + if channels.len() == 1 { + // ========== CHIP WITH DMAMUX + + let dmamux_file = { + if ff.starts_with("STM32L4P") { + "L4PQ" + } else if ff.starts_with("STM32L4S") { + "L4RS" + } else { + &ff[5..7] + } + }; + + let dmamux = match is_explicitly_bdma { + true => "DMAMUX2", + false => "DMAMUX1", + }; + + let mut mfs: Vec<_> = glob::glob(&format!("data/dmamux/{dmamux_file}_*.yaml"))? + .map(Result::unwrap) + .collect(); + mfs.sort(); + for mf in mfs { + let y: HashMap = serde_yaml::from_str(&std::fs::read_to_string(&mf)?)?; + + let mf = mf.file_name().unwrap().to_string_lossy(); + let (_, req_dmamux) = mf.strip_suffix(".yaml").unwrap().split_once('_').unwrap(); // DMAMUX1 or DMAMUX2 + + if req_dmamux == dmamux { + for (request_name, request_num) in y { + let parts: Vec<_> = request_name.split('_').collect(); + let target_peri_name = parts[0]; + let request = { + if parts.len() < 2 { + target_peri_name + } else { + parts[1] + } + }; + chip_dma + .peripherals + .entry(target_peri_name.to_string()) + .or_default() + .push(stm32_data_serde::chip::core::peripheral::DmaChannel { + signal: request.to_string(), + channel: None, + dmamux: Some(req_dmamux.to_string()), + request: Some(request_num), + dma: None, + }) + } + } + } + + let mut dmamux_channel = 0; + for n in dma_peri_name.split(',') { + let n = n.trim(); + let re = regex::Regex::new(&format!(".*{n}{}", r"_(Channel|Stream)\[(\d+)-(\d+)\]")).unwrap(); + if let Some(result) = re.captures(&channels[0].name) { + let low: u8 = result.get(2).unwrap().as_str().parse()?; + let high: u8 = result.get(3).unwrap().as_str().parse()?; + for i in low..=high { + chip_dma.channels.push(stm32_data_serde::chip::core::DmaChannels { + name: format!("{n}_CH{i}"), + dma: n.to_string(), + // Make sure all channels numbers start at 0 + channel: i - low, + dmamux: Some(dmamux.to_string()), + dmamux_channel: Some(dmamux_channel), + supports_2d: None, + }); + dmamux_channel += 1; + } + } + } + } else { + // ========== CHIP WITHOUT DMAMUX + + // see if we can scrape out requests + let mut requests = HashMap::::new(); + for block in parsed + .ref_modes + .iter() + .filter(|x| x.base_mode == Some("DMA_Request".to_string())) + { + let name = block.name.clone(); + // Depending on the chip, the naming is "Channel" or "Request"... + if let Some(request_num) = block + .parameters + .iter() + .find(|x| x.name == "Channel" || x.name == "Request") + { + assert_eq!(request_num.possible_values.len(), 1); + let request_num = request_num.possible_values[0].clone(); + if request_num.starts_with("BDMA1_REQUEST_") { + continue; + } + let request_num = request_num + .strip_prefix("DMA_CHANNEL_") + .or_else(|| request_num.strip_prefix("DMA_REQUEST_")) + .unwrap(); + requests.insert(name, request_num.parse().unwrap()); + } + } + + let mut channel_names: Vec = Vec::new(); + + for channel in channels { + let channel_name = channel.name; + let (_, channel_name) = channel_name.split_once('_').unwrap(); + let channel_name = channel_name + .strip_prefix("Channel") + .or_else(|| channel_name.strip_prefix("Stream")) + .unwrap(); + + channel_names.push(channel_name.parse().unwrap()); + chip_dma.channels.push(stm32_data_serde::chip::core::DmaChannels { + name: format!("{dma_peri_name}_CH{channel_name}"), + dma: dma_peri_name.clone(), + channel: channel_name.parse().unwrap(), + dmamux: None, + dmamux_channel: None, + supports_2d: None, + }); + for target in channel.mode_logic_operator.unwrap().modes { + let original_target_name = target.name; + let parts: Vec<_> = original_target_name.split(':').collect(); + let target_name = parts[0]; + + // Chips with single DAC refer to channels by DAC1/DAC2 + let target_name = match target_name { + "DAC1" => "DAC_CH1", + "DAC2" => "DAC_CH2", + x => x, + }; + + let parts: Vec<_> = target_name.split('_').collect(); + let target_peri_name = parts[0]; + let target_requests = { + if parts.len() < 2 { + vec![target_peri_name] + } else { + target_name.split('_').nth(1).unwrap().split('/').collect() + } + }; + if target_name != "MEMTOMEM" { + let target_peri_name = match target_peri_name { + "LPUART" => "LPUART1", + x => x, + }; + for request in target_requests { + assert!(!request.contains(':')); + let entry = stm32_data_serde::chip::core::peripheral::DmaChannel { + signal: request.to_string(), + channel: Some(format!("{dma_peri_name}_CH{channel_name}")), + dmamux: None, + request: requests.get(&original_target_name).copied(), + dma: None, + }; + chip_dma + .peripherals + .entry(target_peri_name.to_string()) + .or_default() + .push(entry); + } + } + } + } + + // Make sure all channels numbers start at 0 + if channel_names.iter().min().unwrap() != &0 { + for ch in &mut chip_dma.channels { + if ch.dma == dma_peri_name { + ch.channel -= 1; + } + } + } + } + } + + dma_channels.insert(ff, chip_dma); + } + + // STM32U5 + + let mut chip_dma = ChipDma { + peripherals: HashMap::new(), + channels: Vec::new(), + }; + + let parsed: HashMap = + serde_yaml::from_str(&std::fs::read_to_string("data/dmamux/U5_GPDMA1.yaml")?)?; + + for (request_name, request_num) in parsed { + let parts: Vec<_> = request_name.split('_').collect(); + let target_peri_name = parts[0]; + let request = { + if parts.len() < 2 { + target_peri_name + } else { + parts[1] + } + }; + chip_dma + .peripherals + .entry(target_peri_name.to_string()) + .or_default() + .push(stm32_data_serde::chip::core::peripheral::DmaChannel { + signal: request.to_string(), + dma: Some("GPDMA1".to_string()), + channel: None, + dmamux: None, + request: Some(request_num), + }); + } + + for i in 0..16 { + chip_dma.channels.push(stm32_data_serde::chip::core::DmaChannels { + name: format!("GPDMA1_CH{i}"), + dma: "GPDMA1".to_string(), + channel: i, + dmamux: None, + dmamux_channel: None, + supports_2d: Some(i >= 12), + }); + } + + dma_channels.insert("STM32U5_dma3_Cube".to_string(), chip_dma); + + Ok(Self(dma_channels)) + } +} diff --git a/src/docs.rs b/src/docs.rs new file mode 100644 index 0000000..72a0cc3 --- /dev/null +++ b/src/docs.rs @@ -0,0 +1,150 @@ +use std::collections::HashMap; + +mod mcufinder { + use serde::{Deserialize, Serialize}; + + #[derive(Debug, Serialize, Deserialize, PartialEq)] + pub struct Files { + #[serde(rename = "Files")] + pub files: Vec, + } + + #[derive(Debug, Serialize, Deserialize, PartialEq)] + pub struct File { + #[serde(rename = "URL")] + pub url: String, + // #[serde(rename = "displayName")] + // pub display_name: String, + pub id_file: String, + pub name: String, + // #[serde(rename = "related_MCU_count")] + // pub related_mcu_count: String, + pub title: String, + pub r#type: String, + // #[serde(rename = "versionNumber")] + // pub version_number: String, + } + + #[derive(Debug, Serialize, Deserialize, PartialEq)] + pub struct Mcus { + #[serde(rename = "MCUs")] + pub mcus: Vec, + } + + #[derive(Debug, Serialize, Deserialize, PartialEq)] + pub struct Mcu { + #[serde(rename = "RPN")] + pub rpn: String, + pub files: Vec, + } + + #[derive(Debug, Serialize, Deserialize, PartialEq)] + pub struct McuFile { + pub file_id: String, + } +} + +impl From for stm32_data_serde::chip::Doc { + fn from(file: mcufinder::File) -> Self { + Self { + name: file.name, + title: file.title, + url: file.url, + r#type: parse_document_type(&file.r#type).to_string(), + } + } +} + +#[derive(Debug, PartialEq)] +pub struct AllMcuFiles(HashMap); + +impl AllMcuFiles { + pub fn parse() -> anyhow::Result { + let j = std::fs::read_to_string("sources/mcufinder/files.json")?; + let parsed: mcufinder::Files = serde_json::from_str(&j)?; + let all_mcu_files = parsed + .files + .into_iter() + .map(|file| (file.id_file.clone(), file.into())) + .collect(); + Ok(Self(all_mcu_files)) + } +} + +#[derive(Debug, PartialEq)] +pub struct PerMcuFiles(HashMap>); + +impl PerMcuFiles { + pub fn parse() -> anyhow::Result { + let j = std::fs::read_to_string("sources/mcufinder/mcus.json")?; + let parsed: mcufinder::Mcus = serde_json::from_str(&j)?; + + let mut per_mcu_files = HashMap::>::new(); + + for mcu in parsed.mcus { + let rpn = mcu.rpn; + let files = mcu.files.into_iter().map(|file| file.file_id); + per_mcu_files.entry(rpn.to_string()).or_default().extend(files); + } + + Ok(Self(per_mcu_files)) + } +} + +pub struct Docs { + pub all_mcu_files: AllMcuFiles, + pub per_mcu_files: PerMcuFiles, +} + +impl Docs { + pub fn parse() -> anyhow::Result { + Ok(Self { + all_mcu_files: AllMcuFiles::parse()?, + per_mcu_files: PerMcuFiles::parse()?, + }) + } + + pub fn documents_for(&self, chip_name: &str) -> Vec { + let mut docs: Vec<_> = self + .per_mcu_files + .0 + .get(chip_name) + .into_iter() + .flatten() + .flat_map(|id| { + if let Some(file) = self.all_mcu_files.0.get(id) { + let order = order_doc_type(&file.r#type); + Some((order, file)) + } else { + None + } + }) + .collect(); + docs.sort_by_key(|(order, file)| (*order, file.name.clone())); + + docs.into_iter().map(|(_order, file)| file.clone()).collect() + } +} + +fn parse_document_type(t: &str) -> &'static str { + match t { + "Reference manual" => "reference_manual", + "Programming manual" => "programming_manual", + "Datasheet" => "datahseet", // TODO: fix me + "Errata sheet" => "errata_sheet", + "Application note" => "application_note", + "User manual" => "user_manual", + _ => panic!("Unknown doc type {t}"), + } +} + +fn order_doc_type(t: &str) -> u8 { + match t { + "reference_manual" => 0, + "programming_manual" => 1, + "datahseet" => 2, // TODO: fix me + "errata_sheet" => 3, + "application_note" => 4, + _ => panic!("Unknown doc type {t}"), + } +} diff --git a/src/gpio_af.rs b/src/gpio_af.rs new file mode 100644 index 0000000..1c88c84 --- /dev/null +++ b/src/gpio_af.rs @@ -0,0 +1,125 @@ +use std::collections::HashMap; + +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 = "GPIO_Pin")] + pub gpio_pins: Vec, + } + + #[derive(Debug, Deserialize, PartialEq)] + pub struct GpioPin { + #[serde(rename = "Name")] + pub name: String, + #[serde(rename = "PinSignal", default)] + pub pin_signals: Vec, + } + + #[derive(Debug, Deserialize, PartialEq)] + pub struct PinSignal { + #[serde(rename = "Name")] + pub name: String, + #[serde(rename = "SpecificParameter", default)] + pub specific_parameter: Option, + } + + #[derive(Debug, Deserialize, PartialEq)] + pub struct SpecificParameter { + #[serde(rename = "PossibleValue")] + pub possible_value: String, + } +} + +pub fn clean_pin(pin_name: &str) -> Option { + let pin_name = regex!(r"^P[A-Z]\d+").find(pin_name)?.as_str(); + + stm32_data_serde::chip::core::peripheral::pin::Pin::parse(pin_name) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Af(pub HashMap>>); + +impl Af { + pub fn parse() -> anyhow::Result { + let mut af = HashMap::new(); + for f in glob::glob("sources/cubedb/mcu/IP/GPIO-*_gpio_v1_0_Modes.xml")? { + let parsed: xml::Ip = quick_xml::de::from_str(&std::fs::read_to_string(f?)?)?; + + let ff = parsed.version.strip_suffix("_gpio_v1_0").unwrap().to_string(); + + let mut peris = HashMap::<_, Vec<_>>::new(); + + for pin in parsed.gpio_pins { + // Cleanup pin name + let Some(pin_name) = clean_pin(&pin.name) else {continue}; + + // Extract AFs + for signal in pin.pin_signals { + let Some((peri_name, signal_name)) = parse_signal_name(&signal.name) else {continue}; + let afn = if parsed.version.starts_with("STM32F1") { + None + } else { + let afn = signal.specific_parameter.unwrap(); + let afn = afn + .possible_value + .split('_') + .nth(1) + .unwrap() + .strip_prefix("AF") + .unwrap() + .parse() + .unwrap(); + Some(afn) + }; + peris.entry(peri_name.to_string()).or_default().push( + stm32_data_serde::chip::core::peripheral::Pin { + pin: pin_name, + signal: signal_name.to_string(), + af: afn, + }, + ); + } + } + + for p in peris.values_mut() { + p.sort(); + p.dedup(); + } + + af.insert(ff, peris); + } + + Ok(Self(af)) + } +} + +fn parse_signal_name(signal_name: &str) -> Option<(&str, &str)> { + let (peri_name, signal_name) = { + if let Some(signal_name) = signal_name.strip_prefix("USB_OTG_FS_") { + ("USB_OTG_FS", signal_name) + } else if let Some(signal_name) = signal_name.strip_prefix("USB_OTG_HS_") { + ("USB_OTG_HS", signal_name) + } else { + signal_name.split_once('_')? + } + }; + + if signal_name.starts_with("EXTI") { + return None; + } + if peri_name.starts_with("DEBUG") && signal_name.starts_with("SUBGHZSPI") { + let (peri_name, signal_name) = signal_name.split_once('-').unwrap(); + + Some((peri_name, signal_name.strip_suffix("OUT").unwrap_or(signal_name))) + } else { + Some((peri_name, signal_name)) + } +} diff --git a/src/header.rs b/src/header.rs new file mode 100644 index 0000000..c617995 --- /dev/null +++ b/src/header.rs @@ -0,0 +1,286 @@ +use std::collections::HashMap; + +use crate::regex; + +pub struct Headers { + map: HeaderMap, + parsed: HeadersParsed, + regexes: Vec<(regex::Regex, String)>, +} + +impl Headers { + pub fn parse() -> anyhow::Result { + let map = HeaderMap::parse()?; + let parsed = HeadersParsed::parse()?; + let regexes = parsed + .0 + .keys() + .map(|h| { + let pattern = h.replace('x', "."); + let regex = regex::Regex::new(&format!("^{pattern}$")).unwrap(); + (regex, h.clone()) + }) + .collect(); + Ok(Self { map, parsed, regexes }) + } + + pub fn get_for_chip(&self, model: &str) -> Option<&ParsedHeader> { + let model = model.to_ascii_lowercase(); + match self.map.0.get(&model) { + // if it's in the map, just go + Some(name) => Some(self.parsed.0.get(name).unwrap()), + // if not, find it by regex, taking `x` meaning `anything` + None => { + let mut results = self + .regexes + .iter() + .filter_map(|(r, name)| if r.is_match(&model) { Some(name) } else { None }); + let res = results.next(); + assert_eq!(results.next(), None, "found more than one match"); + res.map(|name| self.parsed.0.get(name).unwrap()) + } + } + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct HeaderMap(pub HashMap); + +impl HeaderMap { + pub fn parse() -> anyhow::Result { + let mut res = HashMap::new(); + for (mut header, chips) in + serde_yaml::from_str::>(&std::fs::read_to_string("header_map.yaml")?)? + { + header.make_ascii_lowercase(); + for chip in chips.split(',') { + let chip = chip.trim().to_ascii_lowercase(); + if let Some(old) = res.insert(chip.clone(), header.clone()) { + panic!("Duplicate {chip} found! Overwriting {old} with {header}"); + } + } + } + + Ok(Self(res)) + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct HeadersParsed(pub HashMap); + +impl HeadersParsed { + pub fn parse() -> anyhow::Result { + let files = glob::glob("sources/headers/*.h").unwrap().map(Result::unwrap); + + let for_each_file = |f: std::path::PathBuf| { + let ff = f.file_name().unwrap().to_string_lossy(); + let ff = ff.strip_suffix(".h").unwrap(); + let parsed_header = ParsedHeader::parse(&f).unwrap(); + (ff.to_string(), parsed_header) + }; + + #[cfg(feature = "rayon")] + { + use rayon::prelude::*; + Ok(Self(files.par_bridge().map(for_each_file).collect())) + } + #[cfg(not(feature = "rayon"))] + { + Ok(Self(files.map(for_each_file).collect())) + } + } +} + +fn parens_ok(val: &str) -> bool { + let mut n: i32 = 0; + for c in val.chars() { + match c { + '(' => n += 1, + ')' => { + n -= 1; + if n < 0 { + return false; + } + } + _ => {} + } + } + n == 0 +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct Defines(pub HashMap); + +impl Defines { + // warning: horrible abomination ahead + fn parse_value(&self, val: &str) -> i64 { + let val = val.trim(); + if val.is_empty() { + 0 + } else if let Some(m) = regex!(r"^(0([1-9][0-9]*)(U))").captures(val) { + m.get(2).unwrap().as_str().parse().unwrap() + } else if let Some(m) = regex!(r"^((0x[0-9a-fA-F]+|\d+))(|u|ul|U|UL)$").captures(val) { + let x = m.get(1).unwrap().as_str(); + match x.strip_prefix("0x") { + Some(x) => i64::from_str_radix(x, 16), + None => x.parse(), + } + .unwrap() + } else if let Some(m) = regex!(r"^([0-9A-Za-z_]+)$").captures(val) { + self.0.get(m.get(1).unwrap().as_str()).copied().unwrap_or(0) + } else if let Some(x) = regex!(r"^\((.*)\)$") + .captures(val) + .map(|m| m.get(1).unwrap().as_str()) + .filter(|x| parens_ok(x)) + { + self.parse_value(x) + } else if let Some(m) = regex!(r"^\*?\([0-9A-Za-z_]+ *\*?\)(.*)$").captures(val) { + self.parse_value(m.get(1).unwrap().as_str()) + } else if let Some(m) = regex!(r"^(.*)/(.*)$").captures(val) { + self.parse_value(m.get(1).unwrap().as_str()) / self.parse_value(m.get(2).unwrap().as_str()) + } else if let Some(m) = regex!(r"^(.*)<<(.*)$").captures(val) { + self.parse_value(m.get(1).unwrap().as_str()) << self.parse_value(m.get(2).unwrap().as_str()) & 0xFFFFFFFF + } else if let Some(m) = regex!(r"^(.*)>>(.*)$").captures(val) { + self.parse_value(m.get(1).unwrap().as_str()) >> self.parse_value(m.get(2).unwrap().as_str()) + } else if let Some(m) = regex!(r"^(.*)\|(.*)$").captures(val) { + self.parse_value(m.get(1).unwrap().as_str()) | self.parse_value(m.get(2).unwrap().as_str()) + } else if let Some(m) = regex!(r"^(.*)&(.*)$").captures(val) { + self.parse_value(m.get(1).unwrap().as_str()) & self.parse_value(m.get(2).unwrap().as_str()) + } else if let Some(m) = regex!(r"^~(.*)$").captures(val) { + !self.parse_value(m.get(1).unwrap().as_str()) & 0xFFFFFFFF + } else if let Some(m) = regex!(r"^(.*)\+(.*)$").captures(val) { + self.parse_value(m.get(1).unwrap().as_str()) + self.parse_value(m.get(2).unwrap().as_str()) + } else if let Some(m) = regex!(r"^(.*)-(.*)$").captures(val) { + self.parse_value(m.get(1).unwrap().as_str()) - self.parse_value(m.get(2).unwrap().as_str()) + } else { + panic!("can't parse: {val:?}") + } + } + + pub fn get_peri_addr(&self, pname: &str) -> Option { + const ALT_PERI_DEFINES: &[(&str, &[&str])] = &[ + ("DBGMCU", &["DBGMCU_BASE", "DBG_BASE"]), + ("FLASH", &["FLASH_R_BASE", "FLASH_REG_BASE"]), + ( + "ADC_COMMON", + &["ADC_COMMON", "ADC1_COMMON", "ADC12_COMMON", "ADC123_COMMON"], + ), + ("CAN", &["CAN_BASE", "CAN1_BASE"]), + ("FMC", &["FMC_BASE", "FMC_R_BASE"]), + ("FSMC", &["FSMC_R_BASE"]), + ]; + let alt_peri_defines: HashMap<_, _> = ALT_PERI_DEFINES.iter().copied().collect(); + + let possible_defines: Vec = alt_peri_defines + .get(pname) + .map(|x| x.iter().map(ToString::to_string).collect()) + .unwrap_or_else(|| vec![format!("{pname}_BASE"), pname.to_string()]); + possible_defines + .into_iter() + .find_map(|d| self.0.get(&d).filter(|&&addr| addr != 0)) + .map(|x| u32::try_from(*x).unwrap()) + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct ParsedHeader { + pub cores: Vec, + pub interrupts: HashMap>, + pub defines: HashMap, +} + +impl ParsedHeader { + fn parse(f: impl AsRef) -> anyhow::Result { + let mut irqs = HashMap::>::new(); + let mut defines = HashMap::::new(); + let mut cores = Vec::::new(); + let mut cur_core = "all".to_string(); + + let mut accum = String::new(); + let f = std::fs::read(f)?; + for l in f.split(|b| b == &b'\n') { + let l = String::from_utf8_lossy(l); + let l = l.trim(); + let l = accum.clone() + l; + if l.ends_with('\\') { + accum = l.strip_suffix('\\').unwrap().to_string(); + continue; + } + accum = String::new(); + + // Scoped by a single core + if let Some(m) = regex!(r".*if defined.*CORE_CM(\d+)(PLUS)?.*").captures(&l) { + cur_core = format!("cm{}", m.get(1).unwrap().as_str()); + if m.get(2).is_some() { + cur_core += "p"; + } + if !cores.contains(&cur_core) { + cores.push(cur_core.clone()) + } + } else if regex!(r".*else.*").is_match(&l) { + cur_core = "all".to_string(); + if let Some(m) = regex!(".*else.*CORE_CM(\\d+)(PLUS)?.*").captures(&l) { + cur_core = format!("cm{}", m.get(1).unwrap().as_str()); + if m.get(2).is_some() { + cur_core += "p"; + } + } else if cores.len() > 1 { + // Pick the second core assuming we've already parsed one + cur_core = cores[1].clone(); + } + + if !cores.contains(&cur_core) { + cores.push(cur_core.clone()); + } + } else if regex!(r".*endif.*").is_match(&l) { + cur_core = "all".to_string(); + } + + let irq_entry = irqs.entry(cur_core.clone()).or_default(); + let defines_entry = defines.entry(cur_core.clone()).or_default(); + + if let Some(m) = regex!(r"^([a-zA-Z0-9_]+)_IRQn += (\d+),? +/\*!< (.*) \*/").captures(&l) { + irq_entry.insert( + m.get(1).unwrap().as_str().to_string(), + m.get(2).unwrap().as_str().parse().unwrap(), + ); + } + + if let Some(m) = regex!(r"^#define +([0-9A-Za-z_]+)\(").captures(&l) { + defines_entry.0.insert(m.get(1).unwrap().as_str().to_string(), -1); + } + + if let Some(m) = regex!(r"^#define +([0-9A-Za-z_]+) +(.*)").captures(&l) { + let name = m.get(1).unwrap().as_str().trim(); + if name == "FLASH_SIZE" { + continue; + } + let val = m.get(2).unwrap().as_str(); + let val = val.split("/*").next().unwrap().trim(); + let val = defines_entry.parse_value(val); + + defines_entry.0.insert(name.to_string(), val); + } + } + + if cores.is_empty() { + cores = vec!["all".to_string()]; + } + + for core in &mut cores { + if core != "all" { + let all_irqs = irqs.get("all").unwrap().clone(); + irqs.get_mut(core).unwrap().extend(all_irqs); + + let all_defines = defines.get("all").unwrap().clone(); + defines.get_mut(core).unwrap().0.extend(all_defines.0); + } + } + + Ok(Self { + cores, + interrupts: irqs, + defines, + }) + } +} diff --git a/src/interrupts.rs b/src/interrupts.rs new file mode 100644 index 0000000..2b08cb8 --- /dev/null +++ b/src/interrupts.rs @@ -0,0 +1,384 @@ +use std::collections::{HashMap, HashSet}; + +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, + } + + #[derive(Debug, Deserialize, PartialEq)] + pub struct RefParameter { + #[serde(rename = "Name")] + pub name: String, + #[serde(rename = "PossibleValue", default)] + pub possible_values: Vec, + } + + #[derive(Debug, Deserialize, PartialEq)] + pub struct PossibleValue { + #[serde(rename = "Comment")] + pub comment: String, + #[serde(rename = "Value")] + pub value: String, + } +} + +#[derive(Debug)] +pub struct ChipInterrupts( + pub HashMap<(String, String), HashMap>>, +); + +impl ChipInterrupts { + pub fn parse() -> anyhow::Result { + let mut chip_interrupts = 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 { + let mut irqs = HashMap::::new(); + let file = std::fs::read_to_string(f)?; + let parsed: xml::Ip = quick_xml::de::from_str(&file)?; + for irq in parsed + .ref_parameters + .into_iter() + .filter(|param| param.name == "IRQn") + .flat_map(|param| param.possible_values) + { + let parts = { + let mut iter = irq.value.split(':'); + let parts = [(); 5].map(|_| iter.next().unwrap()); + assert!(iter.next().is_none()); + parts + }; + + let name = { + let name = parts[0].strip_suffix("_IRQn").unwrap(); + + // 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() { + "AES_LPUART1" + } else { + name + } + }; + + let entry = match irqs.entry(name.to_string()) { + std::collections::hash_map::Entry::Occupied(_) => continue, + std::collections::hash_map::Entry::Vacant(entry) => entry, + }; + + // 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 parsed.version == "STM32F100E" && name == "DMA2_Channel4_5" { + continue; + } + // F3 can remap USB IRQs, ignore them. + if parsed.version.starts_with("STM32F3") && irq.comment.contains("remap") { + continue; + } + let mut signals = HashSet::<(String, String)>::new(); + if [ + "NonMaskableInt", + "HardFault", + "MemoryManagement", + "BusFault", + "UsageFault", + "SVCall", + "DebugMonitor", + "PendSV", + "SysTick", + ] + .contains(&name) + { + // pass + } else if flags + .iter() + .map(|flag| ["DMA", "DMAL0", "DMAF0", "DMAL0_DMAMUX", "DMAF0_DMAMUX"].contains(flag)) + .any(std::convert::identity) + { + 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 { + 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" { + signals.insert(("DMAMUX1".to_string(), "OVR".to_string())); + } else if name == "DMAMUX2_OVR" { + 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(',') { + signals.insert(("EXTI".to_string(), signal.to_string())); + } + } else if name == "FLASH" { + signals.insert(("FLASH".to_string(), "GLOBAL".to_string())); + } else if name == "CRS" { + signals.insert(("RCC".to_string(), "CRS".to_string())); + } else if name == "RCC" { + signals.insert(("RCC".to_string(), "GLOBAL".to_string())); + } else { + if parts[2].is_empty() { + continue; + } + + let peri_names: Vec<_> = parts[2].split(',').map(ToString::to_string).collect(); + + 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::::new())) + .collect(); + + let mut curr_peris = Vec::new(); + if peri_names.len() == 1 { + curr_peris = peri_names.clone(); + } + + // Parse IRQ signals from the IRQ name. + for part in tokenize_name(name2) { + let part = { + if part == "TAMPER" { + "TAMP".to_string() + } else { + part + } + }; + + if part == "LSECSS" { + signals.insert(("RCC".to_string(), "LSECSS".to_string())); + } else if part == "CSS" { + signals.insert(("RCC".to_string(), "CSS".to_string())); + } else if part == "LSE" { + signals.insert(("RCC".to_string(), "LSE".to_string())); + } else if part == "CRS" { + signals.insert(("RCC".to_string(), "CRS".to_string())); + } else { + let pp = match_peris(&peri_names, &part); + 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 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:?}"); + } + signals.insert((p.clone(), s)); + } + } + } + + // for (peri, signal) in &signals { + // println!(" {peri}:{signal}"); + // } + entry.insert(signals); + } + + let mut irqs2 = HashMap::<_, Vec<_>>::new(); + for (name, signals) in irqs { + for (p, s) in signals { + irqs2 + .entry(p) + .or_default() + .push(stm32_data_serde::chip::core::peripheral::Interrupt { + signal: s, + interrupt: name.clone(), + }); + } + } + + for pirqs in irqs2.values_mut() { + let mut psirqs = HashMap::<_, Vec<_>>::new(); + for irq in pirqs { + psirqs + .entry(irq.signal.clone()) + .or_default() + .push(irq.interrupt.clone()); + } + // for (s, irqs) in psirqs { + // if irqs.len() != 1 { + // println!("DUPE: {p} {s} {irqs:?}"); + // } + // } + } + + chip_interrupts.insert((parsed.name, parsed.version), irqs2); + } + + Ok(Self(chip_interrupts)) + } +} + +fn tokenize_name(name: &str) -> Vec { + // 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_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 { + const PERI_OVERRIDE: &[(&str, &[&str])] = &[ + ("USB_FS", &["USB"]), + ("OTG_HS", &["USB_OTG_HS"]), + ("OTG_FS", &["USB_OTG_FS"]), + ("USB", &["USB_DRD_FS"]), + ("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 { + const IRQ_SIGNALS_MAP: &[(&str, &[&str])] = &[ + ("CAN", &["TX", "RX0", "RX1", "SCE"]), + ("FDCAN", &["IT0", "IT1", "CAL"]), + ("I2C", &["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"]), + ("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"]), + ]; + + for (prefix, signals) in IRQ_SIGNALS_MAP { + if peri.starts_with(prefix) { + return signals.iter().map(ToString::to_string).collect(); + } + } + vec!["GLOBAL".to_string()] +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..fe7d794 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,102 @@ +mod chips; +mod dma; +mod docs; +mod gpio_af; +mod header; +mod interrupts; +mod memory; +mod rcc; + +#[macro_export] +macro_rules! regex { + ($re:literal) => {{ + ::ref_thread_local::ref_thread_local! { + static managed REGEX: ::regex::Regex = ::regex::Regex::new($re).unwrap(); + } + >::borrow(®EX) + }}; +} + +struct Stopwatch { + start: std::time::Instant, + section_start: Option, +} + +impl Stopwatch { + fn new() -> Self { + eprintln!("Starting timer"); + let start = std::time::Instant::now(); + Self { + start, + section_start: None, + } + } + + fn section(&mut self, status: &str) { + let now = std::time::Instant::now(); + self.print_done(now); + eprintln!(" {status}"); + self.section_start = Some(now); + } + + fn stop(self) { + let now = std::time::Instant::now(); + self.print_done(now); + let total_elapsed = now - self.start; + eprintln!("Total time: {:.2} seconds", total_elapsed.as_secs_f32()); + } + + fn print_done(&self, now: std::time::Instant) { + if let Some(section_start) = self.section_start { + let elapsed = now - section_start; + eprintln!(" done in {:.2} seconds", elapsed.as_secs_f32()); + } + } +} + +fn main() -> anyhow::Result<()> { + let mut stopwatch = Stopwatch::new(); + + stopwatch.section("Parsing headers"); + let headers = header::Headers::parse()?; + + stopwatch.section("Parsing other stuff"); + + // stopwatch.section("Parsing memories"); + let memories = memory::Memories::parse()?; + + // stopwatch.section("Parsing interrupts"); + let chip_interrupts = interrupts::ChipInterrupts::parse()?; + + // stopwatch.section("Parsing RCC registers"); + let peripheral_to_clock = rcc::PeripheralToClock::parse()?; + + // stopwatch.section("Parsing docs"); + let docs = docs::Docs::parse()?; + + // stopwatch.section("Parsing DMA"); + let dma_channels = dma::DmaChannels::parse()?; + + // stopwatch.section("Parsing GPIO AF"); + let af = gpio_af::Af::parse()?; + + stopwatch.section("Parsing chip groups"); + let (chips, chip_groups) = chips::parse_groups()?; + + stopwatch.section("Processing chips"); + chips::dump_all_chips( + chip_groups, + headers, + af, + chip_interrupts, + peripheral_to_clock, + dma_channels, + chips, + memories, + docs, + )?; + + stopwatch.stop(); + + Ok(()) +} diff --git a/src/memory.rs b/src/memory.rs new file mode 100644 index 0000000..0f5a891 --- /dev/null +++ b/src/memory.rs @@ -0,0 +1,348 @@ +use std::fs; + +#[derive(Debug, PartialEq)] +struct Memory { + pub device_id: u16, + pub names: Vec, + pub ram: Ram, + pub flash: Option, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +struct Ram { + pub address: u32, + pub bytes: u32, +} + +#[derive(Clone, Debug, PartialEq)] +struct Flash { + pub address: u32, + pub bytes: u32, + pub settings: stm32_data_serde::chip::memory::Settings, +} + +fn splat_names(base: &str, parts: Vec<&str>) -> Vec { + let mut names = Vec::new(); + for part in parts { + if part.starts_with("STM32") { + names.push(base.to_string()); + } else if part.starts_with(&base[5..6]) { + names.push("STM32".to_string() + part); + } else { + let diff = base.len() - part.len(); + names.push((base[..diff]).to_string() + part); + } + } + + names +} + +fn split_names(str: &str) -> Vec { + let mut cleaned = Vec::new(); + let mut current_base = None; + for name in str.split('/') { + let name = name.split(' ').next().unwrap().trim(); + if name.contains('-') { + let parts: Vec<_> = name.split('-').collect(); + current_base = parts.first().map(ToString::to_string); + let splatted = splat_names(¤t_base.unwrap(), parts); + current_base = splatted.first().map(Clone::clone); + cleaned.extend(splatted); + } else if name.starts_with("STM32") { + current_base = Some(name.to_string()); + cleaned.push(name.to_string()) + } else if name.starts_with(¤t_base.clone().unwrap()[5..6]) { + // names.append('STM32' + name) + cleaned.push("STM32".to_string() + name); + } else { + cleaned.push( + (current_base.clone().unwrap()[0..(current_base.clone().unwrap().len() - name.len())]).to_string() + + name, + ) + } + } + + cleaned +} + +mod xml { + use serde::Deserialize; + + pub fn from_hex<'de, T, D>(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + T: num::Num, + T::FromStrRadixErr: std::fmt::Display, + { + use serde::de::Error; + let s: &str = Deserialize::deserialize(deserializer)?; + let s = s.trim(); + let (prefix, num) = s.split_at(2); + if prefix != "0x" && prefix != "0X" { + panic!("no hex prefix"); + } + T::from_str_radix(num, 16).map_err(D::Error::custom) + } + + pub fn opt_from_hex<'de, T, D>(deserializer: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + T: num::Num, + T::FromStrRadixErr: std::fmt::Display, + { + Ok(Some(from_hex(deserializer)?)) + } + + #[derive(Debug, Deserialize, PartialEq)] + pub struct Root { + #[serde(rename = "Device")] + pub device: root::Device, + } + + mod root { + use serde::Deserialize; + + use super::from_hex; + + #[derive(Debug, Deserialize, PartialEq)] + pub struct Device { + #[serde(rename = "DeviceID", deserialize_with = "from_hex")] + pub device_id: u16, + #[serde(rename = "Name")] + pub name: String, + #[serde(rename = "Peripherals")] + pub peripherals: device::Peripherals, + } + + mod device { + use serde::Deserialize; + + #[derive(Debug, Deserialize, PartialEq)] + pub struct Peripherals { + #[serde(rename = "Peripheral")] + pub peripharal: Vec, + } + + mod peripherals { + use serde::Deserialize; + + use super::super::super::opt_from_hex; + + #[derive(Debug, Deserialize, PartialEq)] + pub struct Peripheral { + #[serde(rename = "Name")] + // pub name: peripheral::Name, + pub name: String, + #[serde(rename = "ErasedValue", deserialize_with = "opt_from_hex", default)] + pub erased_value: Option, + #[serde(rename = "Configuration", default)] + pub configuration: Vec, + } + + mod peripheral { + use serde::Deserialize; + + use super::super::super::super::opt_from_hex; + + #[derive(Debug, Deserialize, PartialEq)] + pub struct Configuration { + #[serde(rename = "Parameters", default)] + pub parameters: Option, + #[serde(rename = "Allignement", deserialize_with = "opt_from_hex", default)] + pub allignement: Option, + #[serde(rename = "Bank")] + pub bank: Vec, + } + + mod configuration { + use serde::Deserialize; + + use super::super::super::super::super::from_hex; + + #[derive(Debug, Deserialize, PartialEq)] + pub struct Parameters { + #[serde(deserialize_with = "from_hex")] + pub address: u32, + #[serde(deserialize_with = "from_hex")] + pub size: u32, + } + + #[derive(Debug, Deserialize, PartialEq)] + pub struct Bank { + #[serde(rename = "Field", default)] + pub field: Vec, + } + + mod bank { + use serde::Deserialize; + + #[derive(Debug, Deserialize, PartialEq)] + pub struct Field { + #[serde(rename = "Parameters")] + pub parameters: super::Parameters, + } + } + } + } + } + } + } +} +pub struct Memories(Vec); + +impl Memories { + pub fn parse() -> anyhow::Result { + let mut paths: Vec<_> = glob::glob("sources/cubeprogdb/db/*.xml") + .unwrap() + .map(Result::unwrap) + .collect(); + paths.sort(); + + let mut memories = Vec::new(); + + for f in paths { + // println!("Parsing {f:?}"); + let file = fs::read_to_string(f)?; + let parsed: xml::Root = quick_xml::de::from_str(&file)?; + // dbg!(&parsed); + + let device_id = parsed.device.device_id; + let names = split_names(&parsed.device.name); + + let mut ram = None; + let mut flash = None; + + for peripheral in parsed.device.peripherals.peripharal { + if peripheral.name == "Embedded SRAM" && ram.is_none() { + let config = peripheral.configuration.first().unwrap(); + let parameters = config.parameters.as_ref().unwrap(); + ram = Some(Ram { + address: parameters.address, + bytes: parameters.size, + }); + } + + if peripheral.name == "Embedded Flash" && flash.is_none() { + let config = peripheral.configuration.first().unwrap(); + let parameters = config.parameters.as_ref().unwrap(); + let bank = config.bank.first().unwrap(); + let erase_size = bank.field.iter().map(|field| field.parameters.size).max().unwrap(); + + flash = Some(Flash { + address: parameters.address, + bytes: parameters.size, + settings: stm32_data_serde::chip::memory::Settings { + erase_value: peripheral.erased_value.unwrap(), + write_size: config.allignement.unwrap(), + erase_size, + }, + }); + } + } + + memories.push(Memory { + device_id, + names, + ram: ram.unwrap(), + flash, + }); + } + + // The chips below are missing from cubeprogdb + memories.push(Memory { + device_id: 0, + names: vec!["STM32F302xD".to_string()], + ram: Ram { + address: 0x20000000, + bytes: 64 * 1024, + }, + flash: Some(Flash { + address: 0x08000000, + bytes: 384 * 1024, + settings: stm32_data_serde::chip::memory::Settings { + erase_value: 0xFF, + write_size: 8, + erase_size: 2048, + }, + }), + }); + + memories.push(Memory { + device_id: 0, + names: vec!["STM32F303xD".to_string()], + ram: Ram { + address: 0x20000000, + bytes: 80 * 1024, + }, + flash: Some(Flash { + address: 0x08000000, + bytes: 384 * 1024, + settings: stm32_data_serde::chip::memory::Settings { + erase_value: 0xFF, + write_size: 8, + erase_size: 2048, + }, + }), + }); + + memories.push(Memory { + device_id: 0, + names: vec!["STM32L100x6".to_string()], + ram: Ram { + address: 0x20000000, + bytes: 32 * 1024, + }, + flash: Some(Flash { + address: 0x08000000, + bytes: 4 * 1024, + settings: stm32_data_serde::chip::memory::Settings { + erase_value: 0xFF, + write_size: 4, + erase_size: 256, + }, + }), + }); + + Ok(Self(memories)) + } + + fn lookup_chip(&self, chip_name: &str) -> &Memory { + for each in &self.0 { + for name in &each.names { + if is_chip_name_match(name, chip_name) { + return each; + } + } + } + panic!("could not find memory information for {chip_name}"); + } + + pub fn determine_ram_size(&self, chip_name: &str) -> u32 { + self.lookup_chip(chip_name).ram.bytes + } + + pub fn determine_flash_size(&self, chip_name: &str) -> u32 { + self.lookup_chip(chip_name).flash.as_ref().unwrap().bytes + } + + pub fn determine_flash_settings(&self, chip_name: &str) -> stm32_data_serde::chip::memory::Settings { + self.lookup_chip(chip_name).flash.as_ref().unwrap().settings.clone() + } + + pub fn determine_device_id(&self, chip_name: &str) -> u16 { + self.lookup_chip(chip_name).device_id + } +} + +fn is_chip_name_match(pattern: &str, chip_name: &str) -> bool { + let mut chip_name = chip_name.replace("STM32F479", "STM32F469"); // F479 is missing, it's the same as F469. + chip_name = chip_name.replace("STM32G050", "STM32G051"); // same... + chip_name = chip_name.replace("STM32G060", "STM32G061"); // same... + chip_name = chip_name.replace("STM32G070", "STM32G071"); // same... + chip_name = chip_name.replace("STM32G0B0", "STM32G0B1"); // same... + chip_name = chip_name.replace("STM32G4A", "STM32G49"); // same... + chip_name = chip_name.replace("STM32L422", "STM32L412"); // same... + chip_name = chip_name.replace("STM32WB30", "STM32WB35"); // same... + let pattern = pattern.replace('x', "."); + regex::Regex::new(&pattern).unwrap().is_match(&chip_name) +} diff --git a/src/rcc.rs b/src/rcc.rs new file mode 100644 index 0000000..403b7ad --- /dev/null +++ b/src/rcc.rs @@ -0,0 +1,92 @@ +use std::collections::HashMap; + +use crate::regex; + +#[derive(Debug)] +pub struct PeripheralToClock( + HashMap<(String, String, String), HashMap>, +); + +impl PeripheralToClock { + pub fn parse() -> anyhow::Result { + let mut peripheral_to_clock = HashMap::new(); + for f in glob::glob("data/registers/rcc_*")? { + let f = f?; + let ff = f + .file_name() + .unwrap() + .to_string_lossy() + .strip_prefix("rcc_") + .unwrap() + .strip_suffix(".yaml") + .unwrap() + .to_string(); + let mut family_clocks = HashMap::new(); + let y: chiptool::ir::IR = serde_yaml::from_str(&std::fs::read_to_string(f)?)?; + for (reg, body) in &y.fieldsets { + let key = format!("fieldset/{reg}"); + if let Some(m) = regex!(r"^fieldset/((A[PH]B\d?)|GPIO)[LH]?ENR\d?$").captures(&key) { + let clock = m.get(1).unwrap().as_str(); + let clock = match clock { + "AHB" => "AHB1", + "APB" => "APB1", + clock => clock, + }; + for field in &body.fields { + if let Some(peri) = field.name.strip_suffix("EN") { + // Timers are a bit special, they may have a x2 freq + let peri_clock = { + if regex!(r"^TIM\d+$").is_match(peri) { + format!("{clock}_TIM") + } else { + clock.to_string() + } + }; + + let mut reset = None; + if let Some(rstr) = y.fieldsets.get(®.replace("ENR", "RSTR")) { + if let Some(_field) = + rstr.fields.iter().find(|field| field.name == format!("{peri}RST")) + { + reset = Some(stm32_data_serde::chip::core::peripheral::rcc::Reset { + register: reg.replace("ENR", "RSTR"), + field: format!("{peri}RST"), + }); + } + } + + let res = stm32_data_serde::chip::core::peripheral::Rcc { + clock: peri_clock, + enable: stm32_data_serde::chip::core::peripheral::rcc::Enable { + register: reg.clone(), + field: field.name.clone(), + }, + reset, + }; + + family_clocks.insert(peri.to_string(), res); + } + } + } + } + peripheral_to_clock.insert(("rcc".to_string(), ff, "RCC".to_string()), family_clocks); + } + + Ok(Self(peripheral_to_clock)) + } + + pub fn match_peri_clock( + &self, + rcc_block: (String, String, String), + peri_name: &str, + ) -> Option<&stm32_data_serde::chip::core::peripheral::Rcc> { + let clocks = self.0.get(&rcc_block)?; + if let Some(res) = clocks.get(peri_name) { + Some(res) + } else if let Some(peri_name) = peri_name.strip_suffix('1') { + self.match_peri_clock(rcc_block, peri_name) + } else { + None + } + } +} diff --git a/stm32-data-serde/Cargo.toml b/stm32-data-serde/Cargo.toml new file mode 100644 index 0000000..983e670 --- /dev/null +++ b/stm32-data-serde/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "stm32-data-serde" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serde = { version = "1.0.147", features = ["derive"] } + +[dev-dependencies] +itertools = "0.10.5" +rayon = "1.5.3" +serde_json = "1.0.87" diff --git a/stm32-data-serde/src/lib.rs b/stm32-data-serde/src/lib.rs new file mode 100644 index 0000000..faab08d --- /dev/null +++ b/stm32-data-serde/src/lib.rs @@ -0,0 +1,287 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)] +pub struct Chip { + pub name: String, + pub family: String, + pub line: String, + pub die: String, + pub device_id: u16, + pub packages: Vec, + pub memory: Vec, + pub docs: Vec, + pub cores: Vec, +} + +pub mod chip { + use serde::{Deserialize, Serialize}; + + #[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)] + pub struct Package { + pub name: String, + pub package: String, + } + + #[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)] + pub struct Memory { + pub name: String, + pub kind: memory::Kind, + pub address: u32, + pub size: u32, + #[serde(skip_serializing_if = "Option::is_none")] + pub settings: Option, + } + + pub mod memory { + use serde::{Deserialize, Serialize}; + + #[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)] + #[serde(rename_all = "lowercase")] + pub enum Kind { + Flash, + Ram, + } + + #[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)] + pub struct Settings { + pub erase_size: u32, + pub write_size: u32, + pub erase_value: u8, + } + } + + #[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)] + pub struct Doc { + pub r#type: String, + pub title: String, + pub name: String, + pub url: String, + } + + #[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)] + pub struct Core { + pub name: String, + pub peripherals: Vec, + pub interrupts: Vec, + pub dma_channels: Vec, + } + + pub mod core { + use serde::{Deserialize, Serialize}; + + #[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)] + pub struct Peripheral { + pub name: String, + pub address: u32, + #[serde(skip_serializing_if = "Option::is_none")] + pub registers: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub rcc: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub pins: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub interrupts: Option>, // TODO: This should just be a Vec + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub dma_channels: Vec, + } + + pub mod peripheral { + use serde::{Deserialize, Serialize}; + + #[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)] + pub struct Registers { + pub kind: String, + pub version: String, + pub block: String, + } + + #[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)] + pub struct Rcc { + pub clock: String, + pub enable: rcc::Enable, + #[serde(skip_serializing_if = "Option::is_none")] + pub reset: Option, + } + + pub mod rcc { + use serde::{Deserialize, Serialize}; + + #[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)] + pub struct Enable { + pub register: String, + pub field: String, + } + + #[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)] + pub struct Reset { + pub register: String, + pub field: String, + } + } + + #[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)] + pub struct Pin { + pub pin: pin::Pin, + pub signal: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub af: Option, + } + + pub mod pin { + use serde::{Deserialize, Serialize}; + + #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)] + pub struct Pin { + pub port: char, + pub num: u8, + } + + impl Pin { + pub fn parse(pin: &str) -> Option { + let mut chars = pin.chars(); + let p = chars.next()?; + if p != 'P' { + return None; + } + let port = chars.next()?; + let num = chars.as_str().parse().ok()?; + + Some(Self { port, num }) + } + } + + impl std::fmt::Display for Pin { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "P{}{}", self.port, self.num) + } + } + + impl Serialize for Pin { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&format!("{self}")) + } + } + + struct PinVisitor; + + impl<'de> serde::de::Visitor<'de> for PinVisitor { + type Value = Pin; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("pin") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + Ok(Pin::parse(v).unwrap()) + } + } + + impl<'de> Deserialize<'de> for Pin { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_str(PinVisitor) + } + } + } + + #[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)] + pub struct Interrupt { + pub signal: String, + pub interrupt: String, + } + + #[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)] + pub struct DmaChannel { + pub signal: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub dma: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub channel: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub dmamux: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub request: Option, + } + } + + #[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)] + pub struct Interrupt { + pub name: String, + pub number: u8, + } + + #[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)] + pub struct DmaChannels { + pub name: String, + pub dma: String, + pub channel: u8, + #[serde(skip_serializing_if = "Option::is_none")] + pub dmamux: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub dmamux_channel: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub supports_2d: Option, + } + } +} + +#[cfg(test)] +mod tests { + use std::borrow::Cow; + use std::path::Path; + use std::{fs, str}; + + use super::*; + + fn normalize_line(line: &str) -> Cow<'_, str> { + // The python script saves with 4 spaces instead of 2 + let line = line.trim_start(); + + // The python script escapes unicode + let mut line = Cow::Borrowed(line); + for symbol in [("\\u00ae", "\u{00ae}"), ("\\u2122", "\u{2122}")] { + if line.contains(symbol.0) { + line = Cow::Owned(line.replace(symbol.0, symbol.1)); + } + } + + line + } + + fn normalize(file: &[u8]) -> impl Iterator> + '_ { + str::from_utf8(file).unwrap().lines().map(normalize_line) + } + + fn check_file(path: impl AsRef) { + println!("Checking {:?}", path.as_ref()); + let original = fs::read(path).unwrap(); + let parsed: Chip = serde_json::from_slice(&original).unwrap(); + let reencoded = serde_json::to_vec_pretty(&parsed).unwrap(); + itertools::assert_equal(normalize(&original), normalize(&reencoded)) + } + + const CHIPS_DIR: &str = "../data/chips/"; + + #[test] + fn test_one() { + let path = Path::new(CHIPS_DIR).join("STM32F030C6.json"); + check_file(path); + } + + #[test] + fn test_all() { + use rayon::prelude::*; + + Path::new(CHIPS_DIR).read_dir().unwrap().par_bridge().for_each(|chip| { + check_file(chip.unwrap().path()); + }); + } +} diff --git a/stm32data/__main__.py b/stm32data/__main__.py deleted file mode 100755 index 3c0e99d..0000000 --- a/stm32data/__main__.py +++ /dev/null @@ -1,1347 +0,0 @@ -#!/usr/bin/env python3 - -import sys -import xmltodict -import functools -import re -import json -import os -from glob import glob -from pathlib import Path - -from stm32data import yaml, header, interrupts, memory -from stm32data.util import * - - -def corename(d): - # print("CHECKING CORENAME", d) - if m := re.match(r'.*Cortex-M(\d+)(\+?)\s*(.*)', d): - name = "cm" + str(m.group(1)) - if m.group(2) == "+": - name += "p" - if m.group(3) == "secure": - name += "s" - return name - - -def children(x, key): - r = x.get(key) - if r is None: - return [] - if type(r) is list: - return r - return [r] - - -def expand_name(name): - if '(' not in name: - return [name] - prefix, suffix = name.split('(') - letters, suffix = suffix.split(')') - return [prefix + x + suffix for x in letters.split('-')] - - -# ======================================== -# ======================================== - -FAKE_PERIPHERALS = [ - # These are real peripherals but with special handling - 'NVIC', - 'GPIO', - 'DMA', - - # IRTIM is just TIM16+TIM17 - 'IRTIM', - - # I2S is just SPI on disguise - 'I2S1', - 'I2S2', - 'I2S3', - 'I2S4', - 'I2S5', - 'I2S6', - 'I2S7', - 'I2S8', - - # We add this as ghost peri - 'SYS', - - # These are software libraries - 'FREERTOS', - 'PDM2PCM', - 'FATFS', - 'LIBJPEG', - 'MBEDTLS', - 'LWIP', - 'USB_HOST', - 'USB_DEVICE', - 'GUI_INTERFACE', - 'TRACER_EMB', - 'TOUCHSENSING', -] - -perimap = [ - ('.*:USART:sci2_v1_1', ('usart', 'v1', 'USART')), - ('.*:USART:sci2_v1_2_F1', ('usart', 'v1', 'USART')), - ('.*:USART:sci2_v1_2', ('usart', 'v1', 'USART')), - ('.*:USART:sci2_v2_0', ('usart', 'v2', 'USART')), - ('.*:USART:sci2_v2_1', ('usart', 'v2', 'USART')), - ('.*:USART:sci2_v2_2', ('usart', 'v2', 'USART')), - ('.*:USART:sci3_v1_0', ('usart', 'v2', 'USART')), - ('.*:USART:sci3_v1_1', ('usart', 'v2', 'USART')), - ('.*:USART:sci3_v1_2', ('usart', 'v2', 'USART')), - ('.*:USART:sci3_v2_0', ('usart', 'v2', 'USART')), - ('.*:USART:sci3_v2_1', ('usart', 'v2', 'USART')), - ('.*:UART:sci2_v1_2_F4', ('usart', 'v1', 'USART')), - ('.*:UART:sci2_v2_1', ('usart', 'v2', 'USART')), - ('.*:UART:sci2_v3_0', ('usart', 'v2', 'USART')), - ('.*:UART:sci2_v3_1', ('usart', 'v2', 'USART')), - ('.*:LPUART:sci3_v1_1', ('lpuart', 'v1', 'LPUART')), - ('.*:LPUART:sci3_v1_2', ('lpuart', 'v2', 'LPUART')), - ('.*:LPUART:sci3_v1_3', ('lpuart', 'v2', 'LPUART')), - ('.*:LPUART:sci3_v1_4', ('lpuart', 'v2', 'LPUART')), - ('.*:RNG:rng1_v1_1', ('rng', 'v1', 'RNG')), - ('.*:RNG:rng1_v2_0', ('rng', 'v1', 'RNG')), - ('.*:RNG:rng1_v2_1', ('rng', 'v1', 'RNG')), - ('.*:RNG:rng1_v3_1', ('rng', 'v1', 'RNG')), - ('.*:SPI:spi2_v1_4', ('spi', 'f1', 'SPI')), - ('.*:SPI:spi2s1_v2_1', ('spi', 'v1', 'SPI')), - ('.*:SPI:spi2s1_v2_2', ('spi', 'v1', 'SPI')), - ('.*:SPI:spi2s1_v3_2', ('spi', 'v2', 'SPI')), - ('.*:SPI:spi2s1_v3_3', ('spi', 'v2', 'SPI')), - ('.*:SPI:spi2s1_v3_5', ('spi', 'v2', 'SPI')), - ('.*:SUBGHZSPI:.*', ('spi', 'v2', 'SPI')), - ('.*:SPI:spi2s1_v3_1', ('spi', 'v2', 'SPI')), - ('.*:SPI:spi2s2_v1_1', ('spi', 'v3', 'SPI')), - ('.*:SPI:spi2s2_v1_0', ('spi', 'v3', 'SPI')), - ('.*:SPI:spi2s3_v1_1', ('spi', 'v4', 'SPI')), - ('.*:I2C:i2c1_v1_5', ('i2c', 'v1', 'I2C')), - ('.*:I2C:i2c2_v1_1', ('i2c', 'v2', 'I2C')), - ('.*:I2C:i2c2_v1_1F7', ('i2c', 'v2', 'I2C')), - ('.*:I2C:i2c2_v1_1U5', ('i2c', 'v2', 'I2C')), - - ('.*:DAC:dacif_v1_1', ('dac', 'v1', 'DAC')), - ('.*:DAC:dacif_v1_1F1', ('dac', 'v1', 'DAC')), - ('.*:DAC:dacif_v2_0', ('dac', 'v2', 'DAC')), - ('.*:DAC:dacif_v3_0', ('dac', 'v2', 'DAC')), - - ('.*:ADC:aditf_v2_5F1', ('adc', 'f1', 'ADC')), - ('.*:ADC:aditf4_v1_1', ('adc', 'v1', 'ADC')), - ('.*:ADC:aditf2_v1_1', ('adc', 'v2', 'ADC')), - ('.*:ADC:aditf5_v2_0', ('adc', 'v3', 'ADC')), - ('.*:ADC:aditf5_v3_0', ('adc', 'v4', 'ADC')), - ('STM32G0.*:ADC:.*', ('adc', 'g0', 'ADC')), - ('STM32G0.*:ADC_COMMON:.*', ('adccommon', 'v3', 'ADC_COMMON')), - ('.*:ADC_COMMON:aditf2_v1_1', ('adccommon', 'v2', 'ADC_COMMON')), - ('.*:ADC_COMMON:aditf5_v2_0', ('adccommon', 'v3', 'ADC_COMMON')), - ('.*:ADC_COMMON:aditf4_v3_0_WL', ('adccommon', 'v3', 'ADC_COMMON')), - ('STM32H7.*:ADC_COMMON:.*', ('adccommon', 'v4', 'ADC_COMMON')), - ('STM32H7.*:ADC3_COMMON:.*', ('adccommon', 'v4', 'ADC_COMMON')), - - ('.*:DCMI:.*', ('dcmi', 'v1', 'DCMI')), - ('STM32F0.*:SYSCFG:.*', ('syscfg', 'f0', 'SYSCFG')), - ('STM32F2.*:SYSCFG:.*', ('syscfg', 'f2', 'SYSCFG')), - ('STM32F3.*:SYSCFG:.*', ('syscfg', 'f3', 'SYSCFG')), - ('STM32F4.*:SYSCFG:.*', ('syscfg', 'f4', 'SYSCFG')), - ('STM32F7.*:SYSCFG:.*', ('syscfg', 'f7', 'SYSCFG')), - ('STM32L0.*:SYSCFG:.*', ('syscfg', 'l0', 'SYSCFG')), - ('STM32L1.*:SYSCFG:.*', ('syscfg', 'l1', 'SYSCFG')), - ('STM32L4.*:SYSCFG:.*', ('syscfg', 'l4', 'SYSCFG')), - ('STM32L5.*:SYSCFG:.*', ('syscfg', 'l5', 'SYSCFG')), - ('STM32G0.*:SYSCFG:.*', ('syscfg', 'g0', 'SYSCFG')), - ('STM32G4.*:SYSCFG:.*', ('syscfg', 'g4', 'SYSCFG')), - ('STM32H7.*:SYSCFG:.*', ('syscfg', 'h7', 'SYSCFG')), - ('STM32U5.*:SYSCFG:.*', ('syscfg', 'u5', 'SYSCFG')), - ('STM32WB.*:SYSCFG:.*', ('syscfg', 'wb', 'SYSCFG')), - ('STM32WL5.*:SYSCFG:.*', ('syscfg', 'wl5', 'SYSCFG')), - ('STM32WLE.*:SYSCFG:.*', ('syscfg', 'wle', 'SYSCFG')), - - ('.*:IWDG:iwdg1_v1_1', ('iwdg', 'v1', 'IWDG')), - ('.*:IWDG:iwdg1_v2_0', ('iwdg', 'v2', 'IWDG')), - ('.*:WWDG:wwdg1_v1_0', ('wwdg', 'v1', 'WWDG')), - ('.*:JPEG:jpeg1_v1_0', ('jpeg', 'v1', 'JPEG')), - ('.*:LPTIM:F7_lptimer1_v1_1', ('lptim', 'v1', 'LPTIM')), - ('.*:LTDC:lcdtft1_v1_1', ('ltdc', 'v1', 'LTDC')), - ('.*:MDIOS:mdios1_v1_0', ('mdios', 'v1', 'MDIOS')), - ('.*:QUADSPI:quadspi1_v1_0', ('quadspi', 'v1', 'QUADSPI')), - ('STM32F1.*:BKP.*', ('bkp', 'v1', 'BKP')), - ('.*:RTC:rtc1_v1_1', ('rtc', 'v1', 'RTC')), - ('STM32F0.*:RTC:rtc2_.*', ('rtc', 'v2f0', 'RTC')), - ('STM32F2.*:RTC:rtc2_.*', ('rtc', 'v2f2', 'RTC')), - ('STM32F3.*:RTC:rtc2_.*', ('rtc', 'v2f3', 'RTC')), - ('STM32F4.*:RTC:rtc2_.*', ('rtc', 'v2f4', 'RTC')), - ('STM32F7.*:RTC:rtc2_.*', ('rtc', 'v2f7', 'RTC')), - ('STM32H7.*:RTC:rtc2_.*', ('rtc', 'v2h7', 'RTC')), - ('STM32L0.*:RTC:rtc2_.*', ('rtc', 'v2l0', 'RTC')), - ('STM32L1.*:RTC:rtc2_.*', ('rtc', 'v2l1', 'RTC')), - ('STM32L4.*:RTC:rtc2_.*', ('rtc', 'v2l4', 'RTC')), - ('STM32WB.*:RTC:rtc2_.*', ('rtc', 'v2wb', 'RTC')), - ('STM32U5.*:RTC:rtc2_.*', ('rtc', 'v3u5', 'RTC')), # Cube says v2, but it's v3 with security stuff - ('.*:RTC:rtc3_v1_0', ('rtc', 'v3', 'RTC')), - ('.*:RTC:rtc3_v1_1', ('rtc', 'v3', 'RTC')), - ('.*:RTC:rtc3_v2_0', ('rtc', 'v3', 'RTC')), - ('.*:RTC:rtc3_v3_0', ('rtc', 'v3', 'RTC')), - ('.*:SAI:sai1_v1_1', ('sai', 'v1', 'SAI')), - ('.*:SDIO:sdmmc_v1_2', ('sdmmc', 'v1', 'SDMMC')), - ('.*:SDMMC:sdmmc_v1_3', ('sdmmc', 'v1', 'SDMMC')), - ('.*:SPDIFRX:spdifrx1_v1_0', ('spdifrx', 'v1', 'SPDIFRX')), - - # USB - ('STM32F302.[BC].*:USB:.*', ('usb', 'v1_x1', 'USB')), - ('STM32F302.[68DE].*:USB:.*', ('usb', 'v2', 'USB')), - ('STM32F303.[BC].*:USB:.*', ('usb', 'v1_x1', 'USB')), - ('STM32F303.[DE].*:USB:.*', ('usb', 'v2', 'USB')), - ('STM32F373.*:USB:.*', ('usb', 'v1_x2', 'USB')), - ('STM32(F1|L1).*:USB:.*', ('usb', 'v1_x1', 'USB')), - ('.*:USB:.*', ('usb', 'v3', 'USB')), - - # USB OTG - ('.*:USB_OTG_FS:otgfs1_v1_.*', ('otgfs', 'v1', 'OTG_FS')), - ('.*:USB_OTG_FS:otgfs1_v3_.*', ('otgfs', 'v1', 'OTG_FS')), - ('.*:USB_OTG_HS:otghs1_v1_.*', ('otghs', 'v1', 'OTG_HS')), - - ('STM32F0.*:RCC:.*', ('rcc', 'f0', 'RCC')), - ('STM32F100.*:RCC:.*', ('rcc', 'f100', 'RCC')), - ('STM32F10[123].*:RCC:.*', ('rcc', 'f1', 'RCC')), - ('STM32F10[57].*:RCC:.*', ('rcc', 'f1cl', 'RCC')), - ('STM32F2.*:RCC:.*', ('rcc', 'f2', 'RCC')), - ('STM32F3.*:RCC:.*', ('rcc', 'f3', 'RCC')), - ('STM32F410.*:RCC:.*', ('rcc', 'f410', 'RCC')), - ('STM32F4.*:RCC:.*', ('rcc', 'f4', 'RCC')), - ('STM32F7.*:RCC:.*', ('rcc', 'f7', 'RCC')), - ('STM32G0.*:RCC:.*', ('rcc', 'g0', 'RCC')), - ('STM32G4.*:RCC:.*', ('rcc', 'g4', 'RCC')), - ('STM32H7[AB].*:RCC:.*', ('rcc', 'h7ab', 'RCC')), - ('STM32H7.*:RCC:.*', ('rcc', 'h7', 'RCC')), - ('STM32L0.*:RCC:.*', ('rcc', 'l0', 'RCC')), - ('STM32L1.*:RCC:.*', ('rcc', 'l1', 'RCC')), - ('STM32L4.*:RCC:.*', ('rcc', 'l4', 'RCC')), - ('STM32L5.*:RCC:.*', ('rcc', 'l5', 'RCC')), - ('STM32U5.*:RCC:.*', ('rcc', 'u5', 'RCC')), - ('STM32WB.*:RCC:.*', ('rcc', 'wb', 'RCC')), - ('STM32WL5.*:RCC:.*', ('rcc', 'wl5', 'RCC')), - ('STM32WLE.*:RCC:.*', ('rcc', 'wle', 'RCC')), - - ('STM32F1.*:SPI[1234]:.*', ('spi', 'f1', 'SPI')), - ('STM32F3.*:SPI[1234]:.*', ('spi', 'v2', 'SPI')), - - ('STM32F1.*:AFIO:.*', ('afio', 'f1', 'AFIO')), - - ('STM32L5.*:EXTI:.*', ('exti', 'l5', 'EXTI')), - ('STM32G0.*:EXTI:.*', ('exti', 'g0', 'EXTI')), - ('STM32H7.*:EXTI:.*', ('exti', 'h7', 'EXTI')), - ('STM32U5.*:EXTI:.*', ('exti', 'u5', 'EXTI')), - ('STM32WB.*:EXTI:.*', ('exti', 'w', 'EXTI')), - ('STM32WL5.*:EXTI:.*', ('exti', 'w', 'EXTI')), - ('STM32WLE.*:EXTI:.*', ('exti', 'wle', 'EXTI')), - ('.*:EXTI:.*', ('exti', 'v1', 'EXTI')), - - ('STM32L0.*:CRS:.*', ('crs', 'l0', 'CRS')), - ('.*SDMMC:sdmmc2_v1_0', ('sdmmc', 'v2', 'SDMMC')), - ('STM32G0.*:PWR:.*', ('pwr', 'g0', 'PWR')), - ('STM32G4.*:PWR:.*', ('pwr', 'g4', 'PWR')), - ('STM32H7(42|43|53|50).*:PWR:.*', ('pwr', 'h7', 'PWR')), - ('STM32H7.*:PWR:.*', ('pwr', 'h7smps', 'PWR')), - ('STM32F2.*:PWR:.*', ('pwr', 'f2', 'PWR')), - ('STM32F3.*:PWR:.*', ('pwr', 'f3', 'PWR')), - ('STM32F4.*:PWR:.*', ('pwr', 'f4', 'PWR')), - ('STM32F7.*:PWR:.*', ('pwr', 'f7', 'PWR')), - ('STM32L1.*:PWR:.*', ('pwr', 'l1', 'PWR')), - ('STM32L4.*:PWR:.*', ('pwr', 'l4', 'PWR')), - ('STM32L5.*:PWR:.*', ('pwr', 'l5', 'PWR')), - ('STM32U5.*:PWR:.*', ('pwr', 'u5', 'PWR')), - ('STM32WL.*:PWR:.*', ('pwr', 'wl5', 'PWR')), - ('STM32WB.*:PWR:.*', ('pwr', 'wb55', 'PWR')), - ('STM32H7.*:FLASH:.*', ('flash', 'h7', 'FLASH')), - ('STM32F0.*:FLASH:.*', ('flash', 'f0', 'FLASH')), - ('STM32F1.*:FLASH:.*', ('flash', 'f1', 'FLASH')), - ('STM32F2.*:FLASH:.*', ('flash', 'f2', 'FLASH')), - ('STM32F3.*:FLASH:.*', ('flash', 'f3', 'FLASH')), - ('STM32F4.*:FLASH:.*', ('flash', 'f4', 'FLASH')), - ('STM32F7.*:FLASH:.*', ('flash', 'f7', 'FLASH')), - ('STM32L0[0-9]2.*:FLASH:.*', ('flash', 'l0', 'FLASH')), - ('STM32L1.*:FLASH:.*', ('flash', 'l1', 'FLASH')), - ('STM32L4.*:FLASH:.*', ('flash', 'l4', 'FLASH')), - ('STM32L5.*:FLASH:.*', ('flash', 'l5', 'FLASH')), - ('STM32U5.*:FLASH:.*', ('flash', 'u5', 'FLASH')), - ('STM32WB.*:FLASH:.*', ('flash', 'wb', 'FLASH')), - ('STM32WL.*:FLASH:.*', ('flash', 'wl', 'FLASH')), - ('STM32G0.*:FLASH:.*', ('flash', 'g0', 'FLASH')), - ('STM32F107.*:ETH:.*', ('eth', 'v1a', 'ETH')), - ('STM32F[24].*:ETH:.*', ('eth', 'v1b', 'ETH')), - ('STM32F7.*:ETH:ETH:ethermac110_v2_0', ('eth', 'v1c', 'ETH')), - ('.*ETH:ethermac110_v3_0', ('eth', 'v2', 'ETH')), - - ('.*:FSMC:.*', ('fsmc', 'v1', 'FSMC')), - ('STM32H7.*:FMC:.*', ('fmc', 'h7', 'FMC')), - - (r'.*LPTIM\d.*:G0xx_lptimer1_v1_4', ('lptim', 'g0', 'LPTIM')), - - ('STM32F1.*:TIM(1|8):.*', ('timer', 'v1', 'TIM_ADV')), - ('STM32F1.*:TIM(2|5):.*', ('timer', 'v1', 'TIM_GP16')), - ('STM32F1.*:TIM(6|7):.*', ('timer', 'v1', 'TIM_BASIC')), - - ('STM32L0.*:TIM2:.*', ('timer', 'v1', 'TIM_GP16')), - - ('STM32U5.*:TIM(2|3|4|5):.*', ('timer', 'v1', 'TIM_GP32')), - - ('STM32.*:TIM(1|8|20):.*', ('timer', 'v1', 'TIM_ADV')), - ('STM32.*:TIM(2|5|23|24):.*', ('timer', 'v1', 'TIM_GP32')), - ('STM32.*:TIM(6|7|18):.*', ('timer', 'v1', 'TIM_BASIC')), - (r'.*TIM\d.*:gptimer.*', ('timer', 'v1', 'TIM_GP16')), - - ('STM32F0.*:DBGMCU:.*', ('dbgmcu', 'f0', 'DBGMCU')), - ('STM32F1.*:DBGMCU:.*', ('dbgmcu', 'f1', 'DBGMCU')), - ('STM32F2.*:DBGMCU:.*', ('dbgmcu', 'f2', 'DBGMCU')), - ('STM32F3.*:DBGMCU:.*', ('dbgmcu', 'f3', 'DBGMCU')), - ('STM32F4.*:DBGMCU:.*', ('dbgmcu', 'f4', 'DBGMCU')), - ('STM32F7.*:DBGMCU:.*', ('dbgmcu', 'f7', 'DBGMCU')), - ('STM32G0.*:DBGMCU:.*', ('dbgmcu', 'g0', 'DBGMCU')), - ('STM32G4.*:DBGMCU:.*', ('dbgmcu', 'g4', 'DBGMCU')), - ('STM32H7.*:DBGMCU:.*', ('dbgmcu', 'h7', 'DBGMCU')), - ('STM32L0.*:DBGMCU:.*', ('dbgmcu', 'l0', 'DBGMCU')), - ('STM32L1.*:DBGMCU:.*', ('dbgmcu', 'l1', 'DBGMCU')), - ('STM32L4.*:DBGMCU:.*', ('dbgmcu', 'l4', 'DBGMCU')), - ('STM32U5.*:DBGMCU:.*', ('dbgmcu', 'u5', 'DBGMCU')), - ('STM32WB.*:DBGMCU:.*', ('dbgmcu', 'wb', 'DBGMCU')), - ('STM32WL.*:DBGMCU:.*', ('dbgmcu', 'wl', 'DBGMCU')), - - ('STM32F1.*:GPIO.*', ('gpio', 'v1', 'GPIO')), - ('.*:GPIO.*', ('gpio', 'v2', 'GPIO')), - - ('.*:IPCC:v1_0', ('ipcc', 'v1', 'IPCC')), - ('.*:DMAMUX.*', ('dmamux', 'v1', 'DMAMUX')), - - (r'.*:GPDMA\d?:.*', ('gpdma', 'v1', 'GPDMA')), - (r'.*:BDMA\d?:.*', ('bdma', 'v1', 'DMA')), - ('STM32H7.*:DMA2D:DMA2D:dma2d1_v1_0', ('dma2d', 'v2', 'DMA2D')), - ('.*:DMA2D:dma2d1_v1_0', ('dma2d', 'v1', 'DMA2D')), - ('STM32L4[PQRS].*:DMA.*', ('bdma', 'v1', 'DMA')), # L4+ - ('STM32L[04].*:DMA.*', ('bdma', 'v2', 'DMA')), # L0, L4 non-plus (since plus is handled above) - ('STM32F030.C.*:DMA.*', ('bdma', 'v2', 'DMA')), # Weird F0 - ('STM32F09.*:DMA.*', ('bdma', 'v2', 'DMA')), # Weird F0 - ('STM32F[247].*:DMA.*', ('dma', 'v2', 'DMA')), - ('STM32H7.*:DMA.*', ('dma', 'v1', 'DMA')), - ('.*:DMA.*', ('bdma', 'v1', 'DMA')), - - ('.*:CAN:bxcan1_v1_1.*', ('can', 'bxcan', 'CAN')), - # stm32F4 CRC peripheral - # ("STM32F4*:CRC:CRC:crc_f4") - # v1: F1, F2, F4, L1 - # v2, adds INIT reg: F0 - # v3, adds POL reg: F3, F7, G0, G4, H7, L0, L4, L5, WB, WL - ('.*:CRC:integtest1_v1_0', ('crc', 'v1', 'CRC')), - ('STM32L[04].*:CRC:integtest1_v2_0', ('crc', 'v3', 'CRC')), - ('.*:CRC:integtest1_v2_0', ('crc', 'v2', 'CRC')), - ('.*:CRC:integtest1_v2_2', ('crc', 'v3', 'CRC')), - - ('.*:LCD:lcdc1_v1.0.*', ('lcd', 'v1', 'LCD')), - ('.*:LCD:lcdc1_v1.2.*', ('lcd', 'v2', 'LCD')), - ('.*:LCD:lcdc1_v1.3.*', ('lcd', 'v2', 'LCD')), -] - -peri_rename = { - 'HDMI_CEC': 'CEC', - 'SUBGHZ': 'SUBGHZSPI', -} - -ghost_peris = [ - 'GPIOA', 'GPIOB', 'GPIOC', 'GPIOD', 'GPIOE', 'GPIOF', 'GPIOG', 'GPIOH', 'GPIOI', 'GPIOJ', 'GPIOK', 'GPIOL', 'GPIOM', 'GPION', 'GPIOO', 'GPIOP', 'GPIOQ', 'GPIOR', 'GPIOS', 'GPIOT', - 'DMA1', 'DMA2', 'BDMA', 'DMAMUX', 'DMAMUX1', 'DMAMUX2', - 'SYSCFG', 'EXTI', 'FLASH', 'DBGMCU', 'CRS', 'PWR', 'AFIO', 'BKP', -] - -alt_peri_defines = { - 'DBGMCU': ['DBGMCU_BASE', 'DBG_BASE'], - 'FLASH': ['FLASH_R_BASE', 'FLASH_REG_BASE'], - 'ADC_COMMON': ['ADC_COMMON', 'ADC1_COMMON', 'ADC12_COMMON', 'ADC123_COMMON'], - 'CAN': ['CAN_BASE', 'CAN1_BASE'], - 'FMC': ['FMC_BASE', 'FMC_R_BASE'], - 'FSMC': ['FSMC_R_BASE'] -} - -# Device address overrides, in case of missing from headers -address_overrides = { - 'STM32F412VE:GPIOF_BASE': 0x40021400, - 'STM32F412VE:GPIOG_BASE': 0x40021800, - 'STM32F412VG:GPIOF_BASE': 0x40021400, - 'STM32F412VG:GPIOG_BASE': 0x40021800, - 'STM32L151CB-A:GPIOF_BASE': 0x40021800, - 'STM32L151CB-A:GPIOG_BASE': 0x40021C00, - 'STM32L432KB:GPIOD_BASE': 0x48000C00, - 'STM32L432KB:GPIOE_BASE': 0x48001000, - 'STM32L432KB:GPIOF_BASE': 0x48001400, - 'STM32L432KB:GPIOG_BASE': 0x48001800, -} - - -def lookup_address(defines, name, d): - if addr := defines.get(d): - return addr - elif addr := address_overrides.get(name + ':' + d): - return addr - - -@functools.cache -def match_peri(peri): - for r, block in perimap: - if re.match('^' + r + '$', peri): - if block == '': - return None - return block - return None - - -all_mcu_files = {} -per_mcu_files = {} - - -def parse_documentations(): - print("linking files and documents") - with open('sources/mcufinder/files.json', 'r', encoding='utf-8') as j: - files = json.load(j) - for file in files['Files']: - file_id = file['id_file'] - if file_id not in all_mcu_files: - all_mcu_files[file_id] = { - 'name': file['name'], - 'title': file['title'], - 'url': file['URL'], - 'type': file['type'], - } - - with open('sources/mcufinder/mcus.json', 'r', encoding='utf-8') as j: - mcus = json.load(j) - for mcu in mcus['MCUs']: - rpn = mcu['RPN'] - if rpn not in per_mcu_files: - per_mcu_files[rpn] = [] - for file in mcu['files']: - per_mcu_files[rpn].append(file['file_id']) - - -def parse_document_type(t): - if t == 'Reference manual': - return 0, 'reference_manual' - if t == 'Programming manual': - return 1, 'programming_manual' - if t == 'Datasheet': - return 2, 'datahseet' - if t == 'Errata sheet': - return 3, 'errata_sheet' - if t == 'Application note': - return 4, 'application_note' - raise Exception(f'Unknown doc type {t}') - - -def documents_for(chip_name): - docs = [] - if ids := per_mcu_files.get(chip_name): - for id in ids: - if file := all_mcu_files.get(id): - file = all_mcu_files[id] - order, doc_type = parse_document_type(file['type']) - docs.append({ - 'order': order, - 'type': doc_type, - 'title': file['title'], - 'name': file['name'], - 'url': file['url'], - }) - docs.sort(key=lambda x: (x['order'], x['name'])) - for doc in docs: - del doc['order'] - return docs - - -def chip_name_from_package_name(x): - name_map = [ - ('(STM32L1....).x([AX])', '\\1-\\2'), - ('(STM32G0....).xN', '\\1'), - ('(STM32F412..).xP', '\\1'), - ('(STM32L4....).xP', '\\1'), - ('(STM32WB....).x[AE]', '\\1'), - ('(STM32G0....).xN', '\\1'), - ('(STM32L5....).x[PQ]', '\\1'), - ('(STM32L0....).xS', '\\1'), - ('(STM32H7....).xQ', '\\1'), - ('(STM32U5....).xQ', '\\1'), - ('(STM32......).x', '\\1'), - ] - - for a, b in name_map: - r, n = re.subn('^' + a + '$', b, x) - if n != 0: - return r - raise Exception("bad name: {}".format(x)) - - -memories_map = { - 'flash': [ - 'FLASH', 'FLASH_BANK1', 'FLASH_BANK2', - 'D1_AXIFLASH', 'D1_AXIICP', - ], - 'ram': [ - 'SRAM', 'SRAM1', 'SRAM2', - 'D1_AXISRAM', - 'D1_ITCMRAM', - 'D1_DTCMRAM', - 'D1_AHBSRAM', - 'D2_AXISRAM', - 'D3_BKPSRAM', - 'D3_SRAM' - ], -} - - -def cleanup_pin_name(pin_name): - if p := parse_pin_name(pin_name): - return f'P{p[0]}{p[1]}' - - -def parse_signal_name(signal_name): - if signal_name.startswith('USB_OTG_FS') or signal_name.startswith('USB_OTG_HS'): - parts = [signal_name[:10], signal_name[11:]] - else: - parts = signal_name.split('_', 1) - - if len(parts) == 1: - return None - peri_name = parts[0] - signal_name = parts[1] - if signal_name.startswith("EXTI"): - return None - if peri_name.startswith("DEBUG") and signal_name.startswith("SUBGHZSPI"): - parts = signal_name.split('-', 1) - if len(parts) == 2: - peri_name = parts[0] - signal_name = removesuffix(parts[1], "OUT") - - return peri_name, signal_name - - -def parse_pin_name(pin_name): - if len(pin_name) < 3: - return None - if pin_name[0] != 'P': - return None - port = pin_name[1] - if not port.isalpha(): - return None - - pin = pin_name[2:] - i = 0 - while i < len(pin) and pin[i].isnumeric(): - i += 1 - - if i == 0: - return None - - pin = int(pin[:i]) - - return port, pin - - -def get_peri_addr(defines, pname): - possible_defines = alt_peri_defines.get(pname) or [f'{pname}_BASE', pname] - for d in possible_defines: - if addr := defines.get(d): - return addr - return None - - -def parse_chips(): - os.makedirs('data/chips', exist_ok=True) - - # XMLs group together chips that are identical except flash/ram size. - # For example STM32L471Z(E-G)Jx.xml is STM32L471ZEJx, STM32L471ZGJx. - # However they do NOT group together identical chips with different package. - # - # We want exactly the opposite: group all packages of a chip together, but - # NOT group equal-except-memory-size chips together. Yay. - # - # We first read all XMLs, and fold together all packages. We don't expand - # flash/ram sizes yet, we want to do it as late as possible to avoid duplicate - # work so that generation is faster. - - chips = {} - chip_groups = [] - - for f in sorted(glob('sources/cubedb/mcu/STM32*.xml')): - f = f.replace(os.path.sep, '/') - - # Not supported - if 'STM32MP' in f: - continue - - # Does not exist in ST website. No datasheet, no RM. - if 'STM32GBK' in f: - continue - if 'STM32L485' in f: - continue - - print(f) - - r = xmltodict.parse(open(f, 'rb'), force_list=['Signal'])['Mcu'] - - package_names = expand_name(r['@RefName']) - package_rams = r['Ram'] - package_flashs = r['Flash'] - if type(package_rams) != list: - package_rams = [package_rams] * len(package_names) - if type(package_flashs) != list: - package_flashs = [package_flashs] * len(package_names) - - group_idx = None - for package_name in package_names: - chip_name = chip_name_from_package_name(package_name) - if chip := chips.get(chip_name): - group_idx = chip['group_idx'] - break - - if group_idx is None: - group_idx = len(chip_groups) - chip_groups.append({ - 'chip_names': [], - 'xml': r, - 'ips': {}, - 'pins': {}, - }) - - for package_i, package_name in enumerate(package_names): - chip_name = chip_name_from_package_name(package_name) - if chip_name not in chips: - chips[chip_name] = { - 'name': chip_name, - 'flash': package_flashs[package_i], - 'ram': package_rams[package_i], - 'group_idx': group_idx, - 'packages': [], - } - chips[chip_name]['packages'].append({ - 'name': package_name, - 'package': r['@Package'], - }) - - # Some packages have some peripehrals removed because the package had to - # remove GPIOs useful for that peripheral. So we merge all peripherals from all packages. - group = chip_groups[group_idx] - for ip in r['IP']: - group['ips'][ip['@InstanceName']] = ip - for pin in r['Pin']: - if pin_name := cleanup_pin_name(pin['@Name']): - group['pins'][pin_name] = pin - - for chip_name, chip in chips.items(): - chip_groups[chip['group_idx']]['chip_names'].append(chip_name) - - for chip in chip_groups: - chip_name = chip["chip_names"][0] - print(f'* processing chip group {chip["chip_names"]}') - - chip['family'] = chip['xml']['@Family'] - chip['line'] = chip['xml']['@Line'] - chip['die'] = chip['xml']['Die'] - - rcc_kind = next(filter(lambda x: x['@Name'] == 'RCC', chip['ips'].values()))['@Version'] - assert rcc_kind is not None - rcc_block = match_peri(f'{chip_name}:RCC:{rcc_kind}') - assert rcc_block is not None - - h = header.get_for_chip(chip_name) - if h is None: - raise Exception("missing header for {}".format(chip_name)) - - chip_af = next(filter(lambda x: x['@Name'] == 'GPIO', chip['ips'].values()))['@Version'] - chip_af = removesuffix(chip_af, '_gpio_v1_0') - chip_af = af[chip_af] - - cores = [] - for core_xml in children(chip['xml'], 'Core'): - core_name = corename(core_xml) - core = { - 'name': core_name, - 'peripherals': [], - } - cores.append(core) - - if not core_name in h['interrupts'] or not core_name in h['defines']: - core_name = 'all' - # print("Defining for core", core_name) - - # C header defines for this core. - defines = h['defines'][core_name] - - # Interrupts! - # Most chips have a single NVIC, named "NVIC" - want_nvic_name = 'NVIC' - - # Exception 1: Multicore: NVIC1 is the first core, NVIC2 is the second. We have to pick the right one. - if chip_name[5:9] in ('H745', 'H747', 'H755', 'H757', 'WL54', 'WL55'): - if core_name == 'cm7': - want_nvic_name = 'NVIC1' - else: - want_nvic_name = 'NVIC2' - if chip_name[5:8] == 'WL5': - if core_name == 'cm4': - want_nvic_name = 'NVIC1' - else: - want_nvic_name = 'NVIC2' - - # Exception 2: TrustZone: NVIC1 is Secure mode, NVIC2 is NonSecure mode. For now, we pick the NonSecure one. - if chip_name[5:7] in ('L5', 'U5'): - want_nvic_name = 'NVIC2' - - chip_nvic = next(filter(lambda x: x['@Name'] == want_nvic_name, chip['ips'].values())) - header_irqs = h['interrupts'][core_name] - chip_irqs = interrupts.get(chip_nvic['@Name'], chip_nvic['@Version'], core_name) - - # F100xE MISC_REMAP remaps some DMA IRQs, so ST decided to give two names - # to the same IRQ number. - if chip_name.startswith('STM32F100') and 'DMA2_Channel4_5' in header_irqs: - del header_irqs['DMA2_Channel4_5'] - - core['interrupts'] = [ - { - 'name': k, - 'number': v, - } - for k, v in header_irqs.items() - ] - - peri_kinds = {} - - for ip in chip['ips'].values(): - pname = ip['@InstanceName'] - pkind = ip['@Name'] + ':' + ip['@Version'] - pkind = removesuffix(pkind, '_Cube') - - if pname in FAKE_PERIPHERALS: - continue - - if rename := peri_rename.get(pname): - pname = rename - - if pname.startswith('ADC'): - if not 'ADC_COMMON' in peri_kinds: - peri_kinds['ADC_COMMON'] = 'ADC_COMMON:' + removesuffix(ip['@Version'], '_Cube') - if pname.startswith('ADC3'): - if chip_name.startswith("STM32H7") and not 'ADC_COMMON3' in peri_kinds: - peri_kinds['ADC3_COMMON'] = 'ADC3_COMMON:' + removesuffix(ip['@Version'], '_Cube') - - peri_kinds[pname] = pkind - - for pname in ghost_peris: - if pname not in peri_kinds and (addr := get_peri_addr(defines, pname)): - peri_kinds[pname] = 'unknown' - - if 'BDMA1' in peri_kinds and 'BDMA' in peri_kinds: - del peri_kinds['BDMA'] - - # get possible used GPIOs for each peripheral from the chip xml - # it's not the full info we would want (stuff like AFIO info which comes from GPIO xml), - # but we actually need to use it because of F1 line - # which doesn't include non-remappable peripherals in GPIO xml - # and some weird edge cases like STM32F030C6 (see merge_periph_pins_info) - periph_pins = {} - for pin_name, pin in chip['pins'].items(): - for signal in pin['Signal']: - signal = signal['@Name'] - if signal.startswith('DEBUG_SUBGHZSPI-'): - signal = 'SUBGHZSPI_' + signal[16:-3] - # TODO: What are those signals (well, GPIO is clear) Which peripheral do they belong to? - if signal not in {'GPIO', 'CEC', 'AUDIOCLK', 'VDDTCXO'} and 'EXTI' not in signal: - # both peripherals and signals can have underscores in their names so there is no easy way to split - # check if signal name starts with one of the peripheral names - for periph in peri_kinds.keys(): - if signal.startswith(periph + '_'): - signal = removeprefix(signal, periph + '_') - pins = periph_pins.setdefault(periph, []) - pins.append({ - 'pin': pin_name, - 'signal': signal, - }) - break - for periph, pins in periph_pins.items(): - pins = remove_duplicates(pins) - sort_pins(pins) - periph_pins[periph] = pins - - peris = [] - for pname, pkind in peri_kinds.items(): - if chip_name.startswith('STM32F0') and pname == 'ADC': - addr = get_peri_addr(defines, 'ADC1') - else: - addr = get_peri_addr(defines, pname) - if addr is None: - continue - - p = { - 'name': pname, - 'address': addr, - } - - if block := match_peri(chip_name + ':' + pname + ':' + pkind): - p['registers'] = { - 'kind': block[0], - 'version': block[1], - 'block': block[2], - } - - if rcc_info := match_peri_clock(rcc_block, pname): - p['rcc'] = rcc_info - - if pins := periph_pins.get(pname): - # merge the core xml info with GPIO xml info to hopefully get the full picture - # if the peripheral does not exist in the GPIO xml (one of the notable one is ADC) - # it probably doesn't need any AFIO writes to work - if af_pins := chip_af.get(pname): - pins = merge_periph_pins_info('STM32F1' in chip_name, pname, pins, af_pins) - p['pins'] = pins - - if pname in chip_irqs: - # filter by available, because some are conditioned on - irqs = interrupts.filter_interrupts(chip_irqs[pname], header_irqs) - irqs = sorted(irqs, key=lambda x: (x['signal'], x['interrupt'])) - p['interrupts'] = irqs - - peris.append(p) - - family_extra = "data/extra/family/" + chip['family'] + ".yaml" - if os.path.exists(family_extra): - with open(family_extra) as extra_f: - extra = yaml.load(extra_f) - for p in extra['peripherals']: - peris.append(p) - - peris.sort(key=lambda x: x['name']) - have_peris = set((p['name'] for p in peris)) - core['peripherals'] = peris - - # Collect DMA versions in the chip - chip_dmas = [] - for ip in chip['ips'].values(): - pkind = ip['@Name'] - version = ip['@Version'] - if pkind in ('DMA', 'BDMA', 'BDMA1', 'BDMA2', 'GPDMA') and version in dma_channels and version not in chip_dmas: - chip_dmas.append(version) - - # Process DMA channels - chs = [] - for dma in chip_dmas: - chs.extend(dma_channels[dma]['channels']) - - # The dma_channels[xx] is generic for multiple chips. The current chip may have less DMAs, - # so we have to filter it. - chs = [ch for ch in chs if ch['dma'] in have_peris] - core['dma_channels'] = chs - - have_chs = set((ch['name'] for ch in chs)) - - # Process peripheral - DMA channel associations - for p in peris: - chs = [] - for dma in chip_dmas: - peri_chs = dma_channels[dma]['peripherals'].get(p['name']) - - # DAC1 is sometimes interchanged with DAC - if not peri_chs and p['name'] == "DAC1": - peri_chs = dma_channels[dma]['peripherals'].get("DAC") - - if peri_chs: - chs.extend([ - ch - for ch in peri_chs - if 'channel' not in ch or ch['channel'] in have_chs - ]) - if chs: - p['dma_channels'] = chs - - # Now that we've processed everything common to the entire group, - # process each chip in the group. - - group = chip - - for chip_name in group['chip_names']: - chip = chips[chip_name] - - flash_total = int(chip['flash']) * 1024 - ram_total = int(chip['ram']) * 1024 - - memory_regions = [] - - found = set() - for each in memories_map['flash']: - if each + '_BASE' in h['defines']['all']: - if each == 'FLASH': - key = 'BANK_1' - elif each == 'FLASH_BANK1': - key = 'BANK_1' - elif each == 'FLASH_BANK2': - key = 'BANK_2' - else: - key = each - - if key in found: - continue - found.add(key) - - size = 0 - if key == 'BANK_1' or key == 'BANK_2': - if size2 := memory.determine_flash_size(chip_name): - size = min(size2, flash_total) - - memory_regions.append({ - 'name': key, - 'kind': 'flash', - 'address': h['defines']['all'][each + '_BASE'], - 'size': size, - 'settings': memory.determine_flash_settings(chip_name), - }) - - found = set() - for each in memories_map['ram']: - if each + '_BASE' in h['defines']['all']: - if each == 'D1_AXISRAM': - key = 'SRAM' - elif each == 'SRAM1': - key = 'SRAM' - else: - key = each - - if key in found: - continue - found.add(key) - - size = 0 - if key == 'SRAM': - if size2 := memory.determine_ram_size(chip_name): - size = min(size2, ram_total) - - memory_regions.append({ - 'name': key, - 'kind': 'ram', - 'address': h['defines']['all'][each + '_BASE'], - 'size': size, - }) - - docs = documents_for(chip_name) - - device_id = memory.determine_device_id(chip_name) - - chip = { - 'name': chip_name, - 'family': group['family'], - 'line': group['line'], - 'die': group['die'], - 'device_id': device_id, - 'packages': chip['packages'], - 'memory': memory_regions, - 'docs': docs, - 'cores': cores, - } - - with open('data/chips/' + chip_name + '.json', 'w') as f: - json.dump(chip, f, indent=4) - - -SIGNAL_REMAP = { - # for some godforsaken reason UART4's and UART5's CTS are called CTS_NSS in the GPIO xml - # so try to match with these - 'CTS': 'CTS_NSS' - - -} - - -def merge_periph_pins_info(is_f1, periph_name, core_pins, af_pins): - if is_f1: - # TODO: actually handle the F1 AFIO information when it will be extracted - return core_pins - - # covert to dict - af_pins = {(v['pin'], v['signal']): v for v in af_pins} - for pin in core_pins: - af = af_pins.get((pin['pin'], pin['signal']), {'af': None})['af'] - - # try to look for a signal with another name - if af is None and (remap := SIGNAL_REMAP.get(pin['signal'])): - af = af_pins.get((pin['pin'], remap), {'af': None})['af'] - - # it appears that for __some__ STM32 MCUs there is no AFIO specified in GPIO file - # (notably - STM32F030C6 with it's I2C1 on PF6 and PF7) - # but the peripheral can actually be mapped to different pins - # this breaks embassy's model, so we pretend that it's AF 0 - # Reference Manual states that there's no GPIOF_AFR register - # but according to Cube-generated core it's OK to write to AFIO reg, it seems to be ignored - # TODO: are there any more signals that have this "feature" - if af is None and periph_name == 'I2C1': - af = 0 - - if af is not None: - # Kinda not nice to modify this dict, but doesn't seem to be a problem - pin['af'] = af - return core_pins - pass - - -af = {} - - -def sort_pins(pins): - pins.sort(key=lambda p: (parse_pin_name(p['pin']), p['signal'])) - - -def remove_duplicates(item_list): - ''' Removes duplicate items from a list ''' - singles_list = [] - for element in item_list: - if element not in singles_list: - singles_list.append(element) - return singles_list - - -def parse_gpio_af(): - # os.makedirs('data/gpio_af', exist_ok=True) - for f in glob('sources/cubedb/mcu/IP/GPIO-*_gpio_v1_0_Modes.xml'): - f = f.replace(os.path.sep, '/') - - ff = removeprefix(f, 'sources/cubedb/mcu/IP/GPIO-') - ff = removesuffix(ff, '_gpio_v1_0_Modes.xml') - r = xmltodict.parse(open(f, 'rb')) - - if 'STM32F1' in f: - peris = parse_gpio_af_f1(r) - else: - peris = parse_gpio_af_nonf1(r) - af[ff] = peris - - -def parse_gpio_af_f1(xml): - peris = {} - for pin in xml['IP']['GPIO_Pin']: - pin_name = pin['@Name'] - - # Cleanup pin name - pin_name = cleanup_pin_name(pin_name) - if pin_name is None: - continue - - # Extract AFs - for signal in children(pin, 'PinSignal'): - p = parse_signal_name(signal['@Name']) - if p is None: - continue - peri_name, signal_name = p - - if peri_name not in peris: - peris[peri_name] = [] - peris[peri_name].append({ - 'pin': pin_name, - 'signal': signal_name, - }) - - for pname, p in peris.items(): - p = remove_duplicates(p) - sort_pins(p) - peris[pname] = p - return peris - - -def parse_gpio_af_nonf1(xml): - peris = {} - - for pin in xml['IP']['GPIO_Pin']: - pin_name = pin['@Name'] - - # Cleanup pin name - pin_name = cleanup_pin_name(pin_name) - if pin_name is None: - continue - - # Extract AFs - for signal in children(pin, 'PinSignal'): - p = parse_signal_name(signal['@Name']) - if p is None: - continue - peri_name, signal_name = p - - afn = signal['SpecificParameter']['PossibleValue'].split('_')[1] - afn = int(removeprefix(afn, 'AF')) - - if peri_name not in peris: - peris[peri_name] = [] - peris[peri_name].append({ - 'pin': pin_name, - 'signal': signal_name, - 'af': afn, - }) - - for pname, p in peris.items(): - p = remove_duplicates(p) - sort_pins(p) - peris[pname] = p - return peris - - -dma_channels = {} - - -def parse_dma(): - for f in glob('sources/cubedb/mcu/IP/DMA*Modes.xml') + glob('sources/cubedb/mcu/IP/BDMA*Modes.xml'): - f = f.replace(os.path.sep, '/') - is_explicitly_bdma = False - ff = removeprefix(f, 'sources/cubedb/mcu/IP/') - if not (ff.startswith('B') or ff.startswith('D')): - continue - if ff.startswith("BDMA"): - is_explicitly_bdma = True - ff = removeprefix(ff, 'DMA-') - ff = removeprefix(ff, 'BDMA-') - ff = removeprefix(ff, 'BDMA1-') - ff = removeprefix(ff, 'BDMA2-') - ff = removesuffix(ff, '_Modes.xml') - print(ff) - - r = xmltodict.parse(open(f, 'rb'), force_list={'Mode', 'RefMode'}) - - chip_dma = { - 'channels': [], - 'peripherals': {}, - } - - for dma in r['IP']['ModeLogicOperator']['Mode']: - dma_peri_name = dma['@Name'] - if ' Context' in dma_peri_name: - continue - channels = dma['ModeLogicOperator']['Mode'] - if len(channels) == 1: - # ========== CHIP WITH DMAMUX - - dmamux_file = ff[5:7] - if ff.startswith('STM32L4P'): - dmamux_file = 'L4PQ' - if ff.startswith('STM32L4S'): - dmamux_file = 'L4RS' - - dmamux = 'DMAMUX1' - if is_explicitly_bdma: - dmamux = 'DMAMUX2' - - for mf in sorted(glob('data/dmamux/{}_*.yaml'.format(dmamux_file))): - mf = mf.replace(os.path.sep, '/') - with open(mf, 'r') as yaml_file: - y = yaml.load(yaml_file) - mf = removesuffix(mf, '.yaml') - req_dmamux = mf[mf.index('_') + 1:] # DMAMUX1 or DMAMUX2 - - if req_dmamux == dmamux: - for (request_name, request_num) in y.items(): - parts = request_name.split('_') - target_peri_name = parts[0] - if len(parts) < 2: - request = target_peri_name - else: - request = parts[1] - chip_dma['peripherals'].setdefault(target_peri_name, []).append({ - 'signal': request, - "dmamux": req_dmamux, - "request": request_num, - }) - - dmamux_channel = 0 - for n in dma_peri_name.split(","): - n = n.strip() - if result := re.match('.*' + n + r'_(Channel|Stream)\[(\d+)-(\d+)\]', channels[0]['@Name']): - low = int(result.group(2)) - high = int(result.group(3)) - for i in range(low, high + 1): - chip_dma['channels'].append({ - 'name': n + '_CH' + str(i), - 'dma': n, - # Make sure all channels numbers start at 0 - 'channel': i - low, - 'dmamux': dmamux, - 'dmamux_channel': dmamux_channel, - }) - dmamux_channel += 1 - - else: - # ========== CHIP WITHOUT DMAMUX - - # see if we can scrape out requests - requests = {} - - request_blocks = filter(lambda x: x['@BaseMode'] == 'DMA_Request', r['IP']['RefMode']) - for block in request_blocks: - name = block['@Name'] - # Depending on the chip, the naming is "Channel" or "Request"... - request_num = next(filter(lambda x: x['@Name'] in ('Channel', 'Request'), block['Parameter']), None) - if request_num is not None: - request_num = request_num['PossibleValue'] - if request_num.startswith('BDMA1_REQUEST_'): - continue - request_num = removeprefix(request_num, "DMA_CHANNEL_") - request_num = removeprefix(request_num, "DMA_REQUEST_") - requests[name] = int(request_num) - - channel_names = [] - for channel in channels: - channel_name = channel['@Name'] - channel_name = removeprefix(channel_name, dma_peri_name + '_') - channel_name = removeprefix(channel_name, "Channel") - channel_name = removeprefix(channel_name, "Stream") - - channel_names.append(channel_name) - chip_dma['channels'].append({ - 'name': dma_peri_name + '_CH' + channel_name, - 'dma': dma_peri_name, - 'channel': int(channel_name), - }) - for target in channel['ModeLogicOperator']['Mode']: - target_name = target['@Name'] - original_target_name = target_name - parts = target_name.split(':') - target_name = parts[0] - - # Chips with single DAC refer to channels by DAC1/DAC2 - if target_name == "DAC1": - target_name = "DAC_CH1" - if target_name == "DAC2": - target_name = "DAC_CH2" - - parts = target_name.split('_') - target_peri_name = parts[0] - if len(parts) < 2: - target_requests = [target_peri_name] - else: - target_requests = target_name.split('_')[1].split('/') - if target_name != 'MEMTOMEM': - if target_peri_name == "LPUART": - target_peri_name = "LPUART1" - for request in target_requests: - if ':' in request: - request = request.split(':')[0] - entry = { - 'signal': request, - 'channel': dma_peri_name + '_CH' + channel_name, - } - if original_target_name in requests: - entry['request'] = requests[original_target_name] - chip_dma['peripherals'].setdefault(target_peri_name, []).append(entry) - - # Make sure all channels numbers start at 0 - if min(map(int, channel_names)) != 0: - for ch in chip_dma['channels']: - if ch['dma'] == dma_peri_name: - ch['channel'] -= 1 - - Path('tmp/dmas').mkdir(parents=True, exist_ok=True) - with open('tmp/dmas/' + ff + '.json', 'w') as f: - json.dump(chip_dma, f, indent=4) - - dma_channels[ff] = chip_dma - - # STM32U5 - - chip_dma = { - 'channels': [], - 'peripherals': {}, - } - - with open('data/dmamux/U5_GPDMA1.yaml', 'r') as yaml_file: - y = yaml.load(yaml_file) - - for (request_name, request_num) in y.items(): - parts = request_name.split('_') - target_peri_name = parts[0] - if len(parts) < 2: - request = target_peri_name - else: - request = parts[1] - chip_dma['peripherals'].setdefault(target_peri_name, []).append({ - 'signal': request, - "dma": 'GPDMA1', - "request": request_num, - }) - - for i in range(16): - chip_dma['channels'].append({ - 'name': 'GPDMA1_CH' + str(i), - 'dma': 'GPDMA1', - 'channel': i, - 'supports_2d': i >= 12, - }) - - ff = 'STM32U5_dma3_Cube' - with open('tmp/dmas/' + ff + '.json', 'w') as f: - json.dump(chip_dma, f, indent=4) - dma_channels[ff] = chip_dma - - -peripheral_to_clock = {} - - -clock_renames = { - 'AHB': 'AHB1', - 'APB': 'APB1', -} - - -def parse_rcc_regs(): - print("parsing RCC registers") - for f in glob('data/registers/rcc_*'): - f = f.replace(os.path.sep, '/') - ff = removeprefix(f, 'data/registers/rcc_') - ff = removesuffix(ff, '.yaml') - family_clocks = {} - with open(f, 'r') as yaml_file: - y = yaml.load(yaml_file) - - for (key, body) in y.items(): - # Some chip families have a separate bus for GPIO so it's not attached to the AHB/APB - # bus but an GPIO bus. Use the GPIO as the clock for these chips. - if m := re.match(r'^fieldset/((A[PH]B\d?)|GPIO)[LH]?ENR\d?$', key): - reg = removeprefix(key, 'fieldset/') - clock = m.group(1) - clock = clock_renames.get(clock, clock) - for field in body['fields']: - if field['name'].endswith('EN'): - peri = removesuffix(field['name'], 'EN') - - # Timers are a bit special, they may have a x2 freq - peri_clock = f'{clock}_TIM' if re.match(r'^TIM\d+$', peri) else clock - res = { - 'clock': peri_clock, - 'enable': { - 'register': reg, - 'field': field['name'], - } - } - if rstr := y.get(key.replace('ENR', 'RSTR')): - if field := next(filter(lambda f: f['name'] == f'{peri}RST', rstr['fields']), None): - res['reset'] = { - 'register': reg.replace('ENR', 'RSTR'), - 'field': f'{peri}RST', - } - family_clocks[peri] = res - - peripheral_to_clock[('rcc', ff, 'RCC')] = family_clocks - - -def match_peri_clock(rcc_block, peri_name): - if rcc_block in peripheral_to_clock: - if res := peripheral_to_clock[rcc_block].get(peri_name): - return res - if peri_name.endswith("1"): - return match_peri_clock(rcc_block, removesuffix(peri_name, "1")) - return None - - -memory.parse() -interrupts.parse() -parse_rcc_regs() -parse_documentations() -parse_dma() -parse_gpio_af() -parse_chips() diff --git a/stm32data/header.py b/stm32data/header.py deleted file mode 100644 index 2867711..0000000 --- a/stm32data/header.py +++ /dev/null @@ -1,209 +0,0 @@ -import re -import os -import json -from glob import glob - -from stm32data import yaml -from stm32data.util import removeprefix, removesuffix - - -headers_parsed = {} -header_map = {} -with open('header_map.yaml', 'r') as f: - y = yaml.load(f) - for header, chips in y.items(): - for chip in chips.split(','): - header_map[chip.strip().lower()] = header.lower() - - -def get_for_chip(model): - if header := get_header_name_for_chip(model): - return headers_parsed[header] - return None - - -def get_header_name_for_chip(model): - # for a, b in header_map: - # model = re.sub(a, b, model, flags=re.IGNORECASE) - model = model.lower() - - # if it's in the map, just go - if r := header_map.get(model): - return r - - # if not, find it by regex, taking `x` meaning `anything` - res = [] - for h in headers_parsed.keys(): - if re.match('^' + h.replace('x', '.') + '$', model): - res.append(h) - - if len(res) == 0: - return None - assert len(res) == 1 - return res[0] - - -def paren_ok(val): - n = 0 - for c in val: - if c == '(': - n += 1 - if c == ')': - n -= 1 - if n < 0: - return False - return n == 0 - - -# warning: horrible abomination ahead - - -def parse_value(val, defines): - val = val.strip() - if val == '': - return 0 - if m := re.match('(0([1-9][0-9]*)(U))', val): - return int(m.group(2), 10) - if m := re.match('((0x[0-9a-fA-F]+|\\d+))(|u|ul|U|UL)$', val): - return int(m.group(1), 0) - if m := re.match('([0-9A-Za-z_]+)$', val): - return defines.get(m.group(1), 0) - if m := re.match('\\((.*)\\)$', val): - if paren_ok(m.group(1)): - return parse_value(m.group(1), defines) - if m := re.match('\\*?\\([0-9A-Za-z_]+ *\\*?\\)(.*)$', val): - return parse_value(m.group(1), defines) - # if m := re.match('\\*?\\(u?int(8|16|32|64)_t\\ *)(.*)$', val): - # return parse_value(m.group(1), defines) - if m := re.match('(.*)/(.*)$', val): - return parse_value(m.group(1), defines) / parse_value(m.group(2), defines) - if m := re.match('(.*)<<(.*)$', val): - return (parse_value(m.group(1), defines) << parse_value(m.group(2), defines)) & 0xFFFFFFFF - if m := re.match('(.*)>>(.*)$', val): - return parse_value(m.group(1), defines) >> parse_value(m.group(2), defines) - if m := re.match('(.*)\\|(.*)$', val): - return parse_value(m.group(1), defines) | parse_value(m.group(2), defines) - if m := re.match('(.*)&(.*)$', val): - return parse_value(m.group(1), defines) | parse_value(m.group(2), defines) - if m := re.match('~(.*)$', val): - return (~parse_value(m.group(1), defines)) & 0xFFFFFFFF - if m := re.match('(.*)\\+(.*)$', val): - return parse_value(m.group(1), defines) + parse_value(m.group(2), defines) - if m := re.match('(.*)-(.*)$', val): - return parse_value(m.group(1), defines) - parse_value(m.group(2), defines) - raise Exception("can't parse: " + val) - - -def parse_header(f): - irqs = {} - defines = {} - cores = [] - cur_core = 'all' - - accum = '' - for l in open(f, 'r', encoding='utf-8', errors='ignore'): - l = l.strip() - l = accum + l - if l.endswith('\\'): - accum = l[:-1] - continue - accum = '' - - # Scoped by a single core - if m := re.match('.*if defined.*CORE_CM(\\d+)(PLUS)?.*', l): - cur_core = "cm" + str(m.group(1)) - if m.group(2) != None: - cur_core += "p" - # print("Cur core is ", cur_core, "matched", l) - found = False - for core in cores: - if core == cur_core: - found = True - if not found: - cores.append(cur_core) - # print("Switching to core", cur_core, "for", f) - elif m := re.match('.*else.*', l): - cur_core = "all" - if m := re.match('.*else.*CORE_CM(\\d+)(PLUS)?.*', l): - cur_core = "cm" + str(m.group(1)) - if m.group(2) != None: - cur_core += "p" - # print("Cur core is ", cur_core, "matched", l) - elif len(cores) > 1: - # Pick the second core assuming we've already parsed one - cur_core = cores[1] - - found = False - for core in cores: - if core == cur_core: - found = True - if not found: - cores.append(cur_core) - # print("Switching to core", cur_core, "for", f) - elif m := re.match('.*endif.*', l): - # print("Switching to common core for", f) - cur_core = "all" - - if cur_core not in irqs: - # print("Registering new core", cur_core) - irqs[cur_core] = {} - if cur_core not in defines: - defines[cur_core] = {} - - if m := re.match('([a-zA-Z0-9_]+)_IRQn += (\\d+),? +/\\*!< (.*) \\*/', l): - # print("Found irq for", cur_core) - irqs[cur_core][m.group(1)] = int(m.group(2)) - - if m := re.match('#define +([0-9A-Za-z_]+)\\(', l): - defines[cur_core][m.group(1)] = -1 - if m := re.match('#define +([0-9A-Za-z_]+) +(.*)', l): - name = m.group(1) - val = m.group(2) - name = name.strip() - if name == 'FLASH_SIZE': - continue - val = val.split('/*')[0].strip() - val = parse_value(val, defines[cur_core]) - # print("Found define for", cur_core) - defines[cur_core][name] = val - - # print("Found", len(cores), "cores for", f) - # print("Found", len(irqs['all']), "shared interrupts for", f) - - if len(cores) == 0: - cores.append("all") - - for core in cores: - if core != "all": - irqs[core].update(irqs['all']) - defines[core].update(defines['all']) - - return { - 'cores': cores, - 'interrupts': irqs, - 'defines': defines, - } - - -def parse_headers(): - os.makedirs('sources/headers_parsed', exist_ok=True) - print('loading headers...') - for f in glob('sources/headers/*.h'): - f = f.replace(os.path.sep, '/') - # if 'stm32f4' not in f: continue - ff = removeprefix(f, 'sources/headers/') - ff = removesuffix(ff, '.h') - - try: - with open('sources/headers_parsed/{}.json'.format(ff), 'r') as j: - res = json.load(j) - except: - print(f) - res = parse_header(f) - with open('sources/headers_parsed/{}.json'.format(ff), 'w') as j: - json.dump(res, j) - - headers_parsed[ff] = res - - -parse_headers() diff --git a/stm32data/interrupts.py b/stm32data/interrupts.py deleted file mode 100644 index d354d07..0000000 --- a/stm32data/interrupts.py +++ /dev/null @@ -1,292 +0,0 @@ -from stm32data.util import * -from glob import glob -import xmltodict -import re -import os - - -chip_interrupts = {} - - -def get(nvic_name, nvic_version, core): - return chip_interrupts[(nvic_name, nvic_version)] - - -def parse(): - print("parsing interrupts") - for f in sorted(glob('sources/cubedb/mcu/IP/NVIC*_Modes.xml')): - if 'STM32MP1' in f: - continue - f = f.replace(os.path.sep, '/') - ff = removeprefix(f, 'sources/cubedb/mcu/IP/') - ff = removesuffix(ff, '_Modes.xml') - - [nvic_name, nvic_version] = ff.split('-') - - irqs = {} - r = xmltodict.parse(open(f, 'rb')) - - xml_irqs = next(filter(lambda x: x['@Name'] == 'IRQn', r['IP']['RefParameter'])) - for irq in xml_irqs['PossibleValue']: - value = irq['@Value'] - parts = value.split(':') - - # Interrupt name - name = removesuffix(parts[0], "_IRQn") - - # Fix typo in STM32Lxx and L083 devices - if name == "AES_RNG_LPUART1" and "RNG" not in str(parts[1:]): - name = "AES_LPUART1" - - if name in irqs: - continue - - print(f'{name:25} {nvic_version:12} {nvic_name:5} {parts[1]:8} {parts[2]:45} {parts[3]:45} {parts[4]:15}') - # 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 - flags = parts[1].split(',') - - # F100xE MISC_REMAP remaps some DMA IRQs, so ST decided to give two names - # to the same IRQ number. - if nvic_version == 'STM32F100E' and name == 'DMA2_Channel4_5': - continue - - # F3 can remap USB IRQs, ignore them. - if nvic_version.startswith('STM32F3') and 'remap' in irq['@Comment']: - continue - - signals = set() - - if name in ['NonMaskableInt', 'HardFault', 'MemoryManagement', 'BusFault', 'UsageFault', 'SVCall', 'DebugMonitor', 'PendSV', 'SysTick']: - pass - elif any(f in flags for f in ['DMA', 'DMAL0', 'DMAF0', 'DMAL0_DMAMUX', 'DMAF0_DMAMUX']): - dmas = parts[3].split(',') - chans = parts[4].split(';') - assert len(dmas) == len(chans) - for i in range(len(dmas)): - dma = dmas[i] - if ',' in chans[i]: - ch_from, ch_to = chans[i].split(',') - else: - ch_from = chans[i] - ch_to = chans[i] - ch_from = int(ch_from) - ch_to = int(ch_to) - for ch in range(ch_from, ch_to+1): - signals.add((dma, f'CH{ch}')) - elif name == 'DMAMUX1': # TODO does DMAMUX have more irq signals? seen in U5 - signals.add(('DMAMUX1', 'OVR')) - elif name == 'DMAMUX1_S': # TODO does DMAMUX have more irq signals? seen in U5 - signals.add(('DMAMUX1', 'OVR')) - elif name == 'DMAMUX_OVR': - signals.add(('DMAMUX1', 'OVR')) - elif name == 'DMAMUX1_OVR': - signals.add(('DMAMUX1', 'OVR')) - elif name == 'DMAMUX2_OVR': - signals.add(('DMAMUX2', 'OVR')) - elif 'DMAMUX' in flags: - assert False # should've been handled above - elif 'EXTI' in flags: - for signal in parts[2].split(','): - signals.add(('EXTI', signal)) - elif name == 'FLASH': - signals.add(('FLASH', 'GLOBAL')) - elif name == 'CRS': - signals.add(('RCC', 'CRS')) - elif name == 'RCC': - signals.add(('RCC', 'GLOBAL')) - else: - if parts[2] == '': - continue - - peri_names = parts[2].split(',') - - name2 = name - if name2 == 'USBWakeUp': - name2 = 'USB_WKUP' - if name2 == 'USBWakeUp_RMP': - name2 = 'USB_WKUP' - if name2.endswith('_S'): - name2 = removesuffix(name2, '_S') - - peri_signals = {p: [] for p in peri_names} - - curr_peris = None - if len(peri_names) == 1: - curr_peris = peri_names - - # Parse IRQ signals from the IRQ name. - for part in tokenize_name(name2): - if part == 'TAMPER': - part = 'TAMP' - - if part == 'LSECSS': - signals.add(('RCC', 'LSECSS')) - elif part == 'CSS': - signals.add(('RCC', 'CSS')) - elif part == 'LSE': - signals.add(('RCC', 'LSE')) - elif part == 'CRS': - signals.add(('RCC', 'CRS')) - elif pp := match_peris(peri_names, part): - curr_peris = pp - else: - assert curr_peris is not None - for p in curr_peris: - peri_signals[p].append(part) - - for p, ss in peri_signals.items(): - known = valid_signals(p) - - # If we have no signals for the peri, assume it's "global" so assign it all known ones - if ss == []: - if p.startswith('COMP'): - ss = ['WKUP'] - else: - ss = known - - for s in ss: - if s not in known: - raise Exception(f'Unknown signal {s} for peri {p}, known={known}') - signals.add((p, s)) - - for (peri, signal) in signals: - print(f' {peri}:{signal}') - irqs[name] = signals - - irqs2 = {} - for name, signals in irqs.items(): - for (p, s) in signals: - irqs2.setdefault(p, []).append({ - 'signal': s, - 'interrupt': name, - }) - - for p, pirqs in irqs2.items(): - psirqs = {} - for irq in pirqs: - psirqs.setdefault(irq['signal'], []).append(irq['interrupt']) - for s, irqs in psirqs.items(): - if len(irqs) != 1: - print(f'DUPE: {p} {s} {irqs}') - - chip_interrupts[(nvic_name, nvic_version)] = irqs2 - - -def tokenize_name(name): - # Treat IRQ names are "tokens" separated by `_`, except some tokens - # contain `_` themselves, such as `C1_RX`. - r = re.compile('(SPDIF_RX|EP\d+_(IN|OUT)|OTG_FS|OTG_HS|USB_FS|C1_RX|C1_TX|C2_RX|C2_TX|[A-Z0-9]+(_\d+)*)_*') - - name = name.upper() - - res = [] - i = 0 - while i < len(name): - m = r.match(name, i) - assert m is not None - res.append(m.group(1)) - i = m.end() - return res - - -PERI_OVERRIDE = { - 'USB_FS': ['USB'], - 'OTG_HS': ['USB_OTG_HS'], - 'OTG_FS': ['USB_OTG_FS'], - 'USB': ['USB_DRD_FS'], - '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'], -} - - -def match_peris(peris, name): - if over := PERI_OVERRIDE.get(name): - res = [] - for p in over: - if p in peris: - res.append(p) - if len(res) != 0: - return res - - res = [] - if m := re.fullmatch('(I2C|[A-Z]+)(\d+(_\d+)*)', name): - name = m.group(1) - for n in m.group(2).split('_'): - p = f'{name}{n}' - if p not in peris: - return [] - res.append(p) - else: - for p in peris: - if p == name or (p.startswith(name) and re.fullmatch('\d+', removeprefix(p, name))): - res.append(p) - - return res - - -def merge_peri_irq_signals(peri_irqs, additional): - for key, value in additional.items(): - if key not in peri_irqs: - peri_irqs[key] = [] - peri_irqs[key].append(value) - - -irq_signals_map = { - 'CAN': ['TX', 'RX0', 'RX1', 'SCE'], - 'FDCAN': ['IT0', 'IT1', 'CAL'], - 'I2C': ['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'], - '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'], -} - - -def valid_signals(peri): - for prefix, signals in irq_signals_map.items(): - if peri.startswith(prefix): - return signals - return ['GLOBAL'] - - -def filter_interrupts(peri_irqs, all_irqs): - return [ - i for i in peri_irqs if i['interrupt'] in all_irqs - ] diff --git a/stm32data/memory.py b/stm32data/memory.py deleted file mode 100644 index cfcb5cf..0000000 --- a/stm32data/memory.py +++ /dev/null @@ -1,206 +0,0 @@ -import sys -import re -import xmltodict -from glob import glob -from stm32data.util import * - - -def splat_names(base, parts): - names = [] - for part in parts: - if part.startswith("STM32"): - names.append(base) - elif part.startswith(base[5]): - names.append('STM32' + part) - else: - names.append(base[0: len(base) - len(part)] + part) - - return names - - -def split_names(str): - cleaned = [] - names = str.split("/") - current_base = None - for name in names: - name = name.split(' ')[0].strip() - if '-' in name: - parts = name.split('-') - current_base = parts[0] - splatted = splat_names(current_base, parts) - current_base = splatted[0] - cleaned = cleaned + splatted - elif name.startswith("STM32"): - current_base = name - cleaned.append(name) - elif name.startswith(current_base[5]): - names.append('STM32' + name) - else: - cleaned.append(current_base[0: len(current_base) - len(name)] + name) - return cleaned - - -memories = [] - - -def parse(): - for f in sorted(glob('sources/cubeprogdb/db/*.xml')): - # print("parsing ", f); - device = xmltodict.parse(open(f, 'rb'))['Root']['Device'] - device_id = device['DeviceID'] - name = device['Name'] - names = split_names(name) - flash_size = None - flash_addr = None - write_size = None - erase_size = None - erase_value = None - ram_size = None - ram_addr = None - - for peripheral in device['Peripherals']['Peripheral']: - if peripheral['Name'] == 'Embedded SRAM' and ram_size is None: - configs = peripheral['Configuration'] - if type(configs) != list: - configs = [configs] - ram_addr = int(configs[0]['Parameters']['@address'], 16) - ram_size = int(configs[0]['Parameters']['@size'], 16) - #print( f'ram {addr} {size}') - if peripheral['Name'] == 'Embedded Flash' and flash_size is None: - configs = peripheral['Configuration'] - if type(configs) != list: - configs = [configs] - flash_addr = int(configs[0]['Parameters']['@address'], 16) - flash_size = int(configs[0]['Parameters']['@size'], 16) - erase_value = int(peripheral['ErasedValue'], 16) - write_size = int(configs[0]['Allignement'], 16) - bank = configs[0]['Bank'] - if type(bank) != list: - bank = [bank] - fields = bank[0]['Field'] - if type(fields) != list: - fields = [fields] - - erase_size = int(fields[0]['Parameters']['@size'], 16) - for field in fields: - # print("Field", field) - erase_size = max(erase_size, int(field['Parameters']['@size'], 16)) - #print( f'flash {addr} {size}') - - chunk = { - 'device-id': int(device_id, 16), - 'names': names, - } - - if ram_size is not None: - chunk['ram'] = { - 'address': ram_addr, - 'bytes': ram_size, - } - - if flash_size is not None: - chunk['flash'] = { - 'address': flash_addr, - 'bytes': flash_size, - 'erase_value': erase_value, - 'write_size': write_size, - 'erase_size': erase_size, - } - - memories.append(chunk) - - # The chips below are missing from cubeprogdb - memories.append({ - 'device-id': 0, - 'names': ['STM32F302xD'], - 'ram': { - 'address': 0x20000000, - 'bytes': 64*1024, - }, - 'flash': { - 'address': 0x08000000, - 'bytes': 384*1024, - 'erase_value': 0xFF, - 'write_size': 8, - 'erase_size': 2048, - } - }) - memories.append({ - 'device-id': 0, - 'names': ['STM32F303xD'], - 'ram': { - 'address': 0x20000000, - 'bytes': 80*1024, - }, - 'flash': { - 'address': 0x08000000, - 'bytes': 384*1024, - 'erase_value': 0xFF, - 'write_size': 8, - 'erase_size': 2048, - } - }) - memories.append({ - 'device-id': 0, - 'names': ['STM32L100x6'], - 'ram': { - 'address': 0x20000000, - 'bytes': 32*1024, - }, - 'flash': { - 'address': 0x08000000, - 'bytes': 4*1024, - 'erase_value': 0xFF, - 'write_size': 4, - 'erase_size': 256, - } - }) - - -def determine_ram_size(chip_name): - for each in memories: - for name in each['names']: - if is_chip_name_match(name, chip_name): - return each['ram']['bytes'] - raise Exception(f'could not find ram size for {chip_name}') - - -def determine_flash_size(chip_name): - for each in memories: - for name in each['names']: - if is_chip_name_match(name, chip_name): - return each['flash']['bytes'] - raise Exception(f'could not find flash size for {chip_name}') - - -def determine_flash_settings(chip_name): - for each in memories: - for name in each['names']: - if is_chip_name_match(name, chip_name): - return { - 'erase_size': each['flash']['erase_size'], - 'write_size': each['flash']['write_size'], - 'erase_value': each['flash']['erase_value'], - } - raise Exception(f'could not find flash settings for {chip_name}') - - -def determine_device_id(chip_name): - for each in memories: - for name in each['names']: - if is_chip_name_match(name, chip_name): - return each['device-id'] - return None - - -def is_chip_name_match(pattern, chip_name): - chip_name = chip_name.replace("STM32F479", "STM32F469") # F479 is missing, it's the same as F469. - chip_name = chip_name.replace("STM32G050", "STM32G051") # same... - chip_name = chip_name.replace("STM32G060", "STM32G061") # same... - chip_name = chip_name.replace("STM32G070", "STM32G071") # same... - chip_name = chip_name.replace("STM32G0B0", "STM32G0B1") # same... - chip_name = chip_name.replace("STM32G4A", "STM32G49") # same... - chip_name = chip_name.replace("STM32L422", "STM32L412") # same... - chip_name = chip_name.replace("STM32WB30", "STM32WB35") # same... - pattern = pattern.replace('x', '.') - return re.match(pattern + ".*", chip_name) diff --git a/stm32data/util.py b/stm32data/util.py deleted file mode 100644 index 51e62c6..0000000 --- a/stm32data/util.py +++ /dev/null @@ -1,13 +0,0 @@ - -def removeprefix(value: str, prefix: str) -> str: - if value.startswith(prefix): - return value[len(prefix):] - else: - return value[:] - - -def removesuffix(value: str, suffix: str, /) -> str: - if value.endswith(suffix): - return value[:-len(suffix)] - else: - return value[:] diff --git a/stm32data/yaml.py b/stm32data/yaml.py deleted file mode 100644 index efdb000..0000000 --- a/stm32data/yaml.py +++ /dev/null @@ -1,19 +0,0 @@ -import yaml - -try: - from yaml import CSafeLoader as SafeLoader -except ImportError: - from yaml import SafeLoader - -try: - from yaml import CDumper as Dumper -except ImportError: - from yaml import Dumper - - -def load(*args, **kwargs): - return yaml.load(*args, Loader=SafeLoader, **kwargs) - - -def dump(*args, **kwargs): - return yaml.dump(*args, Dumper=Dumper, sort_keys=False, **kwargs)