Skip to main content

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}