Skip to main content

openhcl_boot/
memory.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Address space allocator for VTL2 memory used by the bootshim.
5
6use crate::host_params::MAX_NUMA_NODES;
7use crate::host_params::MAX_VTL2_RAM_RANGES;
8use crate::single_threaded::off_stack;
9use arrayvec::ArrayVec;
10use host_fdt_parser::MemoryEntry;
11#[cfg(test)]
12use igvm_defs::MemoryMapEntryType;
13use loader_defs::shim::MemoryVtlType;
14use memory_range::MemoryRange;
15use memory_range::RangeWalkResult;
16use memory_range::walk_ranges;
17use thiserror::Error;
18
19const PAGE_SIZE_4K: u64 = 4096;
20
21/// The maximum number of reserved memory ranges that we might use.
22/// See [`ReservedMemoryType`] definition for details.
23pub const MAX_RESERVED_MEM_RANGES: usize = 6 + sidecar_defs::MAX_NODES;
24
25const MAX_MEMORY_RANGES: usize = MAX_VTL2_RAM_RANGES + MAX_RESERVED_MEM_RANGES;
26
27/// Maximum number of ranges in the address space manager.
28/// For simplicity, make it twice the memory and reserved ranges.
29const MAX_ADDRESS_RANGES: usize = MAX_MEMORY_RANGES * 2;
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq)]
32pub enum ReservedMemoryType {
33    /// VTL2 parameter regions (could be up to 2).
34    Vtl2Config,
35    /// Reserved memory that should not be used by the kernel or usermode. There
36    /// should only be one.
37    Vtl2Reserved,
38    /// Sidecar image. There should only be one.
39    SidecarImage,
40    /// A reserved range per sidecar node.
41    SidecarNode,
42    /// Persistent VTL2 memory used for page allocations in usermode. This
43    /// memory is persisted, both location and contents, across servicing.
44    Vtl2GpaPool,
45    /// Page tables that are used for AP startup, on TDX.
46    TdxPageTables,
47    /// In-memory bootshim log buffer.
48    BootshimLogBuffer,
49    /// Persisted state header.
50    PersistedStateHeader,
51    /// Persisted state payload.
52    PersistedStatePayload,
53}
54
55impl From<ReservedMemoryType> for MemoryVtlType {
56    fn from(r: ReservedMemoryType) -> Self {
57        match r {
58            ReservedMemoryType::Vtl2Config => MemoryVtlType::VTL2_CONFIG,
59            ReservedMemoryType::SidecarImage => MemoryVtlType::VTL2_SIDECAR_IMAGE,
60            ReservedMemoryType::SidecarNode => MemoryVtlType::VTL2_SIDECAR_NODE,
61            ReservedMemoryType::Vtl2Reserved => MemoryVtlType::VTL2_RESERVED,
62            ReservedMemoryType::Vtl2GpaPool => MemoryVtlType::VTL2_GPA_POOL,
63            ReservedMemoryType::TdxPageTables => MemoryVtlType::VTL2_TDX_PAGE_TABLES,
64            ReservedMemoryType::BootshimLogBuffer => MemoryVtlType::VTL2_BOOTSHIM_LOG_BUFFER,
65            ReservedMemoryType::PersistedStateHeader => MemoryVtlType::VTL2_PERSISTED_STATE_HEADER,
66            ReservedMemoryType::PersistedStatePayload => {
67                MemoryVtlType::VTL2_PERSISTED_STATE_PROTOBUF
68            }
69        }
70    }
71}
72
73#[derive(Clone, Copy, Debug, PartialEq, Eq)]
74enum AddressUsage {
75    /// Free for allocation
76    Free,
77    /// Used by the bootshim (usually build time), but free for kernel use
78    Used,
79    /// Reserved and should not be reported to the kernel as usable RAM.
80    Reserved(ReservedMemoryType),
81}
82
83#[derive(Debug)]
84struct AddressRange {
85    range: MemoryRange,
86    vnode: u32,
87    usage: AddressUsage,
88}
89
90impl From<AddressUsage> for MemoryVtlType {
91    fn from(usage: AddressUsage) -> Self {
92        match usage {
93            AddressUsage::Free => MemoryVtlType::VTL2_RAM,
94            AddressUsage::Used => MemoryVtlType::VTL2_RAM,
95            AddressUsage::Reserved(r) => r.into(),
96        }
97    }
98}
99
100#[derive(Debug, Clone, Copy)]
101pub struct AllocatedRange {
102    pub range: MemoryRange,
103    pub vnode: u32,
104}
105
106#[derive(Debug, Error)]
107pub enum Error {
108    #[error("ram len {len} greater than maximum {max}")]
109    RamLen { len: u64, max: u64 },
110    #[error("already initialized")]
111    AlreadyInitialized,
112    #[error(
113        "reserved range {reserved:#x?}, type {typ:?} outside of bootshim used {bootshim_used:#x?}"
114    )]
115    ReservedRangeOutsideBootshimUsed {
116        reserved: MemoryRange,
117        typ: ReservedMemoryType,
118        bootshim_used: MemoryRange,
119    },
120}
121
122#[derive(Debug)]
123pub struct AddressSpaceManager {
124    /// Track the whole address space - this must be sorted.
125    address_space: ArrayVec<AddressRange, MAX_ADDRESS_RANGES>,
126
127    /// Track that the VTL2 GPA pool has at least one allocation.
128    vtl2_pool: bool,
129}
130
131/// A builder used to initialize an [`AddressSpaceManager`].
132pub struct AddressSpaceManagerBuilder<'a, I: Iterator<Item = MemoryRange>> {
133    manager: &'a mut AddressSpaceManager,
134    vtl2_ram: &'a [MemoryEntry],
135    bootshim_used: MemoryRange,
136    persisted_state_region: MemoryRange,
137    vtl2_config: I,
138    reserved_range: Option<MemoryRange>,
139    sidecar_image: Option<MemoryRange>,
140    page_tables: Option<MemoryRange>,
141    log_buffer: Option<MemoryRange>,
142    pool_ranges: ArrayVec<MemoryRange, MAX_NUMA_NODES>,
143}
144
145impl<'a, I: Iterator<Item = MemoryRange>> AddressSpaceManagerBuilder<'a, I> {
146    /// Create a new builder to initialize an [`AddressSpaceManager`].
147    ///
148    /// `vtl2_ram` is the list of ram ranges for VTL2, which must be sorted.
149    ///
150    /// `bootshim_used` is the range used by the bootshim, but may be reclaimed
151    /// as ram by the kernel.
152    ///
153    /// `persisted_state_region` is the range used to store the persisted state
154    /// header described by [`loader_defs::shim::PersistedStateHeader`] and
155    /// corresponding protbuf payload.
156    ///
157    /// Other ranges described by other methods must lie within `bootshim_used`.
158    pub fn new(
159        manager: &'a mut AddressSpaceManager,
160        vtl2_ram: &'a [MemoryEntry],
161        bootshim_used: MemoryRange,
162        persisted_state_region: MemoryRange,
163        vtl2_config: I,
164    ) -> AddressSpaceManagerBuilder<'a, I> {
165        AddressSpaceManagerBuilder {
166            manager,
167            vtl2_ram,
168            bootshim_used,
169            persisted_state_region,
170            vtl2_config,
171            reserved_range: None,
172            sidecar_image: None,
173            page_tables: None,
174            log_buffer: None,
175            pool_ranges: ArrayVec::new(),
176        }
177    }
178
179    /// A reserved range reported as type [`MemoryVtlType::VTL2_RESERVED`].
180    pub fn with_reserved_range(mut self, reserved_range: MemoryRange) -> Self {
181        self.reserved_range = Some(reserved_range);
182        self
183    }
184
185    /// The sidecar image, reported as type [`MemoryVtlType::VTL2_SIDECAR_IMAGE`].
186    pub fn with_sidecar_image(mut self, sidecar_image: MemoryRange) -> Self {
187        self.sidecar_image = Some(sidecar_image);
188        self
189    }
190
191    /// Log buffer that is reported as type [`MemoryVtlType::VTL2_BOOTSHIM_LOG_BUFFER`].
192    pub fn with_log_buffer(mut self, log_buffer: MemoryRange) -> Self {
193        self.log_buffer = Some(log_buffer);
194        self
195    }
196
197    /// Existing VTL2 GPA pool ranges, reported as type [`MemoryVtlType::VTL2_GPA_POOL`].
198    ///
199    /// # Panics
200    ///
201    /// Panics if called more than once.
202    pub fn with_pool_ranges(mut self, pool_ranges: impl Iterator<Item = MemoryRange>) -> Self {
203        assert!(
204            self.pool_ranges.is_empty(),
205            "pool ranges already set on builder"
206        );
207        self.pool_ranges.extend(pool_ranges);
208        self
209    }
210
211    /// Consume the builder and initialize the address space manager.
212    pub fn init(self) -> Result<&'a mut AddressSpaceManager, Error> {
213        let Self {
214            manager,
215            vtl2_ram,
216            bootshim_used,
217            persisted_state_region,
218            vtl2_config,
219            reserved_range,
220            sidecar_image,
221            page_tables,
222            log_buffer,
223            pool_ranges,
224        } = self;
225
226        if vtl2_ram.len() > MAX_VTL2_RAM_RANGES {
227            return Err(Error::RamLen {
228                len: vtl2_ram.len() as u64,
229                max: MAX_VTL2_RAM_RANGES as u64,
230            });
231        }
232
233        if !manager.address_space.is_empty() {
234            return Err(Error::AlreadyInitialized);
235        }
236
237        // Split the persisted state region into two: the header which is the
238        // first 4K page, and the remainder which is the protobuf payload. Both
239        // are reserved ranges.
240        let (persisted_header, persisted_payload) =
241            persisted_state_region.split_at_offset(PAGE_SIZE_4K);
242
243        // The other ranges are reserved, and must overlap with the used range.
244        const MAX_RESERVED_RANGES: usize = 20;
245        let mut reserved = off_stack!(ArrayVec<(MemoryRange, ReservedMemoryType), MAX_RESERVED_RANGES>, ArrayVec::new_const());
246        reserved.clear();
247        reserved.push((persisted_header, ReservedMemoryType::PersistedStateHeader));
248        reserved.push((persisted_payload, ReservedMemoryType::PersistedStatePayload));
249        reserved.extend(vtl2_config.map(|r| (r, ReservedMemoryType::Vtl2Config)));
250        reserved.extend(
251            reserved_range
252                .into_iter()
253                .map(|r| (r, ReservedMemoryType::Vtl2Reserved)),
254        );
255        reserved.extend(
256            sidecar_image
257                .into_iter()
258                .map(|r| (r, ReservedMemoryType::SidecarImage)),
259        );
260        reserved.extend(
261            page_tables
262                .into_iter()
263                .map(|r| (r, ReservedMemoryType::TdxPageTables)),
264        );
265        reserved.extend(
266            log_buffer
267                .into_iter()
268                .map(|r| (r, ReservedMemoryType::BootshimLogBuffer)),
269        );
270        reserved.sort_unstable_by_key(|(r, _)| r.start());
271
272        // Maximum number of used ranges is reserved ranges, plus any allocated
273        // pool ranges (one per node maximally).
274        const MAX_USED_RANGES: usize = MAX_RESERVED_RANGES + MAX_NUMA_NODES;
275        let mut used_ranges = off_stack!(ArrayVec<(MemoryRange, AddressUsage), MAX_USED_RANGES>, ArrayVec::new_const());
276        used_ranges.clear();
277
278        // Construct initial used ranges by walking both the bootshim_used range
279        // and all reserved ranges that overlap.
280        for (entry, r) in walk_ranges(
281            core::iter::once((bootshim_used, AddressUsage::Used)),
282            reserved.iter().cloned(),
283        ) {
284            match r {
285                RangeWalkResult::Left(_) => {
286                    used_ranges.push((entry, AddressUsage::Used));
287                }
288                RangeWalkResult::Both(_, reserved_type) => {
289                    used_ranges.push((entry, AddressUsage::Reserved(reserved_type)));
290                }
291                RangeWalkResult::Right(typ) => {
292                    return Err(Error::ReservedRangeOutsideBootshimUsed {
293                        reserved: entry,
294                        typ,
295                        bootshim_used,
296                    });
297                }
298                RangeWalkResult::Neither => {}
299            }
300        }
301
302        // Add any existing pool ranges as reserved.
303        for range in pool_ranges {
304            used_ranges.push((
305                range,
306                AddressUsage::Reserved(ReservedMemoryType::Vtl2GpaPool),
307            ));
308            manager.vtl2_pool = true;
309        }
310        used_ranges.sort_unstable_by_key(|(r, _)| r.start());
311
312        // Construct the initial state of VTL2 address space by walking ram and reserved ranges
313        assert!(manager.address_space.is_empty());
314        for (entry, r) in walk_ranges(
315            vtl2_ram.iter().map(|e| (e.range, e.vnode)),
316            used_ranges.iter().map(|(r, usage)| (*r, usage)),
317        ) {
318            match r {
319                RangeWalkResult::Left(vnode) => {
320                    // VTL2 normal ram, unused by anything.
321                    manager.address_space.push(AddressRange {
322                        range: entry,
323                        vnode,
324                        usage: AddressUsage::Free,
325                    });
326                }
327                RangeWalkResult::Both(vnode, usage) => {
328                    // VTL2 ram, currently in use.
329                    manager.address_space.push(AddressRange {
330                        range: entry,
331                        vnode,
332                        usage: *usage,
333                    });
334                }
335                RangeWalkResult::Right(usage) => {
336                    panic!("vtl2 range {entry:#x?} used by {usage:?} not contained in vtl2 ram");
337                }
338                RangeWalkResult::Neither => {}
339            }
340        }
341
342        Ok(manager)
343    }
344}
345
346impl AddressSpaceManager {
347    pub const fn new_const() -> Self {
348        Self {
349            address_space: ArrayVec::new_const(),
350            vtl2_pool: false,
351        }
352    }
353
354    /// Split a free range into two, with allocation policy deciding if we
355    /// allocate the low part or high part.
356    ///
357    /// Requires that the caller provides a memory range that has room to
358    /// be chopped up into an aligned range of length len.
359    fn allocate_range(
360        &mut self,
361        index: usize,
362        len: u64,
363        usage: AddressUsage,
364        allocation_policy: AllocationPolicy,
365        alignment: Option<u64>,
366    ) -> AllocatedRange {
367        assert!(usage != AddressUsage::Free);
368        let range = self.address_space.get_mut(index).expect("valid index");
369        assert_eq!(range.usage, AddressUsage::Free);
370
371        let subrange = if let Some(alignment) = alignment {
372            range.range.aligned_subrange(alignment)
373        } else {
374            range.range
375        };
376
377        assert!(subrange.len() >= len);
378        assert_ne!(subrange, MemoryRange::EMPTY);
379
380        let used = match allocation_policy {
381            AllocationPolicy::LowMemory => {
382                // Allocate from the beginning (low addresses)
383                let (used, _) = subrange.split_at_offset(len);
384                used
385            }
386            AllocationPolicy::HighMemory => {
387                // Allocate from the end (high addresses)
388                let offset = subrange.len() - len;
389                let (_, used) = subrange.split_at_offset(offset);
390                used
391            }
392        };
393
394        let left = MemoryRange::new(range.range.start()..used.start());
395        let right = MemoryRange::new(used.end()..range.range.end());
396
397        let to_address_range = |r: MemoryRange| -> Option<AddressRange> {
398            if !r.is_empty() {
399                Some(AddressRange {
400                    range: r,
401                    vnode: range.vnode,
402                    usage: AddressUsage::Free,
403                })
404            } else {
405                None
406            }
407        };
408
409        let left = to_address_range(left);
410        let right = to_address_range(right);
411
412        // Update this range to mark it as used
413        range.usage = usage;
414        range.range = used;
415        let allocated = AllocatedRange {
416            range: used,
417            vnode: range.vnode,
418        };
419
420        if let Some(right) = right {
421            self.address_space.insert(index + 1, right);
422        }
423
424        if let Some(left) = left {
425            self.address_space.insert(index, left);
426        }
427
428        allocated
429    }
430
431    fn allocate_inner(
432        &mut self,
433        required_vnode: Option<u32>,
434        len: u64,
435        allocation_type: AllocationType,
436        allocation_policy: AllocationPolicy,
437        alignment: Option<u64>,
438    ) -> Option<AllocatedRange> {
439        if len == 0 {
440            return None;
441        }
442
443        // Round up to the next 4k page size, if the caller did not specify a
444        // multiple of 4k.
445        let len = len.div_ceil(PAGE_SIZE_4K) * PAGE_SIZE_4K;
446
447        fn find_index<'a>(
448            mut iter: impl Iterator<Item = (usize, &'a AddressRange)>,
449            preferred_vnode: Option<u32>,
450            len: u64,
451            alignment: Option<u64>,
452        ) -> Option<usize> {
453            iter.find_map(|(index, range)| {
454                let is_aligned: bool = alignment.is_none()
455                    || (alignment.is_some()
456                        && range.range.aligned_subrange(alignment.unwrap()).len() >= len);
457                if range.usage == AddressUsage::Free
458                    && range.range.len() >= len
459                    && preferred_vnode.map(|pv| pv == range.vnode).unwrap_or(true)
460                    && is_aligned
461                {
462                    Some(index)
463                } else {
464                    None
465                }
466            })
467        }
468
469        // Walk ranges in forward/reverse order, depending on allocation policy.
470        let index = {
471            let iter = self.address_space.iter().enumerate();
472            match allocation_policy {
473                AllocationPolicy::LowMemory => find_index(iter, required_vnode, len, alignment),
474                AllocationPolicy::HighMemory => {
475                    find_index(iter.rev(), required_vnode, len, alignment)
476                }
477            }
478        };
479
480        let address_usage = match allocation_type {
481            AllocationType::GpaPool => AddressUsage::Reserved(ReservedMemoryType::Vtl2GpaPool),
482            AllocationType::SidecarNode => AddressUsage::Reserved(ReservedMemoryType::SidecarNode),
483            AllocationType::TdxPageTables => {
484                AddressUsage::Reserved(ReservedMemoryType::TdxPageTables)
485            }
486        };
487
488        let alloc = index.map(|index| {
489            self.allocate_range(index, len, address_usage, allocation_policy, alignment)
490        });
491
492        if allocation_type == AllocationType::GpaPool && alloc.is_some() {
493            self.vtl2_pool = true;
494        }
495
496        alloc
497    }
498
499    /// Allocate a new range of memory with the given type and policy. None is
500    /// returned if the allocation was unable to be satisfied.
501    ///
502    /// `len` is the number of bytes to allocate. The number of bytes are
503    /// rounded up to the next 4K page size increment. if `len` is 0, then
504    /// `None` is returned.
505    ///
506    /// `required_vnode` if `Some(u32)` is the vnode to allocate from. If there
507    /// are no free ranges left in that vnode, None is returned.
508    pub fn allocate(
509        &mut self,
510        required_vnode: Option<u32>,
511        len: u64,
512        allocation_type: AllocationType,
513        allocation_policy: AllocationPolicy,
514    ) -> Option<AllocatedRange> {
515        self.allocate_inner(
516            required_vnode,
517            len,
518            allocation_type,
519            allocation_policy,
520            None,
521        )
522    }
523
524    /// Allocate a new range of memory with the given type and policy. None is
525    /// returned if the allocation was unable to be satisfied.
526    ///
527    /// `len` is the number of bytes to allocate. The number of bytes are
528    /// rounded up to the next 4K page size increment. if `len` is 0, then
529    /// `None` is returned.
530    ///
531    /// `required_vnode` if `Some(u32)` is the vnode to allocate from. If there
532    /// are no free ranges left in that vnode, None is returned.
533    ///
534    /// `alignment` aligns the top of HighMemory allocations to `alignment`
535    /// bytes, and aligns the bottom of LowMemory allocations
536    #[cfg_attr(all(target_arch = "aarch64", not(test)), expect(dead_code))]
537    pub fn allocate_aligned(
538        &mut self,
539        required_vnode: Option<u32>,
540        len: u64,
541        allocation_type: AllocationType,
542        allocation_policy: AllocationPolicy,
543        alignment: u64,
544    ) -> Option<AllocatedRange> {
545        self.allocate_inner(
546            required_vnode,
547            len,
548            allocation_type,
549            allocation_policy,
550            Some(alignment),
551        )
552    }
553
554    /// Returns an iterator for all VTL2 ranges.
555    pub fn vtl2_ranges(&self) -> impl Iterator<Item = (MemoryRange, MemoryVtlType)> + use<'_> {
556        memory_range::merge_adjacent_ranges(
557            self.address_space.iter().map(|r| (r.range, r.usage.into())),
558        )
559    }
560
561    /// Returns an iterator for reserved VTL2 ranges that should not be
562    /// described as ram to the kernel.
563    pub fn reserved_vtl2_ranges(
564        &self,
565    ) -> impl Iterator<Item = (MemoryRange, ReservedMemoryType)> + use<'_> {
566        self.address_space.iter().filter_map(|r| match r.usage {
567            AddressUsage::Reserved(typ) => Some((r.range, typ)),
568            _ => None,
569        })
570    }
571
572    /// Returns an iterator for all the free ranges for the given numa node.
573    /// This is used for diagnostics and debugging purposes.
574    pub fn free_ranges(&self, vnode: u32) -> impl Iterator<Item = MemoryRange> + use<'_> {
575        self.address_space.iter().filter_map(move |r| {
576            if r.usage == AddressUsage::Free && r.vnode == vnode {
577                Some(r.range)
578            } else {
579                None
580            }
581        })
582    }
583
584    /// Returns true if there are VTL2 pool allocations.
585    pub fn has_vtl2_pool(&self) -> bool {
586        self.vtl2_pool
587    }
588}
589
590#[derive(Debug, Clone, Copy, PartialEq, Eq)]
591pub enum AllocationType {
592    GpaPool,
593    SidecarNode,
594    #[cfg_attr(target_arch = "aarch64", expect(dead_code))]
595    TdxPageTables,
596}
597
598pub enum AllocationPolicy {
599    // prefer low memory
600    LowMemory,
601    // prefer high memory
602    HighMemory,
603}
604
605#[cfg(test)]
606mod tests {
607    use super::*;
608
609    #[test]
610    fn test_allocate() {
611        let mut address_space = AddressSpaceManager::new_const();
612        let vtl2_ram = &[MemoryEntry {
613            range: MemoryRange::new(0x0..0x20000),
614            vnode: 0,
615            mem_type: MemoryMapEntryType::MEMORY,
616        }];
617
618        AddressSpaceManagerBuilder::new(
619            &mut address_space,
620            vtl2_ram,
621            MemoryRange::new(0x0..0xF000),
622            MemoryRange::new(0x0..0x2000),
623            [
624                MemoryRange::new(0x3000..0x4000),
625                MemoryRange::new(0x5000..0x6000),
626            ]
627            .iter()
628            .cloned(),
629        )
630        .with_reserved_range(MemoryRange::new(0x8000..0xA000))
631        .with_sidecar_image(MemoryRange::new(0xA000..0xC000))
632        .init()
633        .unwrap();
634
635        let range = address_space
636            .allocate(
637                None,
638                0x1000,
639                AllocationType::GpaPool,
640                AllocationPolicy::HighMemory,
641            )
642            .unwrap();
643        assert_eq!(range.range, MemoryRange::new(0x1F000..0x20000));
644        assert!(address_space.has_vtl2_pool());
645
646        let range = address_space
647            .allocate(
648                None,
649                0x2000,
650                AllocationType::GpaPool,
651                AllocationPolicy::HighMemory,
652            )
653            .unwrap();
654        assert_eq!(range.range, MemoryRange::new(0x1D000..0x1F000));
655
656        let range = address_space
657            .allocate(
658                None,
659                0x3000,
660                AllocationType::GpaPool,
661                AllocationPolicy::LowMemory,
662            )
663            .unwrap();
664        assert_eq!(range.range, MemoryRange::new(0xF000..0x12000));
665
666        let range = address_space
667            .allocate(
668                None,
669                0x1000,
670                AllocationType::GpaPool,
671                AllocationPolicy::LowMemory,
672            )
673            .unwrap();
674        assert_eq!(range.range, MemoryRange::new(0x12000..0x13000));
675
676        let free_ranges: Vec<MemoryRange> = address_space.free_ranges(0).collect();
677        assert_eq!(free_ranges, vec![MemoryRange::new(0x13000..0x1D000)]);
678    }
679
680    #[test]
681    fn test_allocate_aligned() {
682        let mut address_space = AddressSpaceManager::new_const();
683        let vtl2_ram = &[MemoryEntry {
684            range: MemoryRange::new(0x0..0x20000),
685            vnode: 0,
686            mem_type: MemoryMapEntryType::MEMORY,
687        }];
688
689        AddressSpaceManagerBuilder::new(
690            &mut address_space,
691            vtl2_ram,
692            MemoryRange::new(0x0..0xF000),
693            MemoryRange::new(0x0..0x2000),
694            [
695                MemoryRange::new(0x3000..0x4000),
696                MemoryRange::new(0x5000..0x6000),
697            ]
698            .iter()
699            .cloned(),
700        )
701        .with_reserved_range(MemoryRange::new(0x8000..0xA000))
702        .with_sidecar_image(MemoryRange::new(0xA000..0xC000))
703        .init()
704        .unwrap();
705
706        let alignment = 4096 * 16;
707        let range = address_space
708            .allocate_aligned(
709                None,
710                0x1000,
711                AllocationType::GpaPool,
712                AllocationPolicy::LowMemory,
713                alignment,
714            )
715            .unwrap();
716
717        assert_eq!(0, range.range.start() % alignment);
718
719        let alignment = 4096 * 4;
720        let range = address_space
721            .allocate_aligned(
722                None,
723                0x1000,
724                AllocationType::GpaPool,
725                AllocationPolicy::HighMemory,
726                alignment,
727            )
728            .unwrap();
729
730        assert_eq!(0, range.range.end() % alignment);
731    }
732
733    #[test]
734    fn test_failed_alignment() {
735        let mut address_space = AddressSpaceManager::new_const();
736        let vtl2_ram = &[MemoryEntry {
737            range: MemoryRange::new(0x0..0x20000),
738            vnode: 0,
739            mem_type: MemoryMapEntryType::MEMORY,
740        }];
741
742        AddressSpaceManagerBuilder::new(
743            &mut address_space,
744            vtl2_ram,
745            MemoryRange::new(0x0..0xF000),
746            MemoryRange::new(0x0..0x2000),
747            [
748                MemoryRange::new(0x3000..0x4000),
749                MemoryRange::new(0x5000..0x6000),
750            ]
751            .iter()
752            .cloned(),
753        )
754        .with_reserved_range(MemoryRange::new(0x8000..0xA000))
755        .with_sidecar_image(MemoryRange::new(0xA000..0xC000))
756        .init()
757        .unwrap();
758
759        let alignment = 1024 * 1024 * 2;
760        let range = address_space.allocate_aligned(
761            None,
762            0x1000,
763            AllocationType::GpaPool,
764            AllocationPolicy::LowMemory,
765            alignment,
766        );
767        assert!(range.is_none());
768    }
769
770    // test numa allocation
771    #[test]
772    fn test_allocate_numa() {
773        let mut address_space = AddressSpaceManager::new_const();
774        let vtl2_ram = &[
775            MemoryEntry {
776                range: MemoryRange::new(0x0..0x20000),
777                vnode: 0,
778                mem_type: MemoryMapEntryType::MEMORY,
779            },
780            MemoryEntry {
781                range: MemoryRange::new(0x20000..0x40000),
782                vnode: 1,
783                mem_type: MemoryMapEntryType::MEMORY,
784            },
785            MemoryEntry {
786                range: MemoryRange::new(0x40000..0x60000),
787                vnode: 2,
788                mem_type: MemoryMapEntryType::MEMORY,
789            },
790            MemoryEntry {
791                range: MemoryRange::new(0x60000..0x80000),
792                vnode: 3,
793                mem_type: MemoryMapEntryType::MEMORY,
794            },
795        ];
796
797        AddressSpaceManagerBuilder::new(
798            &mut address_space,
799            vtl2_ram,
800            MemoryRange::new(0x0..0x10000),
801            MemoryRange::new(0x0..0x2000),
802            [
803                MemoryRange::new(0x3000..0x4000),
804                MemoryRange::new(0x5000..0x6000),
805            ]
806            .iter()
807            .cloned(),
808        )
809        .with_reserved_range(MemoryRange::new(0x8000..0xA000))
810        .with_sidecar_image(MemoryRange::new(0xA000..0xC000))
811        .init()
812        .unwrap();
813
814        let range = address_space
815            .allocate(
816                Some(0),
817                0x1000,
818                AllocationType::GpaPool,
819                AllocationPolicy::HighMemory,
820            )
821            .unwrap();
822        assert_eq!(range.range, MemoryRange::new(0x1F000..0x20000));
823        assert_eq!(range.vnode, 0);
824
825        let range = address_space
826            .allocate(
827                Some(0),
828                0x2000,
829                AllocationType::SidecarNode,
830                AllocationPolicy::HighMemory,
831            )
832            .unwrap();
833        assert_eq!(range.range, MemoryRange::new(0x1D000..0x1F000));
834        assert_eq!(range.vnode, 0);
835
836        let range = address_space
837            .allocate(
838                Some(2),
839                0x3000,
840                AllocationType::GpaPool,
841                AllocationPolicy::HighMemory,
842            )
843            .unwrap();
844        assert_eq!(range.range, MemoryRange::new(0x5D000..0x60000));
845        assert_eq!(range.vnode, 2);
846
847        // allocate all of node 3, then subsequent allocations fail
848        let range = address_space
849            .allocate(
850                Some(3),
851                0x20000,
852                AllocationType::SidecarNode,
853                AllocationPolicy::HighMemory,
854            )
855            .unwrap();
856        assert_eq!(range.range, MemoryRange::new(0x60000..0x80000));
857        assert_eq!(range.vnode, 3);
858
859        let range = address_space.allocate(
860            Some(3),
861            0x1000,
862            AllocationType::SidecarNode,
863            AllocationPolicy::HighMemory,
864        );
865        assert!(
866            range.is_none(),
867            "allocation should fail, no space left for node 3"
868        );
869    }
870
871    // test unaligned 4k allocations
872    #[test]
873    fn test_unaligned_allocations() {
874        let mut address_space = AddressSpaceManager::new_const();
875        let vtl2_ram = &[MemoryEntry {
876            range: MemoryRange::new(0x0..0x20000),
877            vnode: 0,
878            mem_type: MemoryMapEntryType::MEMORY,
879        }];
880
881        AddressSpaceManagerBuilder::new(
882            &mut address_space,
883            vtl2_ram,
884            MemoryRange::new(0x0..0xF000),
885            MemoryRange::new(0x0..0x2000),
886            [
887                MemoryRange::new(0x3000..0x4000),
888                MemoryRange::new(0x5000..0x6000),
889            ]
890            .iter()
891            .cloned(),
892        )
893        .with_reserved_range(MemoryRange::new(0x8000..0xA000))
894        .with_sidecar_image(MemoryRange::new(0xA000..0xC000))
895        .init()
896        .unwrap();
897
898        let range = address_space
899            .allocate(
900                None,
901                0x1001,
902                AllocationType::GpaPool,
903                AllocationPolicy::HighMemory,
904            )
905            .unwrap();
906        assert_eq!(range.range, MemoryRange::new(0x1E000..0x20000));
907
908        let range = address_space
909            .allocate(
910                None,
911                0xFFF,
912                AllocationType::GpaPool,
913                AllocationPolicy::HighMemory,
914            )
915            .unwrap();
916        assert_eq!(range.range, MemoryRange::new(0x1D000..0x1E000));
917
918        let range = address_space.allocate(
919            None,
920            0,
921            AllocationType::GpaPool,
922            AllocationPolicy::HighMemory,
923        );
924        assert!(range.is_none());
925    }
926
927    // test invalid init ranges
928    #[test]
929    fn test_invalid_init_ranges() {
930        let vtl2_ram = [MemoryEntry {
931            range: MemoryRange::new(0x0..0x20000),
932            vnode: 0,
933            mem_type: MemoryMapEntryType::MEMORY,
934        }];
935        let bootshim_used = MemoryRange::new(0x0..0xF000);
936
937        // test config range completely outside of bootshim_used
938        let mut address_space = AddressSpaceManager::new_const();
939
940        let result = AddressSpaceManagerBuilder::new(
941            &mut address_space,
942            &vtl2_ram,
943            bootshim_used,
944            MemoryRange::new(0x0..0x2000),
945            [MemoryRange::new(0x10000..0x11000)].iter().cloned(), // completely outside
946        )
947        .init();
948
949        assert!(matches!(
950            result,
951            Err(Error::ReservedRangeOutsideBootshimUsed { .. })
952        ));
953
954        // test config range partially overlapping with bootshim_used
955
956        let mut address_space = AddressSpaceManager::new_const();
957        let result = AddressSpaceManagerBuilder::new(
958            &mut address_space,
959            &vtl2_ram,
960            bootshim_used,
961            MemoryRange::new(0x0..0x2000),
962            [MemoryRange::new(0xE000..0x10000)].iter().cloned(), // partially overlapping
963        )
964        .init();
965
966        assert!(matches!(
967            result,
968            Err(Error::ReservedRangeOutsideBootshimUsed { .. })
969        ));
970
971        // test persisted region outside of bootshim_used
972        let mut address_space = AddressSpaceManager::new_const();
973        let result = AddressSpaceManagerBuilder::new(
974            &mut address_space,
975            &vtl2_ram,
976            bootshim_used,
977            MemoryRange::new(0x10000..0x14000), // outside
978            [MemoryRange::new(0xE000..0xF000)].iter().cloned(),
979        )
980        .init();
981
982        assert!(matches!(
983            result,
984            Err(Error::ReservedRangeOutsideBootshimUsed { .. })
985        ));
986    }
987
988    #[test]
989    fn test_persisted_range() {
990        let vtl2_ram = [MemoryEntry {
991            range: MemoryRange::new(0x0..0x20000),
992            vnode: 0,
993            mem_type: MemoryMapEntryType::MEMORY,
994        }];
995        let bootshim_used = MemoryRange::new(0x0..0xF000);
996
997        let mut address_space = AddressSpaceManager::new_const();
998        AddressSpaceManagerBuilder::new(
999            &mut address_space,
1000            &vtl2_ram,
1001            bootshim_used,
1002            MemoryRange::new(0x0..0xE000),
1003            [MemoryRange::new(0xE000..0xF000)].iter().cloned(),
1004        )
1005        .init()
1006        .unwrap();
1007
1008        let expected = [
1009            (
1010                MemoryRange::new(0x0..0x1000),
1011                MemoryVtlType::VTL2_PERSISTED_STATE_HEADER,
1012            ),
1013            (
1014                MemoryRange::new(0x1000..0xE000),
1015                MemoryVtlType::VTL2_PERSISTED_STATE_PROTOBUF,
1016            ),
1017            (MemoryRange::new(0xE000..0xF000), MemoryVtlType::VTL2_CONFIG),
1018            (MemoryRange::new(0xF000..0x20000), MemoryVtlType::VTL2_RAM),
1019        ];
1020
1021        for (expected, actual) in expected.iter().zip(address_space.vtl2_ranges()) {
1022            assert_eq!(*expected, actual);
1023        }
1024
1025        // test with free space between state region and config
1026        let mut address_space = AddressSpaceManager::new_const();
1027        AddressSpaceManagerBuilder::new(
1028            &mut address_space,
1029            &vtl2_ram,
1030            bootshim_used,
1031            MemoryRange::new(0x0..0xA000),
1032            [MemoryRange::new(0xE000..0xF000)].iter().cloned(),
1033        )
1034        .init()
1035        .unwrap();
1036
1037        let expected = [
1038            (
1039                MemoryRange::new(0x0..0x1000),
1040                MemoryVtlType::VTL2_PERSISTED_STATE_HEADER,
1041            ),
1042            (
1043                MemoryRange::new(0x1000..0xA000),
1044                MemoryVtlType::VTL2_PERSISTED_STATE_PROTOBUF,
1045            ),
1046            (MemoryRange::new(0xA000..0xE000), MemoryVtlType::VTL2_RAM),
1047            (MemoryRange::new(0xE000..0xF000), MemoryVtlType::VTL2_CONFIG),
1048            (MemoryRange::new(0xF000..0x20000), MemoryVtlType::VTL2_RAM),
1049        ];
1050
1051        for (expected, actual) in expected.iter().zip(address_space.vtl2_ranges()) {
1052            assert_eq!(*expected, actual);
1053        }
1054    }
1055
1056    // Tests for multi-NUMA pool range support in the builder.
1057
1058    #[test]
1059    fn test_single_pool_range() {
1060        let mut address_space = AddressSpaceManager::new_const();
1061        let vtl2_ram = &[MemoryEntry {
1062            range: MemoryRange::new(0x0..0x20000),
1063            vnode: 0,
1064            mem_type: MemoryMapEntryType::MEMORY,
1065        }];
1066
1067        AddressSpaceManagerBuilder::new(
1068            &mut address_space,
1069            vtl2_ram,
1070            MemoryRange::new(0x0..0x4000),
1071            MemoryRange::new(0x0..0x2000),
1072            core::iter::empty(),
1073        )
1074        .with_pool_ranges([MemoryRange::new(0x10000..0x18000)].into_iter())
1075        .init()
1076        .unwrap();
1077
1078        assert!(address_space.has_vtl2_pool());
1079
1080        // Pool range should be reserved.
1081        let reserved: Vec<_> = address_space.reserved_vtl2_ranges().collect();
1082        assert!(
1083            reserved
1084                .iter()
1085                .any(|(r, t)| *r == MemoryRange::new(0x10000..0x18000)
1086                    && *t == ReservedMemoryType::Vtl2GpaPool)
1087        );
1088    }
1089
1090    #[test]
1091    fn test_multiple_pool_ranges() {
1092        let mut address_space = AddressSpaceManager::new_const();
1093        let vtl2_ram = &[
1094            MemoryEntry {
1095                range: MemoryRange::new(0x0..0x20000),
1096                vnode: 0,
1097                mem_type: MemoryMapEntryType::MEMORY,
1098            },
1099            MemoryEntry {
1100                range: MemoryRange::new(0x100000..0x120000),
1101                vnode: 1,
1102                mem_type: MemoryMapEntryType::MEMORY,
1103            },
1104        ];
1105
1106        AddressSpaceManagerBuilder::new(
1107            &mut address_space,
1108            vtl2_ram,
1109            MemoryRange::new(0x0..0x4000),
1110            MemoryRange::new(0x0..0x2000),
1111            core::iter::empty(),
1112        )
1113        .with_pool_ranges(
1114            [
1115                MemoryRange::new(0x10000..0x18000),
1116                MemoryRange::new(0x110000..0x118000),
1117            ]
1118            .into_iter(),
1119        )
1120        .init()
1121        .unwrap();
1122
1123        assert!(address_space.has_vtl2_pool());
1124
1125        // Both pool ranges should be reserved.
1126        let reserved: Vec<_> = address_space
1127            .reserved_vtl2_ranges()
1128            .filter(|(_, t)| *t == ReservedMemoryType::Vtl2GpaPool)
1129            .collect();
1130        assert_eq!(reserved.len(), 2);
1131        assert_eq!(reserved[0].0, MemoryRange::new(0x10000..0x18000));
1132        assert_eq!(reserved[1].0, MemoryRange::new(0x110000..0x118000));
1133    }
1134
1135    #[test]
1136    fn test_allocate_pool_single_numa_node() {
1137        // With a single NUMA node, pool should be allocated as one range on
1138        // node 0 (regardless of force_split, since there's only one node).
1139        let mut address_space = AddressSpaceManager::new_const();
1140        let vtl2_ram = &[MemoryEntry {
1141            range: MemoryRange::new(0x0..0x100000),
1142            vnode: 0,
1143            mem_type: MemoryMapEntryType::MEMORY,
1144        }];
1145
1146        AddressSpaceManagerBuilder::new(
1147            &mut address_space,
1148            vtl2_ram,
1149            MemoryRange::new(0x0..0x4000),
1150            MemoryRange::new(0x0..0x2000),
1151            core::iter::empty(),
1152        )
1153        .init()
1154        .unwrap();
1155
1156        // Allocate pool on node 0.
1157        let pool = address_space
1158            .allocate(
1159                Some(0),
1160                0x10000,
1161                AllocationType::GpaPool,
1162                AllocationPolicy::HighMemory,
1163            )
1164            .unwrap();
1165        assert_eq!(pool.vnode, 0);
1166        assert_eq!(pool.range.len(), 0x10000);
1167        assert!(address_space.has_vtl2_pool());
1168    }
1169
1170    #[test]
1171    fn test_allocate_pool_two_numa_nodes_node0_fits() {
1172        // With 2 NUMA nodes and enough space on node 0, pool should be
1173        // allocated entirely on node 0.
1174        let mut address_space = AddressSpaceManager::new_const();
1175        let vtl2_ram = &[
1176            MemoryEntry {
1177                range: MemoryRange::new(0x0..0x100000),
1178                vnode: 0,
1179                mem_type: MemoryMapEntryType::MEMORY,
1180            },
1181            MemoryEntry {
1182                range: MemoryRange::new(0x200000..0x300000),
1183                vnode: 1,
1184                mem_type: MemoryMapEntryType::MEMORY,
1185            },
1186        ];
1187
1188        AddressSpaceManagerBuilder::new(
1189            &mut address_space,
1190            vtl2_ram,
1191            MemoryRange::new(0x0..0x4000),
1192            MemoryRange::new(0x0..0x2000),
1193            core::iter::empty(),
1194        )
1195        .init()
1196        .unwrap();
1197
1198        // Node 0 has plenty of free space, so the pool fits entirely on node 0.
1199        let pool = address_space
1200            .allocate(
1201                Some(0),
1202                0x10000,
1203                AllocationType::GpaPool,
1204                AllocationPolicy::HighMemory,
1205            )
1206            .unwrap();
1207        assert_eq!(pool.vnode, 0);
1208        assert_eq!(pool.range.len(), 0x10000);
1209    }
1210
1211    #[test]
1212    fn test_allocate_pool_two_numa_nodes_node0_exhausted() {
1213        // Node 0 has no free space after bootshim. Pool must come from node 1.
1214        let mut address_space = AddressSpaceManager::new_const();
1215        let vtl2_ram = &[
1216            MemoryEntry {
1217                range: MemoryRange::new(0x0..0x4000),
1218                vnode: 0,
1219                mem_type: MemoryMapEntryType::MEMORY,
1220            },
1221            MemoryEntry {
1222                range: MemoryRange::new(0x200000..0x300000),
1223                vnode: 1,
1224                mem_type: MemoryMapEntryType::MEMORY,
1225            },
1226        ];
1227
1228        AddressSpaceManagerBuilder::new(
1229            &mut address_space,
1230            vtl2_ram,
1231            MemoryRange::new(0x0..0x4000),
1232            MemoryRange::new(0x0..0x2000),
1233            core::iter::empty(),
1234        )
1235        .init()
1236        .unwrap();
1237
1238        // Node 0 allocation should fail (all used by bootshim).
1239        assert!(
1240            address_space
1241                .allocate(
1242                    Some(0),
1243                    0x10000,
1244                    AllocationType::GpaPool,
1245                    AllocationPolicy::HighMemory,
1246                )
1247                .is_none()
1248        );
1249
1250        // Node 1 should succeed.
1251        let pool = address_space
1252            .allocate(
1253                Some(1),
1254                0x10000,
1255                AllocationType::GpaPool,
1256                AllocationPolicy::HighMemory,
1257            )
1258            .unwrap();
1259        assert_eq!(pool.vnode, 1);
1260        assert_eq!(pool.range.len(), 0x10000);
1261    }
1262}