mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
Makes the code easier to read. Reviewed-by: Steffen Eiden <seiden@linux.ibm.com> Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
401 lines
12 KiB
Rust
401 lines
12 KiB
Rust
// 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<Self> {
|
|
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> {
|
|
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<Rc<Interval>>,
|
|
}
|
|
|
|
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<Self> {
|
|
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<bool> {
|
|
Ok(addr == round_up(addr, self.alignment)?)
|
|
}
|
|
|
|
fn overlaps(&self, b: &Interval) -> Option<Rc<Interval>> {
|
|
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<Option<usize>> {
|
|
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<Rc<Interval>> {
|
|
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<Rc<Interval>> {
|
|
let addr = self.next_addr;
|
|
self.insert_interval(addr, size)
|
|
}
|
|
}
|
|
|
|
impl IntoIterator for Layout {
|
|
type IntoIter = <BTreeSet<Rc<Interval>> as IntoIterator>::IntoIter;
|
|
type Item = Rc<Interval>;
|
|
|
|
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, <u16 as Into<u64>>::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
|
|
);
|
|
}
|
|
}
|