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_VTL2_RAM_RANGES;
7use arrayvec::ArrayVec;
8use host_fdt_parser::MemoryEntry;
9#[cfg(test)]
10use igvm_defs::MemoryMapEntryType;
11use loader_defs::shim::MemoryVtlType;
12use memory_range::MemoryRange;
13use memory_range::RangeWalkResult;
14use memory_range::walk_ranges;
15use thiserror::Error;
16
17const PAGE_SIZE_4K: u64 = 4096;
18
19/// The maximum number of reserved memory ranges that we might use.
20/// See [`ReservedMemoryType`] definition for details.
21pub const MAX_RESERVED_MEM_RANGES: usize = 6 + sidecar_defs::MAX_NODES;
22
23const MAX_MEMORY_RANGES: usize = MAX_VTL2_RAM_RANGES + MAX_RESERVED_MEM_RANGES;
24
25/// Maximum number of ranges in the address space manager.
26/// For simplicity, make it twice the memory and reserved ranges.
27const MAX_ADDRESS_RANGES: usize = MAX_MEMORY_RANGES * 2;
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq)]
30pub enum ReservedMemoryType {
31    /// VTL2 parameter regions (could be up to 2).
32    Vtl2Config,
33    /// Reserved memory that should not be used by the kernel or usermode. There
34    /// should only be one.
35    Vtl2Reserved,
36    /// Sidecar image. There should only be one.
37    SidecarImage,
38    /// A reserved range per sidecar node.
39    SidecarNode,
40    /// Persistent VTL2 memory used for page allocations in usermode. This
41    /// memory is persisted, both location and contents, across servicing.
42    /// Today, we only support a single range.
43    Vtl2GpaPool,
44    /// Page tables that are used for AP startup, on TDX.
45    TdxPageTables,
46}
47
48impl From<ReservedMemoryType> for MemoryVtlType {
49    fn from(r: ReservedMemoryType) -> Self {
50        match r {
51            ReservedMemoryType::Vtl2Config => MemoryVtlType::VTL2_CONFIG,
52            ReservedMemoryType::SidecarImage => MemoryVtlType::VTL2_SIDECAR_IMAGE,
53            ReservedMemoryType::SidecarNode => MemoryVtlType::VTL2_SIDECAR_NODE,
54            ReservedMemoryType::Vtl2Reserved => MemoryVtlType::VTL2_RESERVED,
55            ReservedMemoryType::Vtl2GpaPool => MemoryVtlType::VTL2_GPA_POOL,
56            ReservedMemoryType::TdxPageTables => MemoryVtlType::VTL2_TDX_PAGE_TABLES,
57        }
58    }
59}
60
61#[derive(Clone, Copy, Debug, PartialEq, Eq)]
62enum AddressUsage {
63    /// Free for allocation
64    Free,
65    /// Used by the bootshim (usually build time), but free for kernel use
66    Used,
67    /// Reserved and should not be reported to the kernel as usable RAM.
68    Reserved(ReservedMemoryType),
69}
70
71#[derive(Debug)]
72struct AddressRange {
73    range: MemoryRange,
74    vnode: u32,
75    usage: AddressUsage,
76}
77
78impl From<AddressUsage> for MemoryVtlType {
79    fn from(usage: AddressUsage) -> Self {
80        match usage {
81            AddressUsage::Free => MemoryVtlType::VTL2_RAM,
82            AddressUsage::Used => MemoryVtlType::VTL2_RAM,
83            AddressUsage::Reserved(r) => r.into(),
84        }
85    }
86}
87
88#[derive(Debug, Clone, Copy)]
89pub struct AllocatedRange {
90    pub range: MemoryRange,
91    pub vnode: u32,
92}
93
94#[derive(Debug, Error)]
95pub enum Error {
96    #[error("ram len {len} greater than maximum {max}")]
97    RamLen { len: u64, max: u64 },
98    #[error("already initialized")]
99    AlreadyInitialized,
100    #[error(
101        "reserved range {reserved:#x?}, type {typ:?} outside of bootshim used {bootshim_used:#x?}"
102    )]
103    ReservedRangeOutsideBootshimUsed {
104        reserved: MemoryRange,
105        typ: ReservedMemoryType,
106        bootshim_used: MemoryRange,
107    },
108}
109
110#[derive(Debug)]
111pub struct AddressSpaceManager {
112    /// Track the whole address space - this must be sorted.
113    address_space: ArrayVec<AddressRange, MAX_ADDRESS_RANGES>,
114
115    /// Track that the VTL2 GPA pool has at least one allocation.
116    vtl2_pool: bool,
117}
118
119/// A builder used to initialize an [`AddressSpaceManager`].
120pub struct AddressSpaceManagerBuilder<'a, I: Iterator<Item = MemoryRange>> {
121    manager: &'a mut AddressSpaceManager,
122    vtl2_ram: &'a [MemoryEntry],
123    bootshim_used: MemoryRange,
124    vtl2_config: I,
125    reserved_range: Option<MemoryRange>,
126    sidecar_image: Option<MemoryRange>,
127    page_tables: Option<MemoryRange>,
128}
129
130impl<'a, I: Iterator<Item = MemoryRange>> AddressSpaceManagerBuilder<'a, I> {
131    /// Create a new builder to initialize an [`AddressSpaceManager`].
132    ///
133    /// `vtl2_ram` is the list of ram ranges for VTL2, which must be sorted.
134    ///
135    /// `bootshim_used` is the range used by the bootshim, but may be reclaimed
136    /// as ram by the kernel.
137    ///
138    /// Other ranges described by other methods must lie within `bootshim_used`.
139    pub fn new(
140        manager: &'a mut AddressSpaceManager,
141        vtl2_ram: &'a [MemoryEntry],
142        bootshim_used: MemoryRange,
143        vtl2_config: I,
144    ) -> AddressSpaceManagerBuilder<'a, I> {
145        AddressSpaceManagerBuilder {
146            manager,
147            vtl2_ram,
148            bootshim_used,
149            vtl2_config,
150            reserved_range: None,
151            sidecar_image: None,
152            page_tables: None,
153        }
154    }
155
156    /// A reserved range reported as type [`MemoryVtlType::VTL2_RESERVED`].
157    pub fn with_reserved_range(mut self, reserved_range: MemoryRange) -> Self {
158        self.reserved_range = Some(reserved_range);
159        self
160    }
161
162    /// The sidecar image, reported as type [`MemoryVtlType::VTL2_SIDECAR_IMAGE`].
163    pub fn with_sidecar_image(mut self, sidecar_image: MemoryRange) -> Self {
164        self.sidecar_image = Some(sidecar_image);
165        self
166    }
167
168    /// Pagetables that are reported as type [`MemoryVtlType::VTL2_TDX_PAGE_TABLES`].
169    pub fn with_page_tables(mut self, page_tables: MemoryRange) -> Self {
170        self.page_tables = Some(page_tables);
171        self
172    }
173
174    /// Consume the builder and initialize the address space manager.
175    pub fn init(self) -> Result<&'a mut AddressSpaceManager, Error> {
176        let Self {
177            manager,
178            vtl2_ram,
179            bootshim_used,
180            vtl2_config,
181            reserved_range,
182            sidecar_image,
183            page_tables,
184        } = self;
185
186        if vtl2_ram.len() > MAX_VTL2_RAM_RANGES {
187            return Err(Error::RamLen {
188                len: vtl2_ram.len() as u64,
189                max: MAX_VTL2_RAM_RANGES as u64,
190            });
191        }
192
193        if !manager.address_space.is_empty() {
194            return Err(Error::AlreadyInitialized);
195        }
196
197        // The other ranges are reserved, and must overlap with the used range.
198        let mut reserved: ArrayVec<(MemoryRange, ReservedMemoryType), 5> = ArrayVec::new();
199        reserved.extend(vtl2_config.map(|r| (r, ReservedMemoryType::Vtl2Config)));
200        reserved.extend(
201            reserved_range
202                .into_iter()
203                .map(|r| (r, ReservedMemoryType::Vtl2Reserved)),
204        );
205        reserved.extend(
206            sidecar_image
207                .into_iter()
208                .map(|r| (r, ReservedMemoryType::SidecarImage)),
209        );
210        reserved.extend(
211            page_tables
212                .into_iter()
213                .map(|r| (r, ReservedMemoryType::TdxPageTables)),
214        );
215        reserved.sort_unstable_by_key(|(r, _)| r.start());
216
217        let mut used_ranges: ArrayVec<(MemoryRange, AddressUsage), 10> = ArrayVec::new();
218
219        // Construct initial used ranges by walking both the bootshim_used range
220        // and all reserved ranges that overlap.
221        for (entry, r) in walk_ranges(
222            core::iter::once((bootshim_used, AddressUsage::Used)),
223            reserved.iter().cloned(),
224        ) {
225            match r {
226                RangeWalkResult::Left(_) => {
227                    used_ranges.push((entry, AddressUsage::Used));
228                }
229                RangeWalkResult::Both(_, reserved_type) => {
230                    used_ranges.push((entry, AddressUsage::Reserved(reserved_type)));
231                }
232                RangeWalkResult::Right(typ) => {
233                    return Err(Error::ReservedRangeOutsideBootshimUsed {
234                        reserved: entry,
235                        typ,
236                        bootshim_used,
237                    });
238                }
239                RangeWalkResult::Neither => {}
240            }
241        }
242
243        // Construct the initial state of VTL2 address space by walking ram and reserved ranges
244        assert!(manager.address_space.is_empty());
245        for (entry, r) in walk_ranges(
246            vtl2_ram.iter().map(|e| (e.range, e.vnode)),
247            used_ranges.iter().map(|(r, usage)| (*r, usage)),
248        ) {
249            match r {
250                RangeWalkResult::Left(vnode) => {
251                    // VTL2 normal ram, unused by anything.
252                    manager.address_space.push(AddressRange {
253                        range: entry,
254                        vnode,
255                        usage: AddressUsage::Free,
256                    });
257                }
258                RangeWalkResult::Both(vnode, usage) => {
259                    // VTL2 ram, currently in use.
260                    manager.address_space.push(AddressRange {
261                        range: entry,
262                        vnode,
263                        usage: *usage,
264                    });
265                }
266                RangeWalkResult::Right(usage) => {
267                    panic!("vtl2 range {entry:#x?} used by {usage:?} not contained in vtl2 ram");
268                }
269                RangeWalkResult::Neither => {}
270            }
271        }
272
273        Ok(manager)
274    }
275}
276
277impl AddressSpaceManager {
278    pub const fn new_const() -> Self {
279        Self {
280            address_space: ArrayVec::new_const(),
281            vtl2_pool: false,
282        }
283    }
284
285    /// Split a free range into two, with allocation policy deciding if we
286    /// allocate the low part or high part.
287    fn allocate_range(
288        &mut self,
289        index: usize,
290        len: u64,
291        usage: AddressUsage,
292        allocation_policy: AllocationPolicy,
293    ) -> AllocatedRange {
294        assert!(usage != AddressUsage::Free);
295        let range = self.address_space.get_mut(index).expect("valid index");
296        assert_eq!(range.usage, AddressUsage::Free);
297        assert!(range.range.len() >= len);
298
299        let (used, remainder) = match allocation_policy {
300            AllocationPolicy::LowMemory => {
301                // Allocate from the beginning (low addresses)
302                range.range.split_at_offset(len)
303            }
304            AllocationPolicy::HighMemory => {
305                // Allocate from the end (high addresses)
306                let offset = range.range.len() - len;
307                let (remainder, used) = range.range.split_at_offset(offset);
308                (used, remainder)
309            }
310        };
311
312        let remainder = if !remainder.is_empty() {
313            Some(AddressRange {
314                range: remainder,
315                vnode: range.vnode,
316                usage: AddressUsage::Free,
317            })
318        } else {
319            None
320        };
321
322        // Update this range to mark it as used
323        range.usage = usage;
324        range.range = used;
325        let allocated = AllocatedRange {
326            range: used,
327            vnode: range.vnode,
328        };
329
330        if let Some(remainder) = remainder {
331            match allocation_policy {
332                AllocationPolicy::LowMemory => {
333                    // When allocating from low memory, the remainder goes after
334                    // the allocated range
335                    self.address_space.insert(index + 1, remainder);
336                }
337                AllocationPolicy::HighMemory => {
338                    // When allocating from high memory, the remainder goes
339                    // before the allocated range
340                    self.address_space.insert(index, remainder);
341                }
342            }
343        }
344
345        allocated
346    }
347
348    /// Allocate a new range of memory with the given type and policy. None is
349    /// returned if the allocation was unable to be satisfied.
350    ///
351    /// `len` is the number of bytes to allocate. The number of bytes are
352    /// rounded up to the next 4K page size increment. if `len` is 0, then
353    /// `None` is returned.
354    ///
355    /// `required_vnode` if `Some(u32)` is the vnode to allocate from. If there
356    /// are no free ranges left in that vnode, None is returned.
357    pub fn allocate(
358        &mut self,
359        required_vnode: Option<u32>,
360        len: u64,
361        allocation_type: AllocationType,
362        allocation_policy: AllocationPolicy,
363    ) -> Option<AllocatedRange> {
364        if len == 0 {
365            return None;
366        }
367
368        // Round up to the next 4k page size, if the caller did not specify a
369        // multiple of 4k.
370        let len = len.div_ceil(PAGE_SIZE_4K) * PAGE_SIZE_4K;
371
372        fn find_index<'a>(
373            mut iter: impl Iterator<Item = (usize, &'a AddressRange)>,
374            preferred_vnode: Option<u32>,
375            len: u64,
376        ) -> Option<usize> {
377            iter.find_map(|(index, range)| {
378                if range.usage == AddressUsage::Free
379                    && range.range.len() >= len
380                    && preferred_vnode.map(|pv| pv == range.vnode).unwrap_or(true)
381                {
382                    Some(index)
383                } else {
384                    None
385                }
386            })
387        }
388
389        // Walk ranges in forward/reverse order, depending on allocation policy.
390        let index = {
391            let iter = self.address_space.iter().enumerate();
392            match allocation_policy {
393                AllocationPolicy::LowMemory => find_index(iter, required_vnode, len),
394                AllocationPolicy::HighMemory => find_index(iter.rev(), required_vnode, len),
395            }
396        };
397
398        let alloc = index.map(|index| {
399            self.allocate_range(
400                index,
401                len,
402                match allocation_type {
403                    AllocationType::GpaPool => {
404                        AddressUsage::Reserved(ReservedMemoryType::Vtl2GpaPool)
405                    }
406                    AllocationType::SidecarNode => {
407                        AddressUsage::Reserved(ReservedMemoryType::SidecarNode)
408                    }
409                },
410                allocation_policy,
411            )
412        });
413
414        if allocation_type == AllocationType::GpaPool && alloc.is_some() {
415            self.vtl2_pool = true;
416        }
417
418        alloc
419    }
420
421    /// Returns an iterator for all VTL2 ranges.
422    pub fn vtl2_ranges(&self) -> impl Iterator<Item = (MemoryRange, MemoryVtlType)> + use<'_> {
423        memory_range::merge_adjacent_ranges(
424            self.address_space.iter().map(|r| (r.range, r.usage.into())),
425        )
426    }
427
428    /// Returns an iterator for reserved VTL2 ranges that should not be
429    /// described as ram to the kernel.
430    pub fn reserved_vtl2_ranges(
431        &self,
432    ) -> impl Iterator<Item = (MemoryRange, ReservedMemoryType)> + use<'_> {
433        self.address_space.iter().filter_map(|r| match r.usage {
434            AddressUsage::Reserved(typ) => Some((r.range, typ)),
435            _ => None,
436        })
437    }
438
439    /// Returns true if there are VTL2 pool allocations.
440    pub fn has_vtl2_pool(&self) -> bool {
441        self.vtl2_pool
442    }
443}
444
445#[derive(Debug, Clone, Copy, PartialEq, Eq)]
446pub enum AllocationType {
447    GpaPool,
448    SidecarNode,
449}
450
451pub enum AllocationPolicy {
452    // prefer low memory
453    LowMemory,
454    // prefer high memory
455    // TODO: only used in tests, but will be used in an upcoming change
456    #[allow(dead_code)]
457    HighMemory,
458}
459
460#[cfg(test)]
461mod tests {
462    use super::*;
463
464    #[test]
465    fn test_allocate() {
466        let mut address_space = AddressSpaceManager::new_const();
467        let vtl2_ram = &[MemoryEntry {
468            range: MemoryRange::new(0x0..0x20000),
469            vnode: 0,
470            mem_type: MemoryMapEntryType::MEMORY,
471        }];
472
473        AddressSpaceManagerBuilder::new(
474            &mut address_space,
475            vtl2_ram,
476            MemoryRange::new(0x0..0xF000),
477            [
478                MemoryRange::new(0x3000..0x4000),
479                MemoryRange::new(0x5000..0x6000),
480            ]
481            .iter()
482            .cloned(),
483        )
484        .with_reserved_range(MemoryRange::new(0x8000..0xA000))
485        .with_sidecar_image(MemoryRange::new(0xA000..0xC000))
486        .init()
487        .unwrap();
488
489        let range = address_space
490            .allocate(
491                None,
492                0x1000,
493                AllocationType::GpaPool,
494                AllocationPolicy::HighMemory,
495            )
496            .unwrap();
497        assert_eq!(range.range, MemoryRange::new(0x1F000..0x20000));
498        assert!(address_space.has_vtl2_pool());
499
500        let range = address_space
501            .allocate(
502                None,
503                0x2000,
504                AllocationType::GpaPool,
505                AllocationPolicy::HighMemory,
506            )
507            .unwrap();
508        assert_eq!(range.range, MemoryRange::new(0x1D000..0x1F000));
509
510        let range = address_space
511            .allocate(
512                None,
513                0x3000,
514                AllocationType::GpaPool,
515                AllocationPolicy::LowMemory,
516            )
517            .unwrap();
518        assert_eq!(range.range, MemoryRange::new(0xF000..0x12000));
519
520        let range = address_space
521            .allocate(
522                None,
523                0x1000,
524                AllocationType::GpaPool,
525                AllocationPolicy::LowMemory,
526            )
527            .unwrap();
528        assert_eq!(range.range, MemoryRange::new(0x12000..0x13000));
529    }
530
531    // test numa allocation
532    #[test]
533    fn test_allocate_numa() {
534        let mut address_space = AddressSpaceManager::new_const();
535        let vtl2_ram = &[
536            MemoryEntry {
537                range: MemoryRange::new(0x0..0x20000),
538                vnode: 0,
539                mem_type: MemoryMapEntryType::MEMORY,
540            },
541            MemoryEntry {
542                range: MemoryRange::new(0x20000..0x40000),
543                vnode: 1,
544                mem_type: MemoryMapEntryType::MEMORY,
545            },
546            MemoryEntry {
547                range: MemoryRange::new(0x40000..0x60000),
548                vnode: 2,
549                mem_type: MemoryMapEntryType::MEMORY,
550            },
551            MemoryEntry {
552                range: MemoryRange::new(0x60000..0x80000),
553                vnode: 3,
554                mem_type: MemoryMapEntryType::MEMORY,
555            },
556        ];
557
558        AddressSpaceManagerBuilder::new(
559            &mut address_space,
560            vtl2_ram,
561            MemoryRange::new(0x0..0x10000),
562            [
563                MemoryRange::new(0x3000..0x4000),
564                MemoryRange::new(0x5000..0x6000),
565            ]
566            .iter()
567            .cloned(),
568        )
569        .with_reserved_range(MemoryRange::new(0x8000..0xA000))
570        .with_sidecar_image(MemoryRange::new(0xA000..0xC000))
571        .init()
572        .unwrap();
573
574        let range = address_space
575            .allocate(
576                Some(0),
577                0x1000,
578                AllocationType::GpaPool,
579                AllocationPolicy::HighMemory,
580            )
581            .unwrap();
582        assert_eq!(range.range, MemoryRange::new(0x1F000..0x20000));
583        assert_eq!(range.vnode, 0);
584
585        let range = address_space
586            .allocate(
587                Some(0),
588                0x2000,
589                AllocationType::SidecarNode,
590                AllocationPolicy::HighMemory,
591            )
592            .unwrap();
593        assert_eq!(range.range, MemoryRange::new(0x1D000..0x1F000));
594        assert_eq!(range.vnode, 0);
595
596        let range = address_space
597            .allocate(
598                Some(2),
599                0x3000,
600                AllocationType::GpaPool,
601                AllocationPolicy::HighMemory,
602            )
603            .unwrap();
604        assert_eq!(range.range, MemoryRange::new(0x5D000..0x60000));
605        assert_eq!(range.vnode, 2);
606
607        // allocate all of node 3, then subsequent allocations fail
608        let range = address_space
609            .allocate(
610                Some(3),
611                0x20000,
612                AllocationType::SidecarNode,
613                AllocationPolicy::HighMemory,
614            )
615            .unwrap();
616        assert_eq!(range.range, MemoryRange::new(0x60000..0x80000));
617        assert_eq!(range.vnode, 3);
618
619        let range = address_space.allocate(
620            Some(3),
621            0x1000,
622            AllocationType::SidecarNode,
623            AllocationPolicy::HighMemory,
624        );
625        assert!(
626            range.is_none(),
627            "allocation should fail, no space left for node 3"
628        );
629    }
630
631    // test unaligned 4k allocations
632    #[test]
633    fn test_unaligned_allocations() {
634        let mut address_space = AddressSpaceManager::new_const();
635        let vtl2_ram = &[MemoryEntry {
636            range: MemoryRange::new(0x0..0x20000),
637            vnode: 0,
638            mem_type: MemoryMapEntryType::MEMORY,
639        }];
640
641        AddressSpaceManagerBuilder::new(
642            &mut address_space,
643            vtl2_ram,
644            MemoryRange::new(0x0..0xF000),
645            [
646                MemoryRange::new(0x3000..0x4000),
647                MemoryRange::new(0x5000..0x6000),
648            ]
649            .iter()
650            .cloned(),
651        )
652        .with_reserved_range(MemoryRange::new(0x8000..0xA000))
653        .with_sidecar_image(MemoryRange::new(0xA000..0xC000))
654        .init()
655        .unwrap();
656
657        let range = address_space
658            .allocate(
659                None,
660                0x1001,
661                AllocationType::GpaPool,
662                AllocationPolicy::HighMemory,
663            )
664            .unwrap();
665        assert_eq!(range.range, MemoryRange::new(0x1E000..0x20000));
666
667        let range = address_space
668            .allocate(
669                None,
670                0xFFF,
671                AllocationType::GpaPool,
672                AllocationPolicy::HighMemory,
673            )
674            .unwrap();
675        assert_eq!(range.range, MemoryRange::new(0x1D000..0x1E000));
676
677        let range = address_space.allocate(
678            None,
679            0,
680            AllocationType::GpaPool,
681            AllocationPolicy::HighMemory,
682        );
683        assert!(range.is_none());
684    }
685
686    // test invalid init ranges
687    #[test]
688    fn test_invalid_init_ranges() {
689        let vtl2_ram = [MemoryEntry {
690            range: MemoryRange::new(0x0..0x20000),
691            vnode: 0,
692            mem_type: MemoryMapEntryType::MEMORY,
693        }];
694        let bootshim_used = MemoryRange::new(0x0..0xF000);
695
696        // test config range completely outside of bootshim_used
697        let mut address_space = AddressSpaceManager::new_const();
698
699        let result = AddressSpaceManagerBuilder::new(
700            &mut address_space,
701            &vtl2_ram,
702            bootshim_used,
703            [MemoryRange::new(0x10000..0x11000)].iter().cloned(), // completely outside
704        )
705        .init();
706
707        assert!(matches!(
708            result,
709            Err(Error::ReservedRangeOutsideBootshimUsed { .. })
710        ));
711
712        // test config range partially overlapping with bootshim_used
713
714        let mut address_space = AddressSpaceManager::new_const();
715        let result = AddressSpaceManagerBuilder::new(
716            &mut address_space,
717            &vtl2_ram,
718            bootshim_used,
719            [MemoryRange::new(0xE000..0x10000)].iter().cloned(), // partially overlapping
720        )
721        .init();
722
723        assert!(matches!(
724            result,
725            Err(Error::ReservedRangeOutsideBootshimUsed { .. })
726        ));
727    }
728}