mod check;
mod emit;
mod model;
use std::collections::BTreeMap;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use sha2::{Digest, Sha256};
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("not a readable SVD")]
Svd(#[source] anyhow::Error),
#[error("the SVD describes no CPU")]
NoCpu,
#[error("{instance} has no address block")]
NoAddressBlock {
instance: String,
},
#[error("{register} is {bits} bits wide, and a register is 8, 16, 32, or 64")]
Width {
register: String,
bits: u32,
},
#[error("{register}.{field} ends at bit {end} of a {bits}-bit register")]
FieldOutOfRange {
register: String,
field: String,
end: u32,
bits: u32,
},
#[error("registers {lower} and {upper} overlap")]
OverlappingRegisters {
lower: String,
upper: String,
},
#[error("instances {lower} and {upper} overlap")]
OverlappingInstances {
lower: String,
upper: String,
},
#[error("{first} and {second} become the same identifier")]
NameCollision {
first: String,
second: String,
},
#[error("{what} of {element} is {value}, more than the tables hold")]
TooLarge {
element: String,
what: &'static str,
value: u64,
},
#[error("{} must hold exactly one .svd file, found {found}", directory.display())]
SvdCount {
directory: PathBuf,
found: usize,
},
#[error("cannot access {}", path.display())]
Io {
path: PathBuf,
#[source]
source: io::Error,
},
}
pub type GeneratedFiles = BTreeMap<String, String>;
pub fn generate(svd_xml: &str, source_name: &str) -> Result<GeneratedFiles, Error> {
let device = svd_parser::parse(svd_xml).map_err(Error::Svd)?;
let model = model::from_device(&device)?;
check::check(&model)?;
let digest = format!("{:x}", Sha256::digest(svd_xml.as_bytes()));
Ok(emit::emit(&model, source_name, &digest, ¬ice(svd_xml)))
}
fn notice(svd_xml: &str) -> Vec<String> {
let mut rest = svd_xml.trim_start_matches('\u{feff}');
let mut lines = Vec::new();
loop {
rest = rest.trim_start();
let (inside, tail) = if let Some(after) = rest.strip_prefix("<?") {
match after.split_once("?>") {
Some((_, tail)) => ("", tail),
None => break,
}
} else if let Some(after) = rest.strip_prefix("<!--") {
match after.split_once("-->") {
Some(split) => split,
None => break,
}
} else {
break;
};
lines.extend(
inside
.lines()
.map(str::trim)
.filter(|line| {
line.to_ascii_lowercase().starts_with("copyright")
|| line.starts_with("SPDX-License-Identifier:")
})
.map(|line| line.chars().filter(|c| !c.is_control()).collect()),
);
rest = tail;
}
lines
}
pub fn write_module(soc_directory: &Path) -> Result<(), Error> {
let svd_path = svd_file_in(soc_directory)?;
let svd_xml = fs::read_to_string(&svd_path).map_err(|source| Error::Io {
path: svd_path.clone(),
source,
})?;
let source_name = svd_path
.file_name()
.and_then(|name| name.to_str())
.unwrap_or_default();
let files = generate(&svd_xml, source_name)?;
let tables = soc_directory.join("generated");
if tables.exists() {
fs::remove_dir_all(&tables).map_err(|source| Error::Io {
path: tables.clone(),
source,
})?;
}
fs::create_dir_all(&tables).map_err(|source| Error::Io {
path: tables.clone(),
source,
})?;
for (relative, contents) in &files {
let path = soc_directory.join(relative);
fs::write(&path, contents).map_err(|source| Error::Io { path, source })?;
}
Ok(())
}
fn svd_file_in(directory: &Path) -> Result<PathBuf, Error> {
let entries = fs::read_dir(directory).map_err(|source| Error::Io {
path: directory.to_path_buf(),
source,
})?;
let mut svd_files = Vec::new();
for entry in entries {
let path = entry
.map_err(|source| Error::Io {
path: directory.to_path_buf(),
source,
})?
.path();
if path
.extension()
.is_some_and(|extension| extension.eq_ignore_ascii_case("svd"))
{
svd_files.push(path);
}
}
svd_files.sort();
match svd_files.as_slice() {
[only] => Ok(only.clone()),
found => Err(Error::SvdCount {
directory: directory.to_path_buf(),
found: found.len(),
}),
}
}