Rewrite python script in rust

This commit is contained in:
Grant Miller 2022-11-10 23:57:50 -06:00
parent 1cfd8d516d
commit 6b3a7bc0c2
21 changed files with 3265 additions and 2094 deletions

5
.gitignore vendored
View File

@ -3,4 +3,7 @@
/tmp /tmp
.idea/ .idea/
transform*.yaml transform*.yaml
__pycache__ __pycache__
target/
Cargo.lock

View File

@ -1,11 +1,8 @@
{ {
"rust-analyzer.assist.importMergeBehavior": "last",
"editor.formatOnSave": true, "editor.formatOnSave": true,
"rust-analyzer.cargo.allFeatures": false, "rust-analyzer.cargo.allFeatures": false,
"rust-analyzer.checkOnSave.allFeatures": false, "rust-analyzer.checkOnSave.allFeatures": false,
"rust-analyzer.checkOnSave.allTargets": 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.procMacro.enable": true,
"rust-analyzer.cargo.loadOutDirsFromCheck": true, "rust-analyzer.cargo.loadOutDirsFromCheck": true,
"files.watcherExclude": { "files.watcherExclude": {

27
Cargo.toml Normal file
View File

@ -0,0 +1,27 @@
[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"
[profile.release]
debug = true

View File

@ -52,7 +52,7 @@ STM32F407xx: STM32F407VG, STM32F407VE, STM32F407ZG, STM32F407ZE, STM32F407IG, ST
STM32F417xx: STM32F417VG, STM32F417VE, STM32F417ZG, STM32F417ZE, STM32F417IG, STM32F417IE STM32F417xx: STM32F417VG, STM32F417VE, STM32F417ZG, STM32F417ZE, STM32F417IG, STM32F417IE
STM32F427xx: STM32F427VG, STM32F427VI, STM32F427ZG, STM32F427ZI, STM32F427IG, STM32F427II STM32F427xx: STM32F427VG, STM32F427VI, STM32F427ZG, STM32F427ZI, STM32F427IG, STM32F427II
STM32F437xx: STM32F437VG, STM32F437VI, STM32F437ZG, STM32F437ZI, STM32F437IG, STM32F437II 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 STM32F439xx: STM32F439VG, STM32F439VI, STM32F439ZG, STM32F439ZI, STM32F439BG, STM32F439BI, STM32F439NG, STM32F439NI, STM32F439IG, STM32F439II
STM32F401xC: STM32F401CB, STM32F401CC, STM32F401RB, STM32F401RC, STM32F401VB, STM32F401VC STM32F401xC: STM32F401CB, STM32F401CC, STM32F401RB, STM32F401RC, STM32F401VB, STM32F401VC
STM32F401xE: STM32F401CD, STM32F401RD, STM32F401VD, STM32F401CE, STM32F401RE, STM32F401VE STM32F401xE: STM32F401CD, STM32F401RD, STM32F401VD, STM32F401CE, STM32F401RE, STM32F401VE
@ -69,7 +69,7 @@ STM32F412Vx: STM32F412VET, STM32F412VGT, STM32F412VEH, STM32F412VGH
STM32F412Rx: STM32F412RET, STM32F412RGT, STM32F412REY, STM32F412RGY STM32F412Rx: STM32F412RET, STM32F412RGT, STM32F412REY, STM32F412RGY
STM32F413xx: STM32F413CH, STM32F413MH, STM32F413RH, STM32F413VH, STM32F413ZH, STM32F413CG, STM32F413MG, STM32F413RG, STM32F413VG, STM32F413ZG STM32F413xx: STM32F413CH, STM32F413MH, STM32F413RH, STM32F413VH, STM32F413ZH, STM32F413CG, STM32F413MG, STM32F413RG, STM32F413VG, STM32F413ZG
STM32F423xx: STM32F423CH, STM32F423RH, STM32F423VH, STM32F423ZH 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 STM32F746xx: STM32F746VE, STM32F746VG, STM32F746ZE, STM32F746ZG, STM32F746IE, STM32F746IG, STM32F746BE, STM32F746BG, STM32F746NE, STM32F746NG
STM32F745xx: STM32F745VE, STM32F745VG, STM32F745ZG, STM32F745ZE, STM32F745IE, STM32F745IG STM32F745xx: STM32F745VE, STM32F745VG, STM32F745ZG, STM32F745ZE, STM32F745IE, STM32F745IG
STM32F765xx: STM32F765BI, STM32F765BG, STM32F765NI, STM32F765NG, STM32F765II, STM32F765IG, STM32F765ZI, STM32F765ZG, STM32F765VI, STM32F765VG STM32F765xx: STM32F765BI, STM32F765BG, STM32F765NI, STM32F765NG, STM32F765II, STM32F765IG, STM32F765ZI, STM32F765ZG, STM32F765VI, STM32F765VG
@ -87,7 +87,6 @@ STM32L010x4: STM32L010K4, STM32L010F4
STM32L010x6: STM32L010C6 STM32L010x6: STM32L010C6
STM32L010x8: STM32L010K8, STM32L010R8 STM32L010x8: STM32L010K8, STM32L010R8
STM32L010xB: STM32L010RB STM32L010xB: STM32L010RB
STM32L011xx: STM32L031C6, STM32L031E6, STM32L031F6, STM32L031G6, STM32L031K6
STM32L021xx: STM32L021D4, STM32L021F4, STM32L021G4, STM32L021K4 STM32L021xx: STM32L021D4, STM32L021F4, STM32L021G4, STM32L021K4
STM32L031xx: STM32L031C6, STM32L031E6, STM32L031F6, STM32L031G6, STM32L031K6 STM32L031xx: STM32L031C6, STM32L031E6, STM32L031F6, STM32L031G6, STM32L031K6
STM32L041xx: STM32L041C6, STM32L041K6, STM32L041G6, STM32L041F6, STM32L041E6 STM32L041xx: STM32L041C6, STM32L041K6, STM32L041G6, STM32L041F6, STM32L041E6
@ -164,7 +163,7 @@ STM32H7A3xxQ: STM32H7A3QIY6Q, STM32H7A3IIK6Q, STM32H7A3IIT6Q, STM32H7A3LIH6Q, ST
STM32H7B3xx: STM32H7B3IIK6, STM32H7B3IIT6, STM32H7B3NIH6, STM32H7B3RIT6, STM32H7B3VIH6, STM32H7B3VIT6, STM32H7B3ZIT6 STM32H7B3xx: STM32H7B3IIK6, STM32H7B3IIT6, STM32H7B3NIH6, STM32H7B3RIT6, STM32H7B3VIH6, STM32H7B3VIT6, STM32H7B3ZIT6
STM32H7B3xxQ: STM32H7B3QIY6Q, STM32H7B3IIK6Q, STM32H7B3IIT6Q, STM32H7B3LIH6Q, STM32H7B3VIH6Q, STM32H7B3VIT6Q, STM32H7B3AII6Q, STM32H7B3ZIT6Q STM32H7B3xxQ: STM32H7B3QIY6Q, STM32H7B3IIK6Q, STM32H7B3IIT6Q, STM32H7B3LIH6Q, STM32H7B3VIH6Q, STM32H7B3VIT6Q, STM32H7B3AII6Q, STM32H7B3ZIT6Q
STM32H735xx: STM32H735AGI6, STM32H735IGK6, STM32H735RGV6, STM32H735VGT6, STM32H735VGY6, STM32H735ZGT6 STM32H735xx: STM32H735AGI6, STM32H735IGK6, STM32H735RGV6, STM32H735VGT6, STM32H735VGY6, STM32H735ZGT6
STM32H733xx: STM32H733VGH6, STM32H733VGT6, STM32H733ZGI6, STM32H733ZGT6, STM32H733xx: STM32H733VGH6, STM32H733VGT6, STM32H733ZGI6, STM32H733ZGT6
STM32H730xx: STM32H730VBH6, STM32H730VBT6, STM32H730ZBT6, STM32H730ZBI6 STM32H730xx: STM32H730VBH6, STM32H730VBT6, STM32H730ZBT6, STM32H730ZBI6
STM32H730xxQ: STM32H730IBT6Q, STM32H730ABI6Q, STM32H730IBK6Q STM32H730xxQ: STM32H730IBT6Q, STM32H730ABI6Q, STM32H730IBK6Q
STM32H725xx: STM32H725AGI6, STM32H725IGK6, STM32H725IGT6, STM32H725RGV6, STM32H725VGT6, STM32H725VGY6, STM32H725ZGT6, STM32H725REV6, SM32H725VET6, STM32H725ZET6, STM32H725AEI6, STM32H725IET6, STM32H725IEK6 STM32H725xx: STM32H725AGI6, STM32H725IGK6, STM32H725IGT6, STM32H725RGV6, STM32H725VGT6, STM32H725VGY6, STM32H725ZGT6, STM32H725REV6, SM32H725VET6, STM32H725ZET6, STM32H725AEI6, STM32H725IET6, STM32H725IEK6

1106
src/chips.rs Normal file

File diff suppressed because it is too large Load Diff

337
src/dma.rs Normal file
View File

@ -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<RefParameter>,
#[serde(rename = "RefMode")]
pub ref_modes: Vec<RefMode>,
#[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<String>,
#[serde(rename = "Parameter")]
pub parameters: Vec<Parameter>,
}
#[derive(Debug, Deserialize, PartialEq)]
pub struct Parameter {
#[serde(rename = "Name")]
pub name: String,
#[serde(rename = "PossibleValue", default)]
pub possible_values: Vec<String>,
}
#[derive(Debug, Deserialize, PartialEq)]
pub struct RefParameter {
#[serde(rename = "Name")]
pub name: String,
#[serde(rename = "PossibleValue", default)]
pub possible_values: Vec<PossibleValue>,
}
#[derive(Debug, Deserialize, PartialEq)]
pub struct PossibleValue {
#[serde(rename = "Comment")]
pub comment: String,
#[serde(rename = "Value")]
pub value: String,
}
#[derive(Debug, Deserialize, PartialEq)]
pub struct ModeLogicOperator {
#[serde(rename = "Name")]
pub name: String,
#[serde(rename = "Mode")]
pub modes: Vec<Mode>,
}
#[derive(Debug, Deserialize, PartialEq)]
pub struct Mode {
#[serde(rename = "Name")]
pub name: String,
#[serde(rename = "ModeLogicOperator", default)]
pub mode_logic_operator: Option<ModeLogicOperator>,
}
}
#[derive(Debug, PartialEq)]
pub struct ChipDma {
pub peripherals: HashMap<String, Vec<stm32_data_serde::chip::core::peripheral::DmaChannel>>,
pub channels: Vec<stm32_data_serde::chip::core::DmaChannels>,
}
#[derive(Debug)]
pub struct DmaChannels(pub HashMap<String, ChipDma>);
impl DmaChannels {
pub fn parse() -> anyhow::Result<Self> {
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<String, u8> = 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::<String, u8>::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<u8> = 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<String, u8> =
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))
}
}

150
src/docs.rs Normal file
View File

@ -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<File>,
}
#[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<Mcu>,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct Mcu {
#[serde(rename = "RPN")]
pub rpn: String,
pub files: Vec<McuFile>,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct McuFile {
pub file_id: String,
}
}
impl From<mcufinder::File> 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<String, stm32_data_serde::chip::Doc>);
impl AllMcuFiles {
pub fn parse() -> anyhow::Result<Self> {
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<String, Vec<String>>);
impl PerMcuFiles {
pub fn parse() -> anyhow::Result<Self> {
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::<String, Vec<String>>::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<Self> {
Ok(Self {
all_mcu_files: AllMcuFiles::parse()?,
per_mcu_files: PerMcuFiles::parse()?,
})
}
pub fn documents_for(&self, chip_name: &str) -> Vec<stm32_data_serde::chip::Doc> {
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}"),
}
}

125
src/gpio_af.rs Normal file
View File

@ -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<GpioPin>,
}
#[derive(Debug, Deserialize, PartialEq)]
pub struct GpioPin {
#[serde(rename = "Name")]
pub name: String,
#[serde(rename = "PinSignal", default)]
pub pin_signals: Vec<PinSignal>,
}
#[derive(Debug, Deserialize, PartialEq)]
pub struct PinSignal {
#[serde(rename = "Name")]
pub name: String,
#[serde(rename = "SpecificParameter", default)]
pub specific_parameter: Option<SpecificParameter>,
}
#[derive(Debug, Deserialize, PartialEq)]
pub struct SpecificParameter {
#[serde(rename = "PossibleValue")]
pub possible_value: String,
}
}
pub fn clean_pin(pin_name: &str) -> Option<stm32_data_serde::chip::core::peripheral::pin::Pin> {
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<String, HashMap<String, Vec<stm32_data_serde::chip::core::peripheral::Pin>>>);
impl Af {
pub fn parse() -> anyhow::Result<Self> {
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))
}
}

