rdif_pcie/addr_alloc/mod.rs
1// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
3
4//! Manages system resources that can be allocated to VMs and their devices.
5//!
6//! # Example
7//!
8//! Depending on the use case of the VMM, both the `IdAllocator` and the `AddressAllocator`
9//! can be used. In the example below we assume that the `IdAllocator` is used for allocating
10//! unique identifiers for VM devices. We use the address allocator for allocating MMIO ranges
11//! for virtio devices.
12//!
13//! In the example below we use constants that are typical for the x86 platform, but this has no
14//! impact on the code actually working on aarch64.
15//!
16//! ```rust
17//! use std::collections::HashMap;
18//!
19//! use rdif_pcie::addr_alloc::{
20//! AddressAllocator, AllocPolicy, Error, IdAllocator, RangeInclusive, Result,
21//! };
22//!
23//! const FIRST_ADDR_PAST_32BITS: u64 = 1 << 32;
24//! const MEM_32BIT_GAP_SIZE: u64 = 768 << 20;
25//! const MMIO_MEM_START: u64 = FIRST_ADDR_PAST_32BITS - MEM_32BIT_GAP_SIZE;
26//! const PAGE_SIZE: u64 = 0x1000;
27//!
28//! struct DeviceManager {
29//! id_allocator: IdAllocator,
30//! mmio_allocator: AddressAllocator,
31//! slots: HashMap<u32, RangeInclusive>,
32//! }
33//!
34//! #[derive(Clone, Copy)]
35//! struct DeviceSlot {
36//! id: u32,
37//! mmio_range: RangeInclusive,
38//! }
39//!
40//! impl DeviceManager {
41//! pub fn new() -> Result<Self> {
42//! Ok(DeviceManager {
43//! id_allocator: IdAllocator::new(0, 100)?,
44//! mmio_allocator: AddressAllocator::new(MMIO_MEM_START, MEM_32BIT_GAP_SIZE)?,
45//! slots: HashMap::new(),
46//! })
47//! }
48//!
49//! pub fn reserve_device_slot(&mut self) -> Result<DeviceSlot> {
50//! // We're reserving the first available address that is aligned to the page size.
51//! // For each device we reserve one page of addresses.
52//! let mmio_range =
53//! self.mmio_allocator
54//! .allocate(PAGE_SIZE, PAGE_SIZE, AllocPolicy::FirstMatch)?;
55//! let slot = DeviceSlot {
56//! id: self.id_allocator.allocate_id()?,
57//! mmio_range,
58//! };
59//! self.slots.insert(slot.id, slot.mmio_range);
60//! Ok(slot)
61//! }
62//!
63//! // Free the device slot corresponding to the passed device ID.
64//! pub fn free_device_slot(&mut self, id: u32) -> Result<()> {
65//! let mmio_range = self.slots.get(&id).ok_or(Error::NeverAllocated(id))?;
66//! let _ = self.id_allocator.free_id(id)?;
67//! self.mmio_allocator.free(mmio_range)
68//! }
69//! }
70//!
71//! # fn main() {
72//! # let mut dm = DeviceManager::new().unwrap();
73//! # let slot = dm.reserve_device_slot().unwrap();
74//! # dm.free_device_slot(slot.id).unwrap();
75//! # }
76//! ```
77
78#![deny(missing_docs)]
79
80extern crate alloc;
81extern crate core as std;
82
83mod address_allocator;
84/// Allocation engine used by address allocator.
85mod allocation_engine;
86mod id_allocator;
87
88use std::{
89 cmp::{max, min},
90 result,
91};
92
93pub use address_allocator::AddressAllocator;
94use allocation_engine::NodeState;
95pub use id_allocator::IdAllocator;
96use thiserror::Error;
97
98/// Default alignment that can be used for creating a `Constraint`.
99#[allow(dead_code)]
100pub const DEFAULT_CONSTRAINT_ALIGN: u64 = 4;
101
102/// Error type for IdAllocator usage.
103#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Error)]
104pub enum Error {
105 /// Management operations on desired resource resulted in overflow.
106 #[error("Management operations on desired resource resulted in overflow.")]
107 Overflow,
108 /// An id that is not part of the specified range was requested to be released.
109 #[error("Specified id: {0} is not in the range.")]
110 OutOfRange(u32),
111 /// An id that was already released was requested to be released.
112 #[error("Specified id: {0} is already released.")]
113 AlreadyReleased(u32),
114 /// An id that was never allocated was requested to be released.
115 #[error("Specified id: {0} was never allocated, can't release it.")]
116 NeverAllocated(u32),
117 /// The resource we want to use or update is not available.
118 #[error("The requested resource is not available.")]
119 ResourceNotAvailable,
120 /// The range to manage is invalid.
121 #[error("The range specified: {0}-{1} is not valid.")]
122 InvalidRange(u64, u64),
123 /// Alignment value is invalid
124 #[error("Alignment value is invalid.")]
125 InvalidAlignment,
126 /// The range that we try to insert into the interval tree is overlapping
127 /// with another node from the tree.
128 #[error("Addresses are overlapping.{0:?} intersects with existing {1:?}")]
129 Overlap(RangeInclusive, RangeInclusive),
130 /// A node state can be changed just from Free to Allocated, other transitions
131 /// are not valid.
132 #[error("Invalid state transition for node {0:?} from {1:?} to NodeState::Free")]
133 InvalidStateTransition(RangeInclusive, NodeState),
134 /// Address is unaligned
135 #[error("The address is not aligned.")]
136 UnalignedAddress,
137 /// Management operations on desired resource resulted in underflow.
138 #[error("Management operations on desired resource resulted in underflow.")]
139 Underflow,
140 /// The size of the desired resource is not invalid.
141 #[error("The specified size: {0} is not valid.")]
142 InvalidSize(u64),
143}
144
145/// Wrapper over std::result::Result
146pub type Result<T> = result::Result<T, Error>;
147
148/// A closed interval range [start, end].
149/// The range describes a memory slot which is assigned by the VMM to a device.
150///
151/// # Example
152///
153/// ```rust
154/// use rdif_pcie::addr_alloc::RangeInclusive;
155///
156/// let r = RangeInclusive::new(0x0, 0x100).unwrap();
157/// assert_eq!(r.len(), 0x101);
158/// assert_eq!(r.start(), 0x0);
159/// assert_eq!(r.end(), 0x100);
160///
161/// // Check if a region contains another region.
162/// let other = RangeInclusive::new(0x50, 0x80).unwrap();
163/// assert!(r.contains(&other));
164///
165/// // Check if a region overlaps with another one.
166/// let other = RangeInclusive::new(0x99, 0x150).unwrap();
167/// assert!(r.overlaps(&other));
168/// ```
169// This structure represents the key of the Node object in the interval tree implementation.
170#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Hash, Ord, Debug)]
171#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
172pub struct RangeInclusive {
173 /// Lower boundary of the interval.
174 start: u64,
175 /// Upper boundary of the interval.
176 end: u64,
177}
178
179#[allow(clippy::len_without_is_empty)]
180impl RangeInclusive {
181 /// Creates a new RangeInclusive.
182 pub fn new(start: u64, end: u64) -> Result<Self> {
183 // The length of the interval [0, u64::MAX] is u64::MAX + 1 which does
184 // not fit in a u64::MAX, hence we return `Error::InvalidRange` when
185 // there is an attempt to use that range.
186 if start > end || (start == 0 && end == u64::MAX) {
187 return Err(Error::InvalidRange(start, end));
188 }
189 Ok(RangeInclusive { start, end })
190 }
191
192 /// Returns the length of the range.
193 pub fn len(&self) -> u64 {
194 self.end - self.start + 1
195 }
196
197 /// Returns true if the regions overlap.
198 pub fn overlaps(&self, other: &RangeInclusive) -> bool {
199 max(self.start, other.start) <= min(self.end, other.end)
200 }
201
202 /// Returns true if the current range contains the range passed as a parameter.
203 pub fn contains(&self, other: &RangeInclusive) -> bool {
204 self.start <= other.start && self.end >= other.end
205 }
206
207 /// Returns the lower boundary of the range.
208 pub fn start(&self) -> u64 {
209 self.start
210 }
211
212 /// Returns the upper boundary of the range.
213 pub fn end(&self) -> u64 {
214 self.end
215 }
216}
217
218/// A resource allocation constraint.
219///
220/// # Example
221///
222/// ```rust
223/// use rdif_pcie::addr_alloc::{AllocPolicy, Constraint, DEFAULT_CONSTRAINT_ALIGN, Error};
224///
225/// let constraint =
226/// Constraint::new(0x4, DEFAULT_CONSTRAINT_ALIGN, AllocPolicy::FirstMatch).unwrap();
227/// assert_eq!(constraint.size(), 0x4);
228/// assert_eq!(constraint.align(), 0x4);
229///
230/// // Alignments need to be a power of 2, otherwise an error is returned.
231/// assert_eq!(
232/// Constraint::new(0x4, 0x3, AllocPolicy::LastMatch).unwrap_err(),
233/// Error::InvalidAlignment
234/// );
235///
236/// // When using the ExactMatch policy, the start address must also be aligned, otherwise
237/// // an error is returned.
238/// assert_eq!(
239/// Constraint::new(0x4, 0x4, AllocPolicy::ExactMatch(0x3)).unwrap_err(),
240/// Error::UnalignedAddress
241/// );
242/// ```
243#[derive(Copy, Clone, Debug, Eq, PartialEq)]
244pub struct Constraint {
245 /// Size to allocate.
246 size: u64,
247 /// Alignment for the allocated resource.
248 align: u64,
249 /// Resource allocation policy.
250 policy: AllocPolicy,
251}
252
253impl Constraint {
254 /// Creates a new constraint based on the passed configuration.
255 ///
256 /// When the `ExactMatch` policy is used, the start address MUST be aligned to the
257 /// alignment passed as a parameter.
258 ///
259 /// # Arguments:
260 /// - `size`: size to allocate.
261 /// - `align`: alignment to be used for the allocated resources.
262 /// Valid alignments are a power of 2.
263 /// - `policy`: allocation policy.
264 pub fn new(size: u64, align: u64, policy: AllocPolicy) -> Result<Self> {
265 if size == 0 {
266 return Err(Error::InvalidSize(size));
267 }
268
269 if !align.is_power_of_two() || align == 0 {
270 return Err(Error::InvalidAlignment);
271 }
272
273 if let AllocPolicy::ExactMatch(start_address) = policy
274 && start_address % align != 0
275 {
276 return Err(Error::UnalignedAddress);
277 }
278
279 Ok(Constraint {
280 size,
281 align,
282 policy,
283 })
284 }
285
286 /// Returns the alignment constraint.
287 #[allow(dead_code)]
288 pub fn align(self) -> u64 {
289 self.align
290 }
291
292 /// Returns the size constraint.
293 pub fn size(self) -> u64 {
294 self.size
295 }
296}
297
298/// Policy for resource allocation.
299#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
300#[allow(dead_code, clippy::enum_variant_names)]
301pub enum AllocPolicy {
302 /// Allocate the first matched entry.
303 #[default]
304 FirstMatch,
305 /// Allocate first matched entry from the end of the range.
306 LastMatch,
307 /// Allocate a memory slot starting with the specified address
308 /// if it is available.
309 ExactMatch(u64),
310}
311
312#[cfg(test)]
313mod tests {
314 use super::*;
315
316 #[test]
317 fn test_new_range() {
318 assert_eq!(
319 RangeInclusive::new(2, 1).unwrap_err(),
320 Error::InvalidRange(2, 1)
321 );
322 assert_eq!(
323 RangeInclusive::new(0, u64::MAX).unwrap_err(),
324 Error::InvalidRange(0, u64::MAX)
325 );
326 }
327
328 #[test]
329 fn test_range_overlaps() {
330 let range_a = RangeInclusive::new(1u64, 4u64).unwrap();
331 let range_b = RangeInclusive::new(4u64, 6u64).unwrap();
332 let range_c = RangeInclusive::new(2u64, 3u64).unwrap();
333 let range_e = RangeInclusive::new(5u64, 6u64).unwrap();
334
335 assert!(range_a.overlaps(&range_b));
336 assert!(range_b.overlaps(&range_a));
337 assert!(range_a.overlaps(&range_c));
338 assert!(range_c.overlaps(&range_a));
339 assert!(!range_a.overlaps(&range_e));
340 assert!(!range_e.overlaps(&range_a));
341
342 assert_eq!(range_a.len(), 4);
343 }
344
345 #[test]
346 fn test_range_contain() {
347 let range_a = RangeInclusive::new(2u64, 6u64).unwrap();
348 assert!(range_a.contains(&RangeInclusive::new(2u64, 3u64).unwrap()));
349 assert!(range_a.contains(&RangeInclusive::new(3u64, 4u64).unwrap()));
350 assert!(range_a.contains(&RangeInclusive::new(5u64, 6u64).unwrap()));
351 assert!(!range_a.contains(&RangeInclusive::new(1u64, 2u64).unwrap()));
352 assert!(!range_a.contains(&RangeInclusive::new(1u64, 3u64).unwrap()));
353 assert!(!range_a.contains(&RangeInclusive::new(1u64, 7u64).unwrap()));
354 assert!(!range_a.contains(&RangeInclusive::new(7u64, 8u64).unwrap()));
355 assert!(!range_a.contains(&RangeInclusive::new(6u64, 7u64).unwrap()));
356 assert!(!range_a.contains(&RangeInclusive::new(7u64, 8u64).unwrap()));
357 }
358}