// SPDX-License-Identifier: MIT // // Copyright IBM Corp. 2024 use std::{collections::BTreeSet, fmt::Display, rc::Rc}; use crate::{ misc::round_up, pv_utils::error::{Error, Result}, }; /// Represents a range from [start, stop) (inclusive start, exclusive stop) #[derive(Debug, Clone, Eq, Ord, PartialEq, PartialOrd)] pub struct Interval { pub start: u64, pub stop: u64, } impl Display for Interval { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { format!("start: {:#10x} stop: {:#10x}", self.start, self.stop - 1).fmt(f) } } impl Interval { /// Create a new [`Interval`]. /// /// # Errors /// /// This function will return an error if `stop` is not larger than `start`. const fn new(start: u64, stop: u64) -> Result { if stop <= start { return Err(Error::InvalidInterval { start, stop }); } Ok(Self { start, stop }) } /// Creates a new [`Interval`] with the start address `start` and the size /// of `size`. /// /// # Errors /// /// This function will return an error if `size == 0` or if there was an /// unexpected overflow. pub fn new_with_size(start: u64, size: u64) -> Result { Self::new( start, start.checked_add(size).ok_or(Error::UnexpectedOverflow)?, ) } const fn contains(&self, addr: u64) -> bool { addr >= self.start && addr < self.stop } /// Returns the size of this [`Interval`]. pub const fn size(&self) -> u64 { self.stop - self.start } } #[derive(Debug, PartialEq, Eq)] pub struct Layout { pub next_addr: u64, pub alignment: u64, chunks: BTreeSet>, } impl Layout { /// Creates a new [`Layout`]. /// /// # Errors /// /// This function will return an error if `start_addr` is not aligned. pub fn new(start_addr: u64, alignment: u64) -> Result { if start_addr != round_up(start_addr, alignment)? { return Err(Error::UnalignedAddress { addr: start_addr, alignment, }); } Ok(Self { next_addr: start_addr, alignment, chunks: BTreeSet::new(), }) } fn is_aligned(&self, addr: u64) -> Result { Ok(addr == round_up(addr, self.alignment)?) } fn overlaps(&self, b: &Interval) -> Option> { for a in self.chunks.iter() { if a.start < b.stop && b.start < a.stop { return Some(a.clone()); } } None } /// Returns the maximum chunk size at the given address `addr`. If there is /// no limit `None` is returned. /// /// # Errors /// /// This function will return an error if the address is in use already. pub fn max_size_of_chunk_at_addr(&self, addr: u64) -> Result> { if !self.is_aligned(addr)? { return Err(Error::UnalignedAddress { addr, alignment: self.alignment, }); } if addr >= self.next_addr { return Ok(None); } for chunk in &self.chunks { if chunk.contains(addr) { return Err(Error::NoUnusedAddr { addr }); } if chunk.start >= addr { let max_size = usize::try_from(chunk.start - addr).unwrap(); return Ok(Some(max_size)); } } Ok(None) } /// Insert an interval in the layout. /// /// # Errors /// /// This function will return an error if the given address was unaligned or /// the interval would overlap with an existing interval in the layout. pub fn insert_interval(&mut self, addr: u64, size: u64) -> Result> { let interval = Interval::new_with_size(addr, size)?; assert!(self.next_addr % self.alignment == 0); if interval.start != round_up(interval.start, self.alignment)? { return Err(Error::UnalignedAddress { addr: interval.start, alignment: self.alignment, }); } if let Some(overlapped) = self.overlaps(&interval) { let msg = format!("{overlapped} ... {interval}"); return Err(Error::IntervalOverlap(msg)); } let interval = Rc::new(interval); self.chunks.insert(interval.clone()); let maybe_next_addr = interval .start .checked_add(round_up(size, self.alignment)?) .ok_or(Error::UnexpectedOverflow)?; if maybe_next_addr > self.next_addr { self.next_addr = maybe_next_addr; } assert!(self.next_addr % self.alignment == 0); Ok(interval) } /// Creates and appends this newly created interval with size `size` to the /// layout. Returns the created interval. /// /// # Errors /// /// This function will return an error if it was not possible to append the /// newly created interval. pub fn push(&mut self, size: u64) -> Result> { let addr = self.next_addr; self.insert_interval(addr, size) } } impl IntoIterator for Layout { type IntoIter = > as IntoIterator>::IntoIter; type Item = Rc; fn into_iter(self) -> Self::IntoIter { self.chunks.into_iter() } } #[allow(clippy::shadow_unrelated)] #[cfg(test)] mod tests { use std::{collections::BTreeSet, rc::Rc}; use proptest::{ prelude::{Just, Strategy}, prop_assert, prop_assert_eq, proptest, }; use crate::pv_utils::{Interval, Layout}; proptest! { #[test] fn interval_new( (a,b) in (0..u64::MAX).prop_flat_map(|a| (Just(a), 0..a)) ) { prop_assert!(b < a); Interval::new(b, a).expect("should not fail"); } #[test] fn interval_new_with_size( (start, size) in (0..u64::MAX).prop_flat_map(|a| (Just(a), 1..=u64::MAX - a)) ) { Interval::new_with_size(start, size).expect("should not fail"); } #[test] fn interval_contains( (start, size, c) in (0_u16..4_u16).prop_flat_map(|a| (Just(a), 1_u16..4_u16)).prop_flat_map(|(a,b)| (Just(a), Just(b), a..(a + b))) ) { let interval = Interval::new_with_size(start.into(), size.into()).expect("should not fail"); prop_assert!(interval.contains(c.into())); } #[test] fn interval_cmp( (start, size, start2) in (1..16_u64).prop_flat_map(|a| (Just(a), 1..16_u16)).prop_flat_map(|(a,b)| (Just(a), Just(b), 0..a)) ) { let interval = Interval::new_with_size(start, size.into()).expect("should not fail"); let interval2 = Interval::new_with_size(start2, size.into()).expect("should not fail"); let interval3 = Interval::new_with_size(start, >::into(size) + 1).expect("should not fail"); let interval4 = Interval::new_with_size(start, size.into()).expect("should not fail"); prop_assert!(interval > interval2); prop_assert!(interval != interval2); prop_assert!(interval < interval3); prop_assert!(interval == interval4); } #[test] fn interval_size((start, size) in (0..u64::MAX).prop_flat_map(|a| (Just(a), 1..=u64::MAX - a)) ) { let interval = Interval::new_with_size(start, size).expect("should not fail"); prop_assert_eq!(interval.size(), size); } } #[test] fn interval_overflow() { Interval::new_with_size(u64::MAX, 1).expect_err("should fail"); } #[test] fn memory_layout_test() { // Unaligned start address let layout = Layout::new(0x1_u64, 0x1000_u64); assert!(layout.is_err()); let mut layout = Layout::new(0x1000_u64, 0x1000_u64).unwrap(); assert_eq!( layout, Layout { next_addr: 0x1000, alignment: 0x1000, chunks: BTreeSet::new(), } ); layout.push(0x16).unwrap(); layout.push(0x1000).unwrap(); layout.push(0x1).unwrap(); let mut bin = BTreeSet::from([ Rc::new(Interval::new_with_size(0x1000, 0x16).expect("should not fail")), Rc::new(Interval::new_with_size(0x2000, 0x1000).expect("should not fail")), Rc::new(Interval::new_with_size(0x3000, 0x1).expect("should not fail")), ]); assert_eq!( layout, Layout { next_addr: 0x4000, alignment: 0x1000, chunks: bin.clone() } ); // Invalid chunk size assert!(layout.push(0x0).is_err()); // NonMonolithic address assert!(layout.insert_interval(0x0, 0x1001).is_err()); assert!(layout.insert_interval(0x0, 0x1000).is_ok()); assert!(layout.insert_interval(0x10, 0x1000).is_err()); bin.insert(Rc::new( Interval::new_with_size(0x0, 0x1000).expect("should not fail"), )); assert_eq!( layout, Layout { next_addr: 0x4000, alignment: 0x1000, chunks: bin.clone() } ); assert!(layout.insert_interval(0x3000, 0x400).is_err()); assert!(layout.insert_interval(0x4000, 0x400).is_ok()); bin.insert(Rc::new( Interval::new_with_size(0x4000, 0x400).expect("should not fail"), )); assert_eq!( layout, Layout { next_addr: 0x5000, alignment: 0x1000, chunks: bin } ); } #[test] fn test_max_interval_size_at_addr() { let mut layout = Layout::new(0x0_u64, 0x1000_u64).expect("should not fail"); assert_eq!( layout, Layout { next_addr: 0x0, alignment: 0x1000, chunks: BTreeSet::new(), } ); layout.push(0x16).unwrap(); layout.push(0x1000).unwrap(); layout.push(0x1).unwrap(); layout.push(0x0).expect_err("should fail"); let bin = BTreeSet::from([ Rc::new(Interval::new_with_size(0x0, 0x16).expect("should not fail")), Rc::new(Interval::new_with_size(0x1000, 0x1000).expect("should not fail")), Rc::new(Interval::new_with_size(0x2000, 0x1).expect("should not fail")), ]); assert_eq!( layout, Layout { next_addr: 0x3000, alignment: 0x1000, chunks: bin, } ); layout .max_size_of_chunk_at_addr(0x5) .expect_err("should fail"); layout .max_size_of_chunk_at_addr(0x2000) .expect_err("should fail"); layout .max_size_of_chunk_at_addr(0x2fff) .expect_err("should fail"); assert_eq!( layout .max_size_of_chunk_at_addr(0x3000) .expect("should not fail"), None ); layout.alignment = 1; assert_eq!( layout .max_size_of_chunk_at_addr(0x16) .expect("should not fail"), Some(0x1000 - 0x16) ); assert_eq!( layout .max_size_of_chunk_at_addr(0xfff) .expect("should not fail"), Some(0x1) ); layout .max_size_of_chunk_at_addr(0x1000) .expect_err("should not fail"); assert_eq!( layout .max_size_of_chunk_at_addr(0x2fff) .expect("should not fail"), None ); assert_eq!( layout .max_size_of_chunk_at_addr(0x3000) .expect("should not fail"), None ); } }