286
src/header.rs Normal file
View File

@ -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<Self> {
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<String, String>);
impl HeaderMap {
pub fn parse() -> anyhow::Result<Self> {
let mut res = HashMap::new();
for (mut header, chips) in
serde_yaml::from_str::<HashMap<String, String>>(&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<String, ParsedHeader>);
impl HeadersParsed {
pub fn parse() -> anyhow::Result<Self> {
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<String, i64>);
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<u32> {
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<String> = 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<String>,
pub interrupts: HashMap<String, HashMap<String, u8>>,
pub defines: HashMap<String, Defines>,
}
impl ParsedHeader {
fn parse(f: impl AsRef<std::path::Path>) -> anyhow::Result<Self> {
let mut irqs = HashMap::<String, HashMap<String, u8>>::new();
let mut defines = HashMap::<String, Defines>::new();
let mut cores = Vec::<String>::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,
})
}
}

384
src/interrupts.rs Normal file
View File

@ -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<RefParameter>,
}
#[derive(Debug, Deserialize, PartialEq)]
pub struct RefParameter {
#[serde(rename = "Name")]
pub name: String,
#[serde(rename = "PossibleValue", default)]
pub possible_values: Vec<PossibleValue>,
}
#[derive(Debug, Deserialize, PartialEq)]
pub struct PossibleValue {
#[serde(rename = "Comment")]
pub comment: String,
#[serde(rename = "Value")]
pub value: String,
}
}
#[derive(Debug)]
pub struct ChipInterrupts(
pub HashMap<(String, String), HashMap<String, Vec<stm32_data_serde::chip::core::peripheral::Interrupt>>>,
);
impl ChipInterrupts {
pub fn parse() -> anyhow::Result<Self> {
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::<String, _>::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::<String>::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<String> {
// Treat IRQ names are "tokens" separated by `_`, except some tokens
// contain `_` themselves, such as `C1_RX`.
let r = regex!(r"(SPDIF_RX|EP\d+_(IN|OUT)|OTG_FS|OTG_HS|USB_FS|C1_RX|C1_TX|C2_RX|C2_TX|[A-Z0-9]+(_\d+)*)_*");
let name = name.to_ascii_uppercase();
r.captures_iter(&name)
.map(|cap| cap.get(1).unwrap().as_str().to_string())
.collect()
}
fn match_peris(peris: &[String], name: &str) -> Vec<String> {
const PERI_OVERRIDE: &[(&str, &[&str])] = &[
("USB_FS", &["USB"]),
("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<String> {
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()]
}

102
src/main.rs Normal file
View File

@ -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();
}
<REGEX as ::ref_thread_local::RefThreadLocal<::regex::Regex>>::borrow(&REGEX)
}};
}
struct Stopwatch {
start: std::time::Instant,
section_start: Option<std::time::Instant>,
}
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(())
}

