| // Copyright 2018 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| //! Manages allocating system resources such as address space and interrupt numbers. |
| |
| use std::collections::btree_map; |
| use std::collections::BTreeMap; |
| |
| use base::pagesize; |
| |
| use crate::address_allocator::AddressAllocator; |
| use crate::address_allocator::AddressAllocatorSet; |
| use crate::AddressRange; |
| use crate::Alloc; |
| use crate::Error; |
| use crate::PciAddress; |
| use crate::Result; |
| |
| /// MMIO address Type |
| /// Low: address allocated from low_address_space |
| /// High: address allocated from high_address_space |
| #[derive(Copy, Clone, PartialEq, Eq)] |
| pub enum MmioType { |
| Low, |
| High, |
| } |
| |
| /// Memory allocation options. |
| #[derive(Copy, Clone, Debug)] |
| pub struct AllocOptions { |
| prefetchable: bool, |
| max_address: u64, |
| alignment: Option<u64>, |
| top_down: bool, |
| } |
| |
| impl Default for AllocOptions { |
| fn default() -> Self { |
| AllocOptions::new() |
| } |
| } |
| |
| impl AllocOptions { |
| pub fn new() -> Self { |
| AllocOptions { |
| prefetchable: false, |
| max_address: u64::MAX, |
| alignment: None, |
| top_down: false, |
| } |
| } |
| |
| /// If `true`, memory may be allocated in a prefetchable/cacheable region. |
| /// If `false`, memory must be allocated within a non-prefetechable region, appropriate for |
| /// device registers. |
| /// Default: `false` |
| pub fn prefetchable(&mut self, prefetchable: bool) -> &mut Self { |
| self.prefetchable = prefetchable; |
| self |
| } |
| |
| /// Largest valid address for the end of the allocated region. |
| /// For example, `u32::MAX` may be used to allocate a region that is addressable with a 32-bit |
| /// pointer. |
| /// Default: `u64::MAX` |
| pub fn max_address(&mut self, max_address: u64) -> &mut Self { |
| self.max_address = max_address; |
| self |
| } |
| |
| /// Minimum alignment of the allocated address. |
| /// Default: `None` (allocation preference of the address allocator pool will be used) |
| pub fn align(&mut self, alignment: u64) -> &mut Self { |
| self.alignment = Some(alignment); |
| self |
| } |
| |
| /// If `true`, prefer allocating from the upper end of the region rather than the low end. |
| /// Default: `false` |
| pub fn top_down(&mut self, top_down: bool) -> &mut Self { |
| self.top_down = top_down; |
| self |
| } |
| } |
| |
| pub struct SystemAllocatorConfig { |
| /// IO ports. Only for x86_64. |
| pub io: Option<AddressRange>, |
| /// Low (<=4GB) MMIO region. |
| /// |
| /// Parts of this region may be reserved or otherwise excluded from the |
| /// created SystemAllocator's MmioType::Low allocator. However, no new |
| /// regions will be added. |
| pub low_mmio: AddressRange, |
| /// High (>4GB) MMIO region. |
| /// |
| /// Parts of this region may be reserved or otherwise excluded from the |
| /// created SystemAllocator's MmioType::High allocator. However, no new |
| /// regions will be added. |
| pub high_mmio: AddressRange, |
| /// Platform MMIO space. Only for ARM. |
| pub platform_mmio: Option<AddressRange>, |
| /// The first IRQ number to give out. |
| pub first_irq: u32, |
| } |
| |
| #[derive(Debug)] |
| pub struct SystemAllocator { |
| io_address_space: Option<AddressAllocator>, |
| |
| // Indexed by MmioType::Low and MmioType::High. |
| mmio_address_spaces: [AddressAllocator; 2], |
| mmio_platform_address_spaces: Option<AddressAllocator>, |
| |
| reserved_region: Option<AddressRange>, |
| |
| // Each bus number has a AddressAllocator |
| pci_allocator: BTreeMap<u8, AddressAllocator>, |
| irq_allocator: AddressAllocator, |
| gpe_allocator: AddressAllocator, |
| next_anon_id: usize, |
| } |
| |
| impl SystemAllocator { |
| /// Creates a new `SystemAllocator` for managing addresses and irq numbers. |
| /// Will return an error if `base` + `size` overflows u64 (or allowed |
| /// maximum for the specific type), or if alignment isn't a power of two. |
| /// |
| /// If `reserve_region_size` is not None, then a region is reserved from |
| /// the start of `config.high_mmio` before the mmio allocator is created. |
| /// |
| /// If `mmio_address_ranges` is not empty, then `config.low_mmio` and |
| /// `config.high_mmio` are intersected with the ranges specified. |
| pub fn new( |
| config: SystemAllocatorConfig, |
| reserve_region_size: Option<u64>, |
| mmio_address_ranges: &[AddressRange], |
| ) -> Result<Self> { |
| let page_size = pagesize() as u64; |
| |
| let (high_mmio, reserved_region) = match reserve_region_size { |
| Some(reserved_len) => { |
| let high_mmio_len = config.high_mmio.len().ok_or(Error::OutOfBounds)?; |
| if reserved_len > high_mmio_len { |
| return Err(Error::OutOfSpace); |
| } |
| let reserved_start = config.high_mmio.start; |
| let reserved_end = reserved_start + reserved_len - 1; |
| let high_mmio_start = reserved_end + 1; |
| let high_mmio_end = config.high_mmio.end; |
| ( |
| AddressRange { |
| start: high_mmio_start, |
| end: high_mmio_end, |
| }, |
| Some(AddressRange { |
| start: reserved_start, |
| end: reserved_end, |
| }), |
| ) |
| } |
| None => (config.high_mmio, None), |
| }; |
| |
| let intersect_mmio_range = |src_range: AddressRange| -> Result<Vec<AddressRange>> { |
| Ok(if mmio_address_ranges.is_empty() { |
| vec![src_range] |
| } else { |
| mmio_address_ranges |
| .iter() |
| .map(|r| r.intersect(src_range)) |
| .collect() |
| }) |
| }; |
| |
| Ok(SystemAllocator { |
| io_address_space: if let Some(io) = config.io { |
| // TODO make sure we don't overlap with existing well known |
| // ports such as 0xcf8 (serial ports). |
| if io.end > 0xffff { |
| return Err(Error::IOPortOutOfRange(io)); |
| } |
| Some(AddressAllocator::new(io, Some(0x400), None)?) |
| } else { |
| None |
| }, |
| mmio_address_spaces: [ |
| // MmioType::Low |
| AddressAllocator::new_from_list( |
| intersect_mmio_range(config.low_mmio)?, |
| Some(page_size), |
| None, |
| )?, |
| // MmioType::High |
| AddressAllocator::new_from_list( |
| intersect_mmio_range(high_mmio)?, |
| Some(page_size), |
| None, |
| )?, |
| ], |
| |
| pci_allocator: BTreeMap::new(), |
| |
| mmio_platform_address_spaces: if let Some(platform) = config.platform_mmio { |
| Some(AddressAllocator::new(platform, Some(page_size), None)?) |
| } else { |
| None |
| }, |
| |
| reserved_region, |
| |
| irq_allocator: AddressAllocator::new( |
| AddressRange { |
| start: config.first_irq as u64, |
| end: 1023, |
| }, |
| Some(1), |
| None, |
| )?, |
| |
| // GPE range depends on ACPIPM_RESOURCE_GPE0_BLK_LEN, which is used to determine |
| // ACPIPM_GPE_MAX. The AddressRange should be in sync with ACPIPM_GPE_MAX. The |
| // hard-coded value is used since devices lib (where ACPIPM_* consts are defined) |
| // depends on resource lib. Therefore using ACPI_* const from device lib will not be |
| // possible because it will require introducing cyclic dependencies. |
| gpe_allocator: AddressAllocator::new( |
| AddressRange { start: 0, end: 255 }, |
| Some(1), |
| None, |
| )?, |
| next_anon_id: 0, |
| }) |
| } |
| |
| /// Reserves the next available system irq number. |
| pub fn allocate_irq(&mut self) -> Option<u32> { |
| let id = self.get_anon_alloc(); |
| self.irq_allocator |
| .allocate(1, id, "irq-auto".to_string()) |
| .map(|v| v as u32) |
| .ok() |
| } |
| |
| /// release irq to system irq number pool |
| pub fn release_irq(&mut self, irq: u32) { |
| let _ = self.irq_allocator.release_containing(irq.into()); |
| } |
| |
| /// Reserves the next available system irq number. |
| pub fn reserve_irq(&mut self, irq: u32) -> bool { |
| let id = self.get_anon_alloc(); |
| self.irq_allocator |
| .allocate_at( |
| AddressRange { |
| start: irq.into(), |
| end: irq.into(), |
| }, |
| id, |
| "irq-fixed".to_string(), |
| ) |
| .is_ok() |
| } |
| |
| /// Reserve the next available system GPE number |
| pub fn allocate_gpe(&mut self) -> Option<u32> { |
| let id = self.get_anon_alloc(); |
| self.gpe_allocator |
| .allocate(1, id, "gpe-auto".to_string()) |
| .map(|v| v as u32) |
| .ok() |
| } |
| |
| fn get_pci_allocator_mut(&mut self, bus: u8) -> Option<&mut AddressAllocator> { |
| match self.pci_allocator.entry(bus) { |
| btree_map::Entry::Occupied(entry) => Some(entry.into_mut()), |
| btree_map::Entry::Vacant(entry) => { |
| // pci root is 00:00.0, Bus 0 next device is 00:01.0 with mandatory function number |
| // zero. |
| let base = if bus == 0 { 8 } else { 0 }; |
| |
| // Each bus supports up to 32 (devices) x 8 (functions). |
| // Prefer allocating at device granularity (preferred_align = 8), but fall back to |
| // allocating individual functions (min_align = 1) when we run out of devices. |
| let pci_alloc = AddressAllocator::new( |
| AddressRange { |
| start: base, |
| end: (32 * 8) - 1, |
| }, |
| Some(1), |
| Some(8), |
| ) |
| .ok()?; |
| |
| Some(entry.insert(pci_alloc)) |
| } |
| } |
| } |
| |
| // Check whether devices exist or not on the specified bus |
| pub fn pci_bus_empty(&self, bus: u8) -> bool { |
| !self.pci_allocator.contains_key(&bus) |
| } |
| |
| /// Allocate PCI slot location. |
| pub fn allocate_pci(&mut self, bus: u8, tag: String) -> Option<PciAddress> { |
| let id = self.get_anon_alloc(); |
| let allocator = self.get_pci_allocator_mut(bus)?; |
| allocator |
| .allocate(1, id, tag) |
| .map(|v| PciAddress { |
| bus, |
| dev: (v >> 3) as u8, |
| func: (v & 7) as u8, |
| }) |
| .ok() |
| } |
| |
| /// Reserve PCI slot location. |
| pub fn reserve_pci(&mut self, pci_addr: PciAddress, tag: String) -> bool { |
| let id = self.get_anon_alloc(); |
| |
| let allocator = match self.get_pci_allocator_mut(pci_addr.bus) { |
| Some(v) => v, |
| None => return false, |
| }; |
| let df = ((pci_addr.dev as u64) << 3) | (pci_addr.func as u64); |
| allocator |
| .allocate_at(AddressRange { start: df, end: df }, id, tag) |
| .is_ok() |
| } |
| |
| /// release PCI slot location. |
| pub fn release_pci(&mut self, pci_addr: PciAddress) -> bool { |
| let allocator = match self.get_pci_allocator_mut(pci_addr.bus) { |
| Some(v) => v, |
| None => return false, |
| }; |
| let df = ((pci_addr.dev as u64) << 3) | (pci_addr.func as u64); |
| allocator.release_containing(df).is_ok() |
| } |
| |
| /// Allocate a memory-mapped I/O region with properties requested in `opts`. |
| pub fn allocate_mmio( |
| &mut self, |
| size: u64, |
| alloc: Alloc, |
| tag: String, |
| opts: &AllocOptions, |
| ) -> Result<u64> { |
| // For now, there is no way to ensure allocations fit in less than 32 bits. |
| // This can be removed once AddressAllocator accepts AllocOptions. |
| if opts.max_address < u32::MAX as u64 { |
| return Err(Error::OutOfSpace); |
| } |
| |
| let mut mmio_type = MmioType::High; |
| if opts.max_address < u64::MAX || !opts.prefetchable { |
| mmio_type = MmioType::Low; |
| } |
| |
| let res = self.allocate_mmio_internal(size, alloc, tag.clone(), opts, mmio_type); |
| // If a high allocation failed, retry in low. The reverse is not valid, since the address |
| // may be out of range and/or prefetchable memory may not be appropriate. |
| if mmio_type == MmioType::High && matches!(res, Err(Error::OutOfSpace)) { |
| self.allocate_mmio_internal(size, alloc, tag, opts, MmioType::Low) |
| } else { |
| res |
| } |
| } |
| |
| fn allocate_mmio_internal( |
| &mut self, |
| size: u64, |
| alloc: Alloc, |
| tag: String, |
| opts: &AllocOptions, |
| mmio_type: MmioType, |
| ) -> Result<u64> { |
| let allocator = &mut self.mmio_address_spaces[mmio_type as usize]; |
| match (opts.alignment, opts.top_down) { |
| (Some(align), true) => allocator.reverse_allocate_with_align(size, alloc, tag, align), |
| (Some(align), false) => allocator.allocate_with_align(size, alloc, tag, align), |
| (None, true) => allocator.reverse_allocate(size, alloc, tag), |
| (None, false) => allocator.allocate(size, alloc, tag), |
| } |
| } |
| |
| /// Reserve specified range from pci mmio, get the overlap of specified |
| /// range with mmio pools, exclude the overlap from mmio allocator. |
| /// |
| /// If any part of the specified range has been allocated, return Error. |
| pub fn reserve_mmio(&mut self, range: AddressRange) -> Result<()> { |
| let mut pools = Vec::new(); |
| for pool in self.mmio_pools() { |
| pools.push(*pool); |
| } |
| pools.sort_by(|a, b| a.start.cmp(&b.start)); |
| for pool in &pools { |
| if pool.start > range.end { |
| break; |
| } |
| |
| let overlap = pool.intersect(range); |
| if !overlap.is_empty() { |
| let id = self.get_anon_alloc(); |
| self.mmio_allocator_any().allocate_at( |
| overlap, |
| id, |
| "pci mmio reserve".to_string(), |
| )?; |
| } |
| } |
| |
| Ok(()) |
| } |
| |
| /// Gets an allocator to be used for platform device MMIO allocation. |
| pub fn mmio_platform_allocator(&mut self) -> Option<&mut AddressAllocator> { |
| self.mmio_platform_address_spaces.as_mut() |
| } |
| |
| /// Gets an allocator to be used for IO memory. |
| pub fn io_allocator(&mut self) -> Option<&mut AddressAllocator> { |
| self.io_address_space.as_mut() |
| } |
| |
| /// Gets an allocator to be used for MMIO allocation. |
| /// MmioType::Low: low mmio allocator |
| /// MmioType::High: high mmio allocator |
| pub fn mmio_allocator(&mut self, mmio_type: MmioType) -> &mut AddressAllocator { |
| &mut self.mmio_address_spaces[mmio_type as usize] |
| } |
| |
| /// Gets a set of allocators to be used for MMIO allocation. |
| /// The set of allocators will try the low and high MMIO allocators, in that order. |
| pub fn mmio_allocator_any(&mut self) -> AddressAllocatorSet { |
| AddressAllocatorSet::new(&mut self.mmio_address_spaces) |
| } |
| |
| /// Gets the pools of all mmio allocators. |
| pub fn mmio_pools(&self) -> Vec<&AddressRange> { |
| self.mmio_address_spaces |
| .iter() |
| .flat_map(|mmio_as| mmio_as.pools()) |
| .collect() |
| } |
| |
| /// Gets the reserved address space region. |
| pub fn reserved_region(&self) -> Option<AddressRange> { |
| self.reserved_region |
| } |
| |
| /// Gets a unique anonymous allocation |
| pub fn get_anon_alloc(&mut self) -> Alloc { |
| self.next_anon_id += 1; |
| Alloc::Anon(self.next_anon_id) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| #[test] |
| fn example() { |
| let mut a = SystemAllocator::new( |
| SystemAllocatorConfig { |
| io: Some(AddressRange { |
| start: 0x1000, |
| end: 0xffff, |
| }), |
| low_mmio: AddressRange { |
| start: 0x3000_0000, |
| end: 0x3000_ffff, |
| }, |
| high_mmio: AddressRange { |
| start: 0x1000_0000, |
| end: 0x1fffffff, |
| }, |
| platform_mmio: None, |
| first_irq: 5, |
| }, |
| None, |
| &[], |
| ) |
| .unwrap(); |
| |
| assert_eq!(a.allocate_irq(), Some(5)); |
| assert_eq!(a.allocate_irq(), Some(6)); |
| assert_eq!(a.allocate_gpe(), Some(0)); |
| assert_eq!(a.allocate_gpe(), Some(1)); |
| assert_eq!( |
| a.mmio_allocator(MmioType::High).allocate( |
| 0x100, |
| Alloc::PciBar { |
| bus: 0, |
| dev: 0, |
| func: 0, |
| bar: 0 |
| }, |
| "bar0".to_string() |
| ), |
| Ok(0x10000000) |
| ); |
| assert_eq!( |
| a.mmio_allocator(MmioType::High).get(&Alloc::PciBar { |
| bus: 0, |
| dev: 0, |
| func: 0, |
| bar: 0 |
| }), |
| Some(&( |
| AddressRange { |
| start: 0x10000000, |
| end: 0x100000ff |
| }, |
| "bar0".to_string() |
| )) |
| ); |
| |
| let id = a.get_anon_alloc(); |
| assert_eq!( |
| a.mmio_allocator(MmioType::Low).allocate_at( |
| AddressRange { |
| start: 0x3000_5000, |
| end: 0x30009fff |
| }, |
| id, |
| "Test".to_string() |
| ), |
| Ok(()) |
| ); |
| assert_eq!( |
| a.mmio_allocator(MmioType::Low).release(id), |
| Ok(AddressRange { |
| start: 0x3000_5000, |
| end: 0x30009fff |
| }) |
| ); |
| assert_eq!( |
| a.reserve_mmio(AddressRange { |
| start: 0x3000_2000, |
| end: 0x30005fff |
| }), |
| Ok(()) |
| ); |
| assert_eq!( |
| a.mmio_allocator(MmioType::Low) |
| .allocate_at( |
| AddressRange { |
| start: 0x3000_5000, |
| end: 0x3000_9fff |
| }, |
| id, |
| "Test".to_string() |
| ) |
| .is_err(), |
| true |
| ); |
| } |
| } |