// Copyright 2018 The Chromium OS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. use std::cmp; use std::collections::{BTreeSet, HashMap}; use crate::{Alloc, Error, Result}; /// Manages allocating address ranges. /// Use `AddressAllocator` whenever an address range needs to be allocated to different users. /// Allocations must be uniquely tagged with an Alloc enum, which can be used for lookup. /// An human-readable tag String must also be provided for debugging / reference. /// /// # Examples /// /// ``` /// // Anon is used for brevity. Don't manually instantiate Anon allocs! /// # use resources::{Alloc, AddressAllocator}; /// AddressAllocator::new(0x1000, 0x10000, Some(0x100)).map(|mut pool| { /// assert_eq!(pool.allocate(0x110, Alloc::Anon(0), "caps".to_string()), Ok(0x1000)); /// assert_eq!(pool.allocate(0x100, Alloc::Anon(1), "cache".to_string()), Ok(0x1200)); /// assert_eq!(pool.allocate(0x100, Alloc::Anon(2), "etc".to_string()), Ok(0x1300)); /// assert_eq!(pool.get(&Alloc::Anon(1)), Some(&(0x1200, 0x100, "cache".to_string()))); /// }); /// ``` #[derive(Debug, Eq, PartialEq)] pub struct AddressAllocator { alignment: u64, allocs: HashMap, regions: BTreeSet<(u64, u64)>, } impl AddressAllocator { /// Creates a new `AddressAllocator` for managing a range of addresses. /// Can return `None` if `pool_base` + `pool_size` overflows a u64 or if alignment isn't a power /// of two. /// /// * `pool_base` - The starting address of the range to manage. /// * `pool_size` - The size of the address range in bytes. /// * `align_size` - The minimum size of an address region to align to, defaults to four. pub fn new(pool_base: u64, pool_size: u64, align_size: Option) -> Result { if pool_size == 0 { return Err(Error::PoolSizeZero); } let pool_end = pool_base .checked_add(pool_size - 1) .ok_or(Error::PoolOverflow { base: pool_base, size: pool_size, })?; let alignment = align_size.unwrap_or(4); if !alignment.is_power_of_two() || alignment == 0 { return Err(Error::BadAlignment); } let mut regions = BTreeSet::new(); regions.insert((pool_base, pool_end)); Ok(AddressAllocator { alignment, allocs: HashMap::new(), regions, }) } /// Allocates a range of addresses from the managed region with an optional tag /// and minimal alignment. Returns allocated_address. (allocated_address, size, tag) /// can be retrieved through the `get` method. pub fn allocate_with_align( &mut self, size: u64, alloc: Alloc, tag: String, alignment: u64, ) -> Result { let alignment = cmp::max(self.alignment, alignment); if self.allocs.contains_key(&alloc) { return Err(Error::ExistingAlloc(alloc)); } if size == 0 { return Err(Error::AllocSizeZero); } if !alignment.is_power_of_two() { return Err(Error::BadAlignment); } // finds first region matching alignment and size. match self .regions .iter() .find(|range| { match range.0 % alignment { 0 => range.0.checked_add(size - 1), r => range.0.checked_add(size - 1 + alignment - r), } .map_or(false, |end| end <= range.1) }) .cloned() { Some(slot) => { self.regions.remove(&slot); let start = match slot.0 % alignment { 0 => slot.0, r => slot.0 + alignment - r, }; let end = start + size - 1; if slot.0 < start { self.regions.insert((slot.0, start - 1)); } if slot.1 > end { self.regions.insert((end + 1, slot.1)); } self.allocs.insert(alloc, (start, size, tag)); Ok(start) } None => Err(Error::OutOfSpace), } } pub fn allocate(&mut self, size: u64, alloc: Alloc, tag: String) -> Result { self.allocate_with_align(size, alloc, tag, self.alignment) } /// Allocates a range of addresses from the managed region with an optional tag /// and required location. Allocation alignment is not enforced. /// Returns OutOfSpace if requested range is not available (e.g. already allocated /// with a different alloc tag). pub fn allocate_at(&mut self, start: u64, size: u64, alloc: Alloc, tag: String) -> Result<()> { if self.allocs.contains_key(&alloc) { return Err(Error::ExistingAlloc(alloc)); } if size == 0 { return Err(Error::AllocSizeZero); } let end = start.checked_add(size - 1).ok_or(Error::OutOfSpace)?; match self .regions .iter() .find(|range| range.0 <= start && range.1 >= end) .cloned() { Some(slot) => { self.regions.remove(&slot); if slot.0 < start { self.regions.insert((slot.0, start - 1)); } if slot.1 > end { self.regions.insert((end + 1, slot.1)); } self.allocs.insert(alloc, (start, size, tag)); Ok(()) } None => Err(Error::OutOfSpace), } } /// Releases exising allocation back to free pool. pub fn release(&mut self, alloc: Alloc) -> Result<()> { self.allocs .remove(&alloc) .map_or_else(|| Err(Error::BadAlloc(alloc)), |v| self.insert_at(v.0, v.1)) } /// Returns allocation associated with `alloc`, or None if no such allocation exists. pub fn get(&self, alloc: &Alloc) -> Option<&(u64, u64, String)> { self.allocs.get(alloc) } /// Insert range of addresses into the pool, coalescing neighboring regions. fn insert_at(&mut self, start: u64, size: u64) -> Result<()> { if size == 0 { return Err(Error::AllocSizeZero); } let mut slot = (start, start.checked_add(size - 1).ok_or(Error::OutOfSpace)?); let mut left = None; let mut right = None; // simple coalescing with linear search over free regions. // // Calculating the distance between start and end of two regions we can // detect if they are disjoint (>1), adjacent (=1) or possibly // overlapping (<1). Saturating arithmetic is used to avoid overflow. // Overlapping regions are detected if both oposite ends are overlapping. // Algorithm assumes all existing regions are disjoined and represented // as pair of inclusive location point (start, end), where end >= start. for range in self.regions.iter() { match ( slot.0.saturating_sub(range.1), range.0.saturating_sub(slot.1), ) { (1, 0) => { left = Some(*range); } (0, 1) => { right = Some(*range); } (0, 0) => { return Err(Error::RegionOverlap { base: start, size }); } (_, _) => (), } } if let Some(left) = left { self.regions.remove(&left); slot.0 = left.0; } if let Some(right) = right { self.regions.remove(&right); slot.1 = right.1; } self.regions.insert(slot); Ok(()) } /// Returns an address from associated PCI `alloc` given an allocation offset and size. pub fn address_from_pci_offset(&self, alloc: Alloc, offset: u64, size: u64) -> Result { match alloc { Alloc::PciBar { .. } => (), _ => return Err(Error::InvalidAlloc(alloc)), }; match self.allocs.get(&alloc) { Some((start_addr, length, _)) => { let address = start_addr.checked_add(offset).ok_or(Error::OutOfBounds)?; let range = *start_addr..*start_addr + *length; let end = address.checked_add(size).ok_or(Error::OutOfBounds)?; match (range.contains(&address), range.contains(&end)) { (true, true) => Ok(address), _ => return Err(Error::OutOfBounds), } } None => return Err(Error::InvalidAlloc(alloc)), } } } #[cfg(test)] mod tests { use super::*; #[test] fn new_fails_overflow() { assert!(AddressAllocator::new(u64::max_value(), 0x100, None).is_err()); } #[test] fn new_fails_size_zero() { assert!(AddressAllocator::new(0x1000, 0, None).is_err()); } #[test] fn new_fails_alignment_zero() { assert!(AddressAllocator::new(0x1000, 0x10000, Some(0)).is_err()); } #[test] fn new_fails_alignment_non_power_of_two() { assert!(AddressAllocator::new(0x1000, 0x10000, Some(200)).is_err()); } #[test] fn allocate_fails_exising_alloc() { let mut pool = AddressAllocator::new(0x1000, 0x1000, Some(0x100)).unwrap(); assert_eq!( pool.allocate(0x800, Alloc::Anon(0), String::from("bar0")), Ok(0x1000) ); assert_eq!( pool.allocate(0x800, Alloc::Anon(0), String::from("bar0")), Err(Error::ExistingAlloc(Alloc::Anon(0))) ); } #[test] fn allocate_fails_not_enough_space() { let mut pool = AddressAllocator::new(0x1000, 0x1000, Some(0x100)).unwrap(); assert_eq!( pool.allocate(0x800, Alloc::Anon(0), String::from("bar0")), Ok(0x1000) ); assert_eq!( pool.allocate(0x900, Alloc::Anon(1), String::from("bar1")), Err(Error::OutOfSpace) ); assert_eq!( pool.allocate(0x800, Alloc::Anon(2), String::from("bar2")), Ok(0x1800) ); } #[test] fn allocate_with_special_alignment() { let mut pool = AddressAllocator::new(0x1000, 0x1000, Some(0x100)).unwrap(); assert_eq!( pool.allocate(0x10, Alloc::Anon(0), String::from("bar0")), Ok(0x1000) ); assert_eq!( pool.allocate_at(0x1200, 0x100, Alloc::Anon(1), String::from("bar1")), Ok(()) ); assert_eq!( pool.allocate_with_align(0x800, Alloc::Anon(2), String::from("bar2"), 0x800), Ok(0x1800) ); } #[test] fn allocate_and_split_allocate_at() { let mut pool = AddressAllocator::new(0x1000, 0x1000, Some(0x100)).unwrap(); assert_eq!( pool.allocate_at(0x1200, 0x800, Alloc::Anon(0), String::from("bar0")), Ok(()) ); assert_eq!( pool.allocate(0x800, Alloc::Anon(1), String::from("bar1")), Err(Error::OutOfSpace) ); assert_eq!( pool.allocate(0x600, Alloc::Anon(2), String::from("bar2")), Ok(0x1a00) ); assert_eq!( pool.allocate(0x200, Alloc::Anon(3), String::from("bar3")), Ok(0x1000) ); } #[test] fn allocate_alignment() { let mut pool = AddressAllocator::new(0x1000, 0x10000, Some(0x100)).unwrap(); assert_eq!( pool.allocate(0x110, Alloc::Anon(0), String::from("bar0")), Ok(0x1000) ); assert_eq!( pool.allocate(0x100, Alloc::Anon(1), String::from("bar1")), Ok(0x1200) ); } #[test] fn allocate_retrieve_alloc() { let mut pool = AddressAllocator::new(0x1000, 0x10000, Some(0x100)).unwrap(); assert_eq!( pool.allocate(0x110, Alloc::Anon(0), String::from("bar0")), Ok(0x1000) ); assert_eq!( pool.get(&Alloc::Anon(0)), Some(&(0x1000, 0x110, String::from("bar0"))) ); } #[test] fn allocate_with_alignment_allocator_alignment() { let mut pool = AddressAllocator::new(0x1000, 0x10000, Some(0x100)).unwrap(); assert_eq!( pool.allocate_with_align(0x110, Alloc::Anon(0), String::from("bar0"), 0x1), Ok(0x1000) ); assert_eq!( pool.allocate_with_align(0x100, Alloc::Anon(1), String::from("bar1"), 0x1), Ok(0x1200) ); } #[test] fn allocate_with_alignment_custom_alignment() { let mut pool = AddressAllocator::new(0x1000, 0x10000, Some(0x4)).unwrap(); assert_eq!( pool.allocate_with_align(0x110, Alloc::Anon(0), String::from("bar0"), 0x100), Ok(0x1000) ); assert_eq!( pool.allocate_with_align(0x100, Alloc::Anon(1), String::from("bar1"), 0x100), Ok(0x1200) ); } #[test] fn allocate_with_alignment_no_allocator_alignment() { let mut pool = AddressAllocator::new(0x1000, 0x10000, None).unwrap(); assert_eq!( pool.allocate_with_align(0x110, Alloc::Anon(0), String::from("bar0"), 0x100), Ok(0x1000) ); assert_eq!( pool.allocate_with_align(0x100, Alloc::Anon(1), String::from("bar1"), 0x100), Ok(0x1200) ); } #[test] fn allocate_with_alignment_alignment_non_power_of_two() { let mut pool = AddressAllocator::new(0x1000, 0x10000, None).unwrap(); assert!(pool .allocate_with_align(0x110, Alloc::Anon(0), String::from("bar0"), 200) .is_err()); } #[test] fn allocate_with_release() { let mut pool = AddressAllocator::new(0x1000, 0x1000, None).unwrap(); assert_eq!( pool.allocate_with_align(0x100, Alloc::Anon(0), String::from("bar0"), 0x100), Ok(0x1000) ); assert!(pool.release(Alloc::Anon(0)).is_ok()); assert_eq!( pool.allocate_with_align(0x1000, Alloc::Anon(0), String::from("bar0"), 0x100), Ok(0x1000) ); } #[test] fn coalescing_and_overlap() { let mut pool = AddressAllocator::new(0x1000, 0x1000, None).unwrap(); assert!(pool.insert_at(0x3000, 0x1000).is_ok()); assert!(pool.insert_at(0x1fff, 0x20).is_err()); assert!(pool.insert_at(0x2ff1, 0x10).is_err()); assert!(pool.insert_at(0x1800, 0x1000).is_err()); assert!(pool.insert_at(0x2000, 0x1000).is_ok()); assert_eq!( pool.allocate(0x3000, Alloc::Anon(0), String::from("bar0")), Ok(0x1000) ); } #[test] fn coalescing_single_addresses() { let mut pool = AddressAllocator::new(0x1000, 0x1000, None).unwrap(); assert!(pool.insert_at(0x2001, 1).is_ok()); assert!(pool.insert_at(0x2003, 1).is_ok()); assert!(pool.insert_at(0x2000, 1).is_ok()); assert!(pool.insert_at(0x2002, 1).is_ok()); assert_eq!( pool.allocate(0x1004, Alloc::Anon(0), String::from("bar0")), Ok(0x1000) ); } #[test] fn allocate_and_verify_pci_offset() { let mut pool = AddressAllocator::new(0x1000, 0x10000, None).unwrap(); let pci_bar0 = Alloc::PciBar { bus: 1, dev: 2, func: 0, bar: 0, }; let pci_bar1 = Alloc::PciBar { bus: 1, dev: 2, func: 0, bar: 1, }; let pci_bar2 = Alloc::PciBar { bus: 1, dev: 2, func: 0, bar: 2, }; let anon = Alloc::Anon(1); assert_eq!( pool.allocate(0x800, pci_bar0, String::from("bar0")), Ok(0x1000) ); assert_eq!( pool.allocate(0x800, pci_bar1, String::from("bar1")), Ok(0x1800) ); assert_eq!(pool.allocate(0x800, anon, String::from("anon")), Ok(0x2000)); assert_eq!( pool.address_from_pci_offset(pci_bar0, 0x600, 0x100), Ok(0x1600) ); assert_eq!( pool.address_from_pci_offset(pci_bar1, 0x600, 0x100), Ok(0x1E00) ); assert_eq!( pool.address_from_pci_offset(pci_bar0, 0x7FE, 0x001), Ok(0x17FE) ); assert_eq!( pool.address_from_pci_offset(pci_bar0, 0x7FF, 0x001), Err(Error::OutOfBounds) ); assert_eq!( pool.address_from_pci_offset(pci_bar2, 0x7FF, 0x001), Err(Error::InvalidAlloc(pci_bar2)) ); assert_eq!( pool.address_from_pci_offset(anon, 0x600, 0x100), Err(Error::InvalidAlloc(anon)) ); } }