348
src/memory.rs Normal file
View File

@ -0,0 +1,348 @@
use std::fs;
#[derive(Debug, PartialEq)]
struct Memory {
pub device_id: u16,
pub names: Vec<String>,
pub ram: Ram,
pub flash: Option<Flash>,
}
#[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<String> {
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<String> {
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(&current_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(&current_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<T, D::Error>
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<Option<T>, 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<peripherals::Peripheral>,
}
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<u8>,
#[serde(rename = "Configuration", default)]
pub configuration: Vec<peripheral::Configuration>,
}
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<configuration::Parameters>,
#[serde(rename = "Allignement", deserialize_with = "opt_from_hex", default)]
pub allignement: Option<u32>,
#[serde(rename = "Bank")]
pub bank: Vec<configuration::Bank>,
}
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<bank::Field>,
}
mod bank {
use serde::Deserialize;
#[derive(Debug, Deserialize, PartialEq)]
pub struct Field {
#[serde(rename = "Parameters")]
pub parameters: super::Parameters,
}
}
}
}
}
}
}
}
pub struct Memories(Vec<Memory>);
impl Memories {
pub fn parse() -> anyhow::Result<Self> {
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)
}

92
src/rcc.rs Normal file
View File

@ -0,0 +1,92 @@
use std::collections::HashMap;
use crate::regex;
#[derive(Debug)]
pub struct PeripheralToClock(
HashMap<(String, String, String), HashMap<String, stm32_data_serde::chip::core::peripheral::Rcc>>,
);
impl PeripheralToClock {
pub fn parse() -> anyhow::Result<Self> {
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(&reg.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
}
}
}

