use alloc::collections::BTreeMap;
use alloc::ffi::CString;
use alloc::string::String;
use alloc::vec::Vec;
use core::cmp::{Ord, Ordering};
use core::convert::TryInto;
use core::fmt;
use core::mem::size_of_val;
use hashbrown::HashSet;
use super::{
FDT_BEGIN_NODE, FDT_END, FDT_END_NODE, FDT_MAGIC, FDT_PROP, NODE_NAME_MAX_LEN,
PROPERTY_NAME_MAX_LEN,
};
#[derive(Debug, Eq, PartialEq)]
pub enum Error {
PropertyBeforeBeginNode,
PropertyAfterEndNode,
PropertyValueTooLarge,
TotalSizeTooLarge,
InvalidString,
OutOfOrderEndNode,
UnclosedNode,
InvalidMemoryReservation,
OverlappingMemoryReservations,
InvalidNodeName,
InvalidPropertyName,
NodeDepthTooLarge,
DuplicatePhandle,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::PropertyBeforeBeginNode => {
write!(f, "Properties may not be added before beginning a node")
}
Error::PropertyAfterEndNode => {
write!(f, "Properties may not be added after a node has been ended")
}
Error::PropertyValueTooLarge => write!(f, "Property value size must fit in 32 bits"),
Error::TotalSizeTooLarge => write!(f, "Total size must fit in 32 bits"),
Error::InvalidString => write!(f, "Strings cannot contain NUL"),
Error::OutOfOrderEndNode => {
write!(f, "Attempted to end a node that was not the most recent")
}
Error::UnclosedNode => write!(f, "Attempted to call finish without ending all nodes"),
Error::InvalidMemoryReservation => write!(f, "Memory reservation is invalid"),
Error::OverlappingMemoryReservations => {
write!(f, "Memory reservations are overlapping")
}
Error::InvalidNodeName => write!(f, "Invalid node name"),
Error::InvalidPropertyName => write!(f, "Invalid property name"),
Error::NodeDepthTooLarge => write!(f, "Node depth exceeds FDT_MAX_NODE_DEPTH"),
Error::DuplicatePhandle => write!(f, "Duplicate phandle value"),
}
}
}
pub type Result<T> = core::result::Result<T, Error>;
const FDT_HEADER_SIZE: usize = 40;
const FDT_VERSION: u32 = 17;
const FDT_LAST_COMP_VERSION: u32 = 16;
const FDT_MAX_NODE_DEPTH: usize = 64;
#[derive(Debug)]
pub struct FdtWriter {
data: Vec<u8>,
off_mem_rsvmap: u32,
off_dt_struct: u32,
strings: Vec<u8>,
string_offsets: BTreeMap<CString, u32>,
node_depth: usize,
node_ended: bool,
boot_cpuid_phys: u32,
#[allow(dead_code)]
phandles: HashSet<u32>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct FdtReserveEntry {
address: u64,
size: u64,
}
impl FdtReserveEntry {
#[allow(dead_code)]
pub fn new(address: u64, size: u64) -> Result<Self> {
if address.checked_add(size).is_none() || size == 0 {
return Err(Error::InvalidMemoryReservation);
}
Ok(FdtReserveEntry { address, size })
}
}
impl Ord for FdtReserveEntry {
fn cmp(&self, other: &Self) -> Ordering {
self.address.cmp(&other.address)
}
}
impl PartialOrd for FdtReserveEntry {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
fn check_overlapping(mem_reservations: &[FdtReserveEntry]) -> Result<()> {
let mut mem_rsvmap_copy = mem_reservations.to_vec();
mem_rsvmap_copy.sort();
let overlapping = mem_rsvmap_copy.windows(2).any(|w| {
w[0].address + w[0].size > w[1].address
});
if overlapping {
return Err(Error::OverlappingMemoryReservations);
}
Ok(())
}
fn node_name_valid(name: &str) -> bool {
if name.is_empty() {
return true;
}
let mut parts = name.split('@');
let node_name = parts.next().unwrap(); let unit_address = parts.next();
if unit_address.is_some() && parts.next().is_some() {
return false;
}
if node_name.is_empty() || node_name.len() > NODE_NAME_MAX_LEN {
return false;
}
if node_name.contains(|c: char| !node_name_valid_char(c)) {
return false;
}
if let Some(unit_address) = unit_address
&& unit_address.contains(|c: char| !node_name_valid_char(c))
{
return false;
}
true
}
fn node_name_valid_char(c: char) -> bool {
c.is_ascii_alphanumeric() || matches!(c, ',' | '.' | '_' | '+' | '-')
}
#[allow(dead_code)]
fn node_name_valid_first_char(c: char) -> bool {
c.is_ascii_alphabetic()
}
fn property_name_valid(name: &str) -> bool {
if name.is_empty() || name.len() > PROPERTY_NAME_MAX_LEN {
return false;
}
if name.contains(|c: char| !property_name_valid_char(c)) {
return false;
}
true
}
fn property_name_valid_char(c: char) -> bool {
matches!(c, '0'..='9' | 'a'..='z' | 'A'..='Z' | ',' | '.' | '_' | '+' | '?' | '#' | '-')
}
#[derive(Debug)]
pub struct FdtWriterNode {
depth: usize,
}
impl FdtWriter {
pub fn new() -> Result<Self> {
FdtWriter::new_with_mem_reserv(&[])
}
pub fn new_with_mem_reserv(mem_reservations: &[FdtReserveEntry]) -> Result<Self> {
let data = vec![0u8; FDT_HEADER_SIZE];
let mut fdt = FdtWriter {
data,
off_mem_rsvmap: 0,
off_dt_struct: 0,
strings: Vec::new(),
string_offsets: BTreeMap::new(),
node_depth: 0,
node_ended: false,
boot_cpuid_phys: 0,
phandles: HashSet::new(),
};
fdt.align(8);
fdt.off_mem_rsvmap = fdt.data.len() as u32;
check_overlapping(mem_reservations)?;
fdt.write_mem_rsvmap(mem_reservations);
fdt.align(4);
fdt.off_dt_struct = fdt
.data
.len()
.try_into()
.map_err(|_| Error::TotalSizeTooLarge)?;
Ok(fdt)
}
fn write_mem_rsvmap(&mut self, mem_reservations: &[FdtReserveEntry]) {
for rsv in mem_reservations {
self.append_u64(rsv.address);
self.append_u64(rsv.size);
}
self.append_u64(0);
self.append_u64(0);
}
#[allow(dead_code)]
pub fn set_boot_cpuid_phys(&mut self, boot_cpuid_phys: u32) {
self.boot_cpuid_phys = boot_cpuid_phys;
}
fn pad(&mut self, num_bytes: usize) {
self.data.extend(core::iter::repeat_n(0, num_bytes));
}
fn align(&mut self, alignment: usize) {
let offset = self.data.len() % alignment;
if offset != 0 {
self.pad(alignment - offset);
}
}
fn update_u32(&mut self, offset: usize, val: u32) {
let data_slice = &mut self.data[offset..offset + 4];
data_slice.copy_from_slice(&val.to_be_bytes());
}
fn append_u32(&mut self, val: u32) {
self.data.extend_from_slice(&val.to_be_bytes());
}
fn append_u64(&mut self, val: u64) {
self.data.extend_from_slice(&val.to_be_bytes());
}
pub fn begin_node(&mut self, name: &str) -> Result<FdtWriterNode> {
if self.node_depth >= FDT_MAX_NODE_DEPTH {
return Err(Error::NodeDepthTooLarge);
}
let name_cstr = CString::new(name).map_err(|_| Error::InvalidString)?;
if !node_name_valid(name) {
return Err(Error::InvalidNodeName);
}
self.append_u32(FDT_BEGIN_NODE);
self.data.extend(name_cstr.to_bytes_with_nul());
self.align(4);
self.node_depth += 1;
self.node_ended = false;
Ok(FdtWriterNode {
depth: self.node_depth,
})
}
pub fn end_node(&mut self, node: FdtWriterNode) -> Result<()> {
if node.depth != self.node_depth {
return Err(Error::OutOfOrderEndNode);
}
self.append_u32(FDT_END_NODE);
self.node_depth -= 1;
self.node_ended = true;
Ok(())
}
fn intern_string(&mut self, s: CString) -> Result<u32> {
if let Some(off) = self.string_offsets.get(&s) {
Ok(*off)
} else {
let off = self
.strings
.len()
.try_into()
.map_err(|_| Error::TotalSizeTooLarge)?;
self.strings.extend_from_slice(s.to_bytes_with_nul());
self.string_offsets.insert(s, off);
Ok(off)
}
}
pub fn property(&mut self, name: &str, val: &[u8]) -> Result<()> {
if self.node_ended {
return Err(Error::PropertyAfterEndNode);
}
if self.node_depth == 0 {
return Err(Error::PropertyBeforeBeginNode);
}
let name_cstr = CString::new(name).map_err(|_| Error::InvalidString)?;
if !property_name_valid(name) {
return Err(Error::InvalidPropertyName);
}
let len = val
.len()
.try_into()
.map_err(|_| Error::PropertyValueTooLarge)?;
let nameoff = self.intern_string(name_cstr)?;
self.append_u32(FDT_PROP);
self.append_u32(len);
self.append_u32(nameoff);
self.data.extend_from_slice(val);
self.align(4);
Ok(())
}
#[allow(dead_code)]
pub fn property_null(&mut self, name: &str) -> Result<()> {
self.property(name, &[])
}
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64", test))]
pub fn property_string(&mut self, name: &str, val: &str) -> Result<()> {
let cstr_value = CString::new(val).map_err(|_| Error::InvalidString)?;
self.property(name, cstr_value.to_bytes_with_nul())
}
#[allow(dead_code)]
pub fn property_string_list(&mut self, name: &str, values: Vec<String>) -> Result<()> {
let mut bytes = Vec::new();
for s in values {
let cstr = CString::new(s).map_err(|_| Error::InvalidString)?;
bytes.extend_from_slice(cstr.to_bytes_with_nul());
}
self.property(name, &bytes)
}
#[allow(dead_code)]
pub fn property_u32(&mut self, name: &str, val: u32) -> Result<()> {
self.property(name, &val.to_be_bytes())
}
#[allow(dead_code)]
pub fn property_u64(&mut self, name: &str, val: u64) -> Result<()> {
self.property(name, &val.to_be_bytes())
}
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64", test))]
pub fn property_array_u32(&mut self, name: &str, cells: &[u32]) -> Result<()> {
let mut arr = Vec::with_capacity(size_of_val(cells));
for &c in cells {
arr.extend(c.to_be_bytes());
}
self.property(name, &arr)
}
#[allow(dead_code)]
pub fn property_array_u64(&mut self, name: &str, cells: &[u64]) -> Result<()> {
let mut arr = Vec::with_capacity(size_of_val(cells));
for &c in cells {
arr.extend(c.to_be_bytes());
}
self.property(name, &arr)
}
#[allow(dead_code)]
pub fn property_phandle(&mut self, val: u32) -> Result<()> {
if !self.phandles.insert(val) {
return Err(Error::DuplicatePhandle);
}
self.property("phandle", &val.to_be_bytes())
}
pub fn finish(mut self) -> Result<Vec<u8>> {
if self.node_depth > 0 {
return Err(Error::UnclosedNode);
}
self.append_u32(FDT_END);
let size_dt_plus_header: u32 = self
.data
.len()
.try_into()
.map_err(|_| Error::TotalSizeTooLarge)?;
let size_dt_struct = size_dt_plus_header - self.off_dt_struct;
let off_dt_strings = self
.data
.len()
.try_into()
.map_err(|_| Error::TotalSizeTooLarge)?;
let size_dt_strings = self
.strings
.len()
.try_into()
.map_err(|_| Error::TotalSizeTooLarge)?;
let totalsize = self
.data
.len()
.checked_add(self.strings.len())
.ok_or(Error::TotalSizeTooLarge)?;
let totalsize = totalsize.try_into().map_err(|_| Error::TotalSizeTooLarge)?;
self.update_u32(0, FDT_MAGIC);
self.update_u32(4, totalsize);
self.update_u32(2 * 4, self.off_dt_struct);
self.update_u32(3 * 4, off_dt_strings);
self.update_u32(4 * 4, self.off_mem_rsvmap);
self.update_u32(5 * 4, FDT_VERSION);
self.update_u32(6 * 4, FDT_LAST_COMP_VERSION);
self.update_u32(7 * 4, self.boot_cpuid_phys);
self.update_u32(8 * 4, size_dt_strings);
self.update_u32(9 * 4, size_dt_struct);
self.data.append(&mut self.strings);
Ok(self.data)
}
}