Rewrite python script in rust
This commit is contained in:
parent
1cfd8d516d
commit
6b3a7bc0c2
3
.gitignore
vendored
3
.gitignore
vendored
@ -4,3 +4,6 @@
|
||||
.idea/
|
||||
transform*.yaml
|
||||
__pycache__
|
||||
|
||||
target/
|
||||
Cargo.lock
|
||||
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -1,11 +1,8 @@
|
||||
{
|
||||
"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": {
|
||||
|
27
Cargo.toml
Normal file
27
Cargo.toml
Normal 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
|
@ -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
|
||||
|
1106
src/chips.rs
Normal file
1106
src/chips.rs
Normal file
File diff suppressed because it is too large
Load Diff
337
src/dma.rs
Normal file
337
src/dma.rs
Normal 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
150
src/docs.rs
Normal 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
125
src/gpio_af.rs
Normal 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
286
src/header.rs
Normal 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
384
src/interrupts.rs
Normal 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
102
src/main.rs
Normal 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(®EX)
|
||||
}};
|
||||
}
|
||||
|
||||
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
348
src/memory.rs
Normal 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(¤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<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
92
src/rcc.rs
Normal 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(®.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
|
||||
}
|
||||
}
|
||||
}
|
14
stm32-data-serde/Cargo.toml
Normal file
14
stm32-data-serde/Cargo.toml
Normal 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
287
stm32-data-serde/src/lib.rs
Normal 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
@ -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()
|
@ -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
|
||||
]
|
@ -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)
|
@ -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[:]
|
@ -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)
|
Loading…
x
Reference in New Issue
Block a user