View File

@ -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"

287
stm32-data-serde/src/lib.rs Normal file
View File

@ -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<chip::Package>,
pub memory: Vec<chip::Memory>,
pub docs: Vec<chip::Doc>,
pub cores: Vec<chip::Core>,
}
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<memory::Settings>,
}
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<core::Peripheral>,
pub interrupts: Vec<core::Interrupt>,
pub dma_channels: Vec<core::DmaChannels>,
}
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<peripheral::Registers>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rcc: Option<peripheral::Rcc>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub pins: Vec<peripheral::Pin>,
#[serde(skip_serializing_if = "Option::is_none")]
pub interrupts: Option<Vec<peripheral::Interrupt>>, // TODO: This should just be a Vec
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub dma_channels: Vec<peripheral::DmaChannel>,
}
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<rcc::Reset>,
}
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<u8>,
}
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<Self> {
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(Pin::parse(v).unwrap())
}
}
impl<'de> Deserialize<'de> for Pin {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub channel: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dmamux: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub request: Option<u8>,
}
}
#[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<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dmamux_channel: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub supports_2d: Option<bool>,
}
}
}
#[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<Item = Cow<'_, str>> + '_ {
str::from_utf8(file).unwrap().lines().map(normalize_line)
}
fn check_file(path: impl AsRef<Path>) {
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());
});
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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()

View File

@ -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
]

View File

@ -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)

View File

@ -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[:]

View File

@ -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)