use std::collections::BTreeMap;
use svd_parser::svd::{
Access, DeriveFrom, Device, FieldInfo, ModifiedWriteValues, PeripheralInfo, ReadAction,
RegisterCluster, RegisterInfo, RegisterProperties,
};
use crate::svd::Error;
pub(crate) struct Model {
pub(crate) device_name: String,
pub(crate) cpu: Cpu,
pub(crate) instances: Vec<Instance>,
pub(crate) layouts: BTreeMap<String, Vec<Register>>,
}
pub(crate) struct Cpu {
pub(crate) name: String,
pub(crate) fpu: bool,
pub(crate) interrupt_lines: u16,
pub(crate) interrupt_lines_stated: bool,
}
pub(crate) struct Instance {
pub(crate) name: String,
pub(crate) base: u64,
pub(crate) size: u64,
pub(crate) interrupts: Vec<u16>,
pub(crate) layout: String,
}
pub(crate) struct Register {
pub(crate) name: String,
pub(crate) offset: u32,
pub(crate) bits: u32,
pub(crate) reset: u64,
pub(crate) access: Access,
pub(crate) read: Option<ReadAction>,
pub(crate) write: Option<ModifiedWriteValues>,
pub(crate) fields: Vec<Field>,
pub(crate) alternate: bool,
}
pub(crate) struct Field {
pub(crate) name: String,
pub(crate) bit: u8,
pub(crate) width: u8,
pub(crate) access: Access,
pub(crate) read: Option<ReadAction>,
pub(crate) write: Option<ModifiedWriteValues>,
}
const BITS_PER_BYTE: u32 = 8;
const DEFAULT_REGISTER_BITS: u32 = 32;
const REGISTER_WIDTHS: [u32; 4] = [8, 16, 32, 64];
impl Register {
pub(crate) const fn end(&self) -> u32 {
self.offset + self.bits / BITS_PER_BYTE
}
}
pub(crate) fn from_device(device: &Device) -> Result<Model, Error> {
let layout_of = layout_roots(device);
let expanded = svd_parser::expand::expand(device).map_err(Error::Svd)?;
let cpu = expanded.cpu.as_ref().ok_or(Error::NoCpu)?;
let mut instances = Vec::new();
let mut layouts = BTreeMap::new();
for peripheral in &expanded.peripherals {
let defaults = peripheral
.default_register_properties
.derive_from(&expanded.default_register_properties);
let layout = layout_of
.get(&peripheral.name)
.cloned()
.unwrap_or_else(|| peripheral.name.clone());
if layout == peripheral.name {
layouts.insert(layout.clone(), registers_of(peripheral, &defaults)?);
}
instances.push(instance_from(peripheral, layout)?);
}
instances.sort_by(|left, right| (left.base, &left.name).cmp(&(right.base, &right.name)));
let highest_line = instances
.iter()
.flat_map(|instance| &instance.interrupts)
.max()
.copied();
let interrupt_lines = cpu
.device_num_interrupts
.unwrap_or_else(|| highest_line.map_or(0, |line| u32::from(line) + 1));
Ok(Model {
device_name: expanded.name.clone(),
cpu: Cpu {
name: cpu.name.clone(),
fpu: cpu.fpu_present,
interrupt_lines: narrow(interrupt_lines.into(), "cpu", "deviceNumInterrupts")?,
interrupt_lines_stated: cpu.device_num_interrupts.is_some(),
},
instances,
layouts,
})
}
fn layout_roots(device: &Device) -> BTreeMap<String, String> {
let derived: BTreeMap<&str, &str> = device
.peripherals
.iter()
.filter(|peripheral| peripheral.registers.is_none())
.filter_map(|peripheral| {
peripheral
.derived_from
.as_deref()
.map(|parent| (peripheral.name.as_str(), parent))
})
.collect();
derived
.keys()
.map(|&name| {
let mut root = name;
while let Some(&parent) = derived.get(root) {
root = parent;
}
(name.to_owned(), root.to_owned())
})
.collect()
}
fn instance_from(peripheral: &PeripheralInfo, layout: String) -> Result<Instance, Error> {
let blocks = peripheral.address_block.as_deref().unwrap_or(&[]);
let size = blocks
.iter()
.map(|block| u64::from(block.offset) + u64::from(block.size))
.max()
.ok_or_else(|| Error::NoAddressBlock {
instance: peripheral.name.clone(),
})?;
let mut interrupts = peripheral
.interrupt
.iter()
.map(|interrupt| narrow(interrupt.value.into(), &peripheral.name, "interrupt"))
.collect::<Result<Vec<u16>, Error>>()?;
interrupts.sort_unstable();
interrupts.dedup();
Ok(Instance {
name: peripheral.name.clone(),
base: peripheral.base_address,
size,
interrupts,
layout,
})
}
fn registers_of(
peripheral: &PeripheralInfo,
defaults: &RegisterProperties,
) -> Result<Vec<Register>, Error> {
let mut registers = Vec::new();
for item in peripheral.registers.as_deref().unwrap_or(&[]) {
let RegisterCluster::Register(info) = item else {
return Err(Error::Svd(anyhow::anyhow!(
"{}: a cluster survived expansion",
peripheral.name
)));
};
registers.push(register_from(&peripheral.name, info, defaults)?);
}
registers.sort_by(|left, right| (left.offset, &left.name).cmp(&(right.offset, &right.name)));
Ok(registers)
}
fn register_from(
peripheral_name: &str,
info: &RegisterInfo,
defaults: &RegisterProperties,
) -> Result<Register, Error> {
let path = format!("{peripheral_name}.{}", info.name);
let properties = info.properties.derive_from(defaults);
let bits = properties.size.unwrap_or(DEFAULT_REGISTER_BITS);
if !REGISTER_WIDTHS.contains(&bits) {
return Err(Error::Width {
register: path,
bits,
});
}
let access = properties.access.unwrap_or(Access::ReadWrite);
let mut fields = info
.fields
.as_deref()
.unwrap_or(&[])
.iter()
.map(|field| field_from(&path, field, bits, access, info))
.collect::<Result<Vec<Field>, Error>>()?;
fields.sort_by(|left, right| (left.bit, &left.name).cmp(&(right.bit, &right.name)));
Ok(Register {
name: info.name.clone(),
offset: info.address_offset,
bits,
reset: properties.reset_value.unwrap_or(0),
access,
read: info.read_action,
write: info.modified_write_values,
fields,
alternate: info.alternate_register.is_some() || info.alternate_group.is_some(),
})
}
fn field_from(
register_path: &str,
field: &FieldInfo,
register_bits: u32,
register_access: Access,
register: &RegisterInfo,
) -> Result<Field, Error> {
let end = field.bit_range.offset + field.bit_range.width;
if end > register_bits {
return Err(Error::FieldOutOfRange {
register: register_path.to_owned(),
field: field.name.clone(),
end,
bits: register_bits,
});
}
Ok(Field {
name: field.name.clone(),
bit: narrow(field.bit_range.offset.into(), register_path, "bitOffset")?,
width: narrow(field.bit_range.width.into(), register_path, "bitWidth")?,
access: field.access.unwrap_or(register_access),
read: field.read_action.or(register.read_action),
write: field
.modified_write_values
.or(register.modified_write_values),
})
}
fn narrow<T: TryFrom<u64>>(value: u64, element: &str, what: &'static str) -> Result<T, Error> {
T::try_from(value).map_err(|_overflow| Error::TooLarge {
element: element.to_owned(),
what,
value,
})
}