#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RawMapping {
pub pin: String,
pub af: u8,
pub peripheral: String,
pub signal: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PackError {
Malformed(String),
}
impl core::fmt::Display for PackError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
PackError::Malformed(what) => write!(f, "malformed pack data: {what}"),
}
}
}
impl std::error::Error for PackError {}
#[derive(Debug, Clone, Copy)]
pub enum Patch {
Remove {
pin: &'static str,
af: u8,
peripheral: &'static str,
signal: &'static str,
reason: &'static str,
},
Add {
pin: &'static str,
af: u8,
peripheral: &'static str,
signal: &'static str,
reason: &'static str,
},
}
pub const PATCHES: &[Patch] = &[];
pub fn parse_gpio_modes(xml: &str) -> Result<Vec<RawMapping>, PackError> {
let doc = roxmltree::Document::parse(xml)
.map_err(|e| PackError::Malformed(format!("gpio modes xml: {e}")))?;
let mut mappings = Vec::new();
for gpio_pin in elements_named(&doc, "GPIO_Pin") {
let pin = required_attr(&gpio_pin, "Name")?;
let Some(pin) = normalize_pin_name(pin) else {
continue;
};
for pin_signal in gpio_pin
.children()
.filter(|n| n.is_element() && n.tag_name().name() == "PinSignal")
{
let signal_name = required_attr(&pin_signal, "Name")?;
let Some(af) = gpio_af_value(&pin_signal) else {
continue;
};
let af = af
.strip_prefix("GPIO_AF")
.and_then(|s| s.split('_').next())
.and_then(|s| s.parse::<u8>().ok())
.filter(|af| *af <= 15)
.ok_or_else(|| PackError::Malformed(format!("bad GPIO_AF value {af:?}")))?;
let (peripheral, signal) = signal_name
.split_once('_')
.unwrap_or((signal_name, signal_name));
mappings.push(RawMapping {
pin: pin.to_string(),
af,
peripheral: peripheral.to_string(),
signal: signal.to_string(),
});
}
}
Ok(mappings)
}
pub fn parse_package_pins(xml: &str) -> Result<Vec<String>, PackError> {
let doc = roxmltree::Document::parse(xml)
.map_err(|e| PackError::Malformed(format!("mcu xml: {e}")))?;
Ok(elements_named(&doc, "Pin")
.filter_map(|pin| {
let name = pin.attribute("Name")?;
normalize_pin_name(name).map(str::to_string)
})
.collect())
}
pub fn apply_patches(mappings: &mut Vec<RawMapping>, patches: &[Patch]) {
for patch in patches {
match *patch {
Patch::Remove {
pin,
af,
peripheral,
signal,
reason: _,
} => mappings.retain(|m| {
!(m.pin == pin && m.af == af && m.peripheral == peripheral && m.signal == signal)
}),
Patch::Add {
pin,
af,
peripheral,
signal,
reason: _,
} => mappings.push(RawMapping {
pin: pin.to_string(),
af,
peripheral: peripheral.to_string(),
signal: signal.to_string(),
}),
}
}
}
pub fn generate_table(mappings: &[RawMapping], package_pins: &[String]) -> String {
let mut rows: Vec<(char, u8, &RawMapping)> = mappings
.iter()
.filter(|m| package_pins.contains(&m.pin))
.filter_map(|m| {
let (port, number) = pin_sort_key(&m.pin)?;
Some((port, number, m))
})
.collect();
rows.sort_by_key(|(port, number, m)| {
(*port, *number, m.af, m.peripheral.clone(), m.signal.clone())
});
rows.dedup_by(|a, b| a.2 == b.2);
let mut out = String::from(
"// @generated by build.rs from packdata/ — do not edit.\n\
pub(crate) const F446RE: &[AfMapping] = &[\n",
);
for (port, number, m) in rows {
out.push_str(&format!(
" map(Port::{port}, {number}, {af}, \"{peripheral}\", \"{signal}\"),\n",
af = m.af,
peripheral = m.peripheral,
signal = m.signal,
));
}
out.push_str("];\n");
out
}
fn normalize_pin_name(name: &str) -> Option<&str> {
let base = name.split(['-', '/', ' ']).next().unwrap_or(name);
pin_sort_key(base).map(|_| base)
}
fn pin_sort_key(pin: &str) -> Option<(char, u8)> {
let rest = pin.strip_prefix('P')?;
let port = rest.chars().next().filter(char::is_ascii_uppercase)?;
let number: u8 = rest[1..].parse().ok().filter(|n| *n <= 15)?;
Some((port, number))
}
fn elements_named<'a>(
doc: &'a roxmltree::Document<'a>,
name: &'static str,
) -> impl Iterator<Item = roxmltree::Node<'a, 'a>> {
doc.descendants()
.filter(move |n| n.is_element() && n.tag_name().name() == name)
}
fn required_attr<'a>(
node: &roxmltree::Node<'a, '_>,
attr: &'static str,
) -> Result<&'a str, PackError> {
node.attribute(attr).ok_or_else(|| {
PackError::Malformed(format!(
"<{}> missing {attr} attribute",
node.tag_name().name()
))
})
}
fn gpio_af_value<'a>(pin_signal: &roxmltree::Node<'a, '_>) -> Option<&'a str> {
pin_signal
.children()
.find(|n| {
n.is_element()
&& n.tag_name().name() == "SpecificParameter"
&& n.attribute("Name") == Some("GPIO_AF")
})?
.children()
.find(|n| n.is_element() && n.tag_name().name() == "PossibleValue")?
.text()
}