Skip to main content

membacking/memory_manager/
mod.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! OpenVMM's memory manager.
5
6mod device_memory;
7
8pub use device_memory::DeviceMemoryMapper;
9
10use crate::RemoteProcess;
11use crate::mapping_manager::Mappable;
12use crate::mapping_manager::MappingManager;
13use crate::mapping_manager::MappingManagerClient;
14use crate::mapping_manager::VaMapper;
15use crate::mapping_manager::VaMapperError;
16use crate::partition_mapper::PartitionMapper;
17use crate::region_manager::MapParams;
18use crate::region_manager::RegionHandle;
19use crate::region_manager::RegionManager;
20use guestmem::GuestMemory;
21use hvdef::Vtl;
22use inspect::Inspect;
23use memory_range::MemoryRange;
24use mesh::MeshPayload;
25use pal_async::DefaultPool;
26use sparse_mmap::SparseMapping;
27use std::io;
28use std::sync::Arc;
29use std::thread::JoinHandle;
30use thiserror::Error;
31use vm_topology::memory::MemoryLayout;
32
33/// The OpenVMM memory manager.
34#[derive(Debug, Inspect)]
35pub struct GuestMemoryManager {
36    /// Guest RAM allocation. None in private memory mode.
37    #[inspect(skip)]
38    guest_ram: Option<Mappable>,
39
40    #[inspect(skip)]
41    ram_regions: Arc<Vec<RamRegion>>,
42
43    #[inspect(flatten)]
44    mapping_manager: MappingManager,
45
46    #[inspect(flatten)]
47    region_manager: RegionManager,
48
49    #[inspect(skip)]
50    va_mapper: Arc<VaMapper>,
51
52    #[inspect(skip)]
53    _thread: JoinHandle<()>,
54
55    vtl0_alias_map_offset: Option<u64>,
56    pin_mappings: bool,
57}
58
59#[derive(Debug)]
60struct RamRegion {
61    range: MemoryRange,
62    handle: RegionHandle,
63}
64
65/// Errors when attaching a partition to a [`GuestMemoryManager`].
66#[derive(Error, Debug)]
67pub enum PartitionAttachError {
68    /// Failure to allocate a VA mapper.
69    #[error("failed to reserve VA range for partition mapping")]
70    VaMapper(#[source] VaMapperError),
71    /// Failure to map memory into a partition.
72    #[error("failed to attach partition to memory manager")]
73    PartitionMapper(#[source] crate::partition_mapper::PartitionMapperError),
74}
75
76/// Errors creating a [`GuestMemoryManager`].
77#[derive(Error, Debug)]
78pub enum MemoryBuildError {
79    /// RAM too large.
80    #[error("ram size {0} is too large")]
81    RamTooLarge(MemorySize),
82    /// Couldn't allocate RAM.
83    #[error("failed to allocate memory")]
84    AllocationFailed(#[source] io::Error),
85    /// Couldn't allocate hugetlb-backed RAM.
86    #[error(
87        "failed to reserve {page_count} hugetlb pages of {hugepage_size} each ({size} total); increase the hugetlb pool or reduce guest memory size"
88    )]
89    HugepageAllocationFailed {
90        /// Total RAM backing size.
91        size: MemorySize,
92        /// Requested or default hugepage size.
93        hugepage_size: MemorySize,
94        /// Number of hugepages required.
95        page_count: usize,
96        /// The allocation error.
97        #[source]
98        error: io::Error,
99    },
100    /// Couldn't allocate VA mapper.
101    #[error("failed to create VA mapper")]
102    VaMapper(#[source] VaMapperError),
103    /// Memory layout incompatible with VTL0 alias map.
104    #[error("not enough guest address space available for the vtl0 alias map")]
105    AliasMapWontFit,
106    /// Memory layout incompatible with x86 legacy support.
107    #[error("x86 support requires RAM to start at 0 and contain at least 1MB")]
108    InvalidRamForX86,
109    /// Private memory is incompatible with x86 legacy support.
110    #[error("private memory is incompatible with x86 legacy support")]
111    PrivateMemoryWithLegacy,
112    /// Private memory is incompatible with existing memory backing.
113    #[error("private memory is incompatible with existing memory backing")]
114    PrivateMemoryWithExistingBacking,
115    /// Failed to allocate private RAM range.
116    #[error("failed to allocate private RAM range {1}")]
117    PrivateRamAlloc(#[source] io::Error, MemoryRange),
118    /// THP requires private memory mode.
119    #[error("transparent huge pages requires private memory mode")]
120    ThpWithoutPrivateMemory,
121    /// THP is only supported on Linux.
122    #[error("transparent huge pages is only supported on Linux")]
123    ThpUnsupportedPlatform,
124    /// Hugepage size is too large.
125    #[error("hugepage size {0} is too large")]
126    HugepageSizeTooLarge(MemorySize),
127    /// Hugepages are only supported on Linux.
128    #[error("hugepages are only supported on Linux")]
129    HugepagesUnsupportedPlatform,
130    /// Hugepages require shared memory mode.
131    #[error("hugepages require shared memory mode")]
132    HugepagesWithPrivateMemory,
133    /// Hugepages are incompatible with existing memory backing.
134    #[error("hugepages are incompatible with existing memory backing")]
135    HugepagesWithExistingBacking,
136    /// Hugepages are incompatible with x86 legacy RAM splitting.
137    #[error("hugepages are incompatible with x86 legacy RAM splitting")]
138    HugepagesWithLegacy,
139    /// Invalid hugepage size.
140    #[error("hugepage size {0} must be a power of two and at least the host page size")]
141    InvalidHugepageSize(MemorySize),
142    /// RAM size is not aligned to the hugepage size.
143    #[error(
144        "RAM size {ram_size} is not aligned to {hugepage_size} hugepages; choose a memory size that is a multiple of the hugepage size"
145    )]
146    HugepageRamSizeUnaligned {
147        /// Total RAM backing size.
148        ram_size: MemorySize,
149        /// Required hugepage alignment.
150        hugepage_size: MemorySize,
151    },
152    /// A RAM range is not aligned to the hugepage size.
153    #[error(
154        "RAM range {range} ({range_size}) is not aligned to {hugepage_size} hugepages; range start and size must both be multiples of the hugepage size"
155    )]
156    HugepageRamRangeUnaligned {
157        /// The unaligned RAM range.
158        range: MemoryRange,
159        /// The RAM range size.
160        range_size: MemorySize,
161        /// Required hugepage alignment.
162        hugepage_size: MemorySize,
163    },
164}
165
166const DEFAULT_HUGEPAGE_SIZE: u64 = 2 * 1024 * 1024;
167
168/// Explicit hugetlb memfd backing configuration.
169#[derive(Debug, Copy, Clone)]
170struct HugepageConfig {
171    size: Option<u64>,
172}
173
174fn validate_hugepage_size(size: u64) -> Result<usize, MemoryBuildError> {
175    if !size.is_power_of_two() || size < SparseMapping::page_size() as u64 {
176        return Err(MemoryBuildError::InvalidHugepageSize(MemorySize(size)));
177    }
178    size.try_into()
179        .map_err(|_| MemoryBuildError::HugepageSizeTooLarge(MemorySize(size)))
180}
181
182/// A byte count displayed in a human-readable format in error messages.
183#[derive(Debug, Copy, Clone)]
184pub struct MemorySize(
185    /// The size in bytes.
186    pub u64,
187);
188
189impl std::fmt::Display for MemorySize {
190    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
191        const KB: u64 = 1024;
192        const MB: u64 = 1024 * KB;
193        const GB: u64 = 1024 * MB;
194        const TB: u64 = 1024 * GB;
195
196        for (unit, suffix) in [(TB, "TB"), (GB, "GB"), (MB, "MB"), (KB, "KB")] {
197            if self.0 != 0 && self.0.is_multiple_of(unit) {
198                return write!(f, "{} {suffix}", self.0 / unit);
199            }
200        }
201
202        write!(f, "{} bytes", self.0)
203    }
204}
205
206fn validate_hugepage_ram_alignment(
207    ram_size: u64,
208    ram_ranges: &[MemoryRange],
209    hugepage_size: u64,
210) -> Result<(), MemoryBuildError> {
211    if !ram_size.is_multiple_of(hugepage_size) {
212        return Err(MemoryBuildError::HugepageRamSizeUnaligned {
213            ram_size: MemorySize(ram_size),
214            hugepage_size: MemorySize(hugepage_size),
215        });
216    }
217    for &range in ram_ranges {
218        if !range.start().is_multiple_of(hugepage_size)
219            || !range.len().is_multiple_of(hugepage_size)
220        {
221            return Err(MemoryBuildError::HugepageRamRangeUnaligned {
222                range,
223                range_size: MemorySize(range.len()),
224                hugepage_size: MemorySize(hugepage_size),
225            });
226        }
227    }
228    Ok(())
229}
230
231/// A builder for [`GuestMemoryManager`].
232pub struct GuestMemoryBuilder {
233    existing_mapping: Option<SharedMemoryBacking>,
234    vtl0_alias_map: Option<u64>,
235    prefetch_ram: bool,
236    pin_mappings: bool,
237    x86_legacy_support: bool,
238    private_memory: bool,
239    transparent_hugepages: bool,
240    hugepages: Option<HugepageConfig>,
241}
242
243impl GuestMemoryBuilder {
244    /// Returns a new builder.
245    pub fn new() -> Self {
246        Self {
247            existing_mapping: None,
248            vtl0_alias_map: None,
249            pin_mappings: false,
250            prefetch_ram: false,
251            x86_legacy_support: false,
252            private_memory: false,
253            transparent_hugepages: false,
254            hugepages: None,
255        }
256    }
257
258    /// Specifies an existing memory backing to use.
259    pub fn existing_backing(mut self, mapping: Option<SharedMemoryBacking>) -> Self {
260        self.existing_mapping = mapping;
261        self
262    }
263
264    /// Specifies the offset of the VTL0 alias map, if enabled for VTL2. This is
265    /// a mirror of VTL0 memory into a high portion of the VM's physical address
266    /// space.
267    pub fn vtl0_alias_map(mut self, offset: Option<u64>) -> Self {
268        self.vtl0_alias_map = offset;
269        self
270    }
271
272    /// Specify whether to pin mappings in memory. This is used to support
273    /// device assignment for devices that require the IOMMU to be programmed
274    /// for all addresses.
275    pub fn pin_mappings(mut self, enable: bool) -> Self {
276        self.pin_mappings = enable;
277        self
278    }
279
280    /// Specify whether to prefetch RAM mappings. This improves boot performance
281    /// by reducing memory intercepts at the cost of pre-allocating all of RAM.
282    pub fn prefetch_ram(mut self, enable: bool) -> Self {
283        self.prefetch_ram = enable;
284        self
285    }
286
287    /// Enables legacy x86 support.
288    ///
289    /// When set, create separate RAM regions for the various low memory ranges
290    /// that are special on x86 platforms. Specifically:
291    ///
292    /// 1. Create a separate RAM region for the VGA VRAM window:
293    ///    0xa0000-0xbffff.
294    /// 2. Create separate RAM regions within 0xc0000-0xfffff for control by PAM
295    ///    registers.
296    ///
297    /// The caller can use [`RamVisibilityControl`] to adjust the visibility of
298    /// these ranges.
299    pub fn x86_legacy_support(mut self, enable: bool) -> Self {
300        self.x86_legacy_support = enable;
301        self
302    }
303
304    /// Enables private anonymous memory for guest RAM.
305    ///
306    /// When set, guest RAM is backed by anonymous pages (`mmap
307    /// MAP_ANONYMOUS` on Linux, `VirtualAlloc` on Windows) rather than
308    /// shared file-backed sections. This supports decommit to release
309    /// physical pages back to the host.
310    ///
311    /// This is incompatible with [`x86_legacy_support`](Self::x86_legacy_support)
312    /// and [`existing_backing`](Self::existing_backing).
313    pub fn private_memory(mut self, enable: bool) -> Self {
314        self.private_memory = enable;
315        self
316    }
317
318    /// Enables Transparent Huge Pages for guest RAM.
319    ///
320    /// When set, `madvise(MADV_HUGEPAGE)` is called on private RAM allocations
321    /// to allow khugepaged to collapse 4K pages into 2MB huge pages.
322    /// Requires [`private_memory`](Self::private_memory) and Linux; `build()`
323    /// will return an error if either condition is not met.
324    pub fn transparent_hugepages(mut self, enable: bool) -> Self {
325        self.transparent_hugepages = enable;
326        self
327    }
328
329    /// Enables explicit hugetlb memfd backing for guest RAM.
330    pub fn hugepages(mut self, size: Option<u64>) -> Self {
331        self.hugepages = Some(HugepageConfig { size });
332        self
333    }
334
335    /// Builds the memory backing, allocating memory if existing memory was not
336    /// provided by [`existing_backing`](Self::existing_backing).
337    pub async fn build(
338        self,
339        mem_layout: &MemoryLayout,
340    ) -> Result<GuestMemoryManager, MemoryBuildError> {
341        // Validate private memory constraints.
342        if self.private_memory {
343            if self.x86_legacy_support {
344                return Err(MemoryBuildError::PrivateMemoryWithLegacy);
345            }
346            if self.existing_mapping.is_some() {
347                return Err(MemoryBuildError::PrivateMemoryWithExistingBacking);
348            }
349        }
350
351        // Validate THP constraints.
352        if self.transparent_hugepages {
353            if !self.private_memory {
354                return Err(MemoryBuildError::ThpWithoutPrivateMemory);
355            }
356            if !cfg!(target_os = "linux") {
357                return Err(MemoryBuildError::ThpUnsupportedPlatform);
358            }
359        }
360
361        let ram_size = mem_layout.ram_size() + mem_layout.vtl2_range().map_or(0, |r| r.len());
362
363        let mut ram_ranges = mem_layout
364            .ram()
365            .iter()
366            .map(|x| x.range)
367            .chain(mem_layout.vtl2_range())
368            .collect::<Vec<_>>();
369
370        let hugepage_size = if let Some(hugepages) = self.hugepages {
371            if !cfg!(target_os = "linux") {
372                return Err(MemoryBuildError::HugepagesUnsupportedPlatform);
373            }
374            if self.private_memory {
375                return Err(MemoryBuildError::HugepagesWithPrivateMemory);
376            }
377            if self.existing_mapping.is_some() {
378                return Err(MemoryBuildError::HugepagesWithExistingBacking);
379            }
380            if self.x86_legacy_support {
381                return Err(MemoryBuildError::HugepagesWithLegacy);
382            }
383            let size = validate_hugepage_size(hugepages.size.unwrap_or(DEFAULT_HUGEPAGE_SIZE))?;
384            validate_hugepage_ram_alignment(ram_size, &ram_ranges, size as u64)?;
385            Some(size)
386        } else {
387            None
388        };
389
390        let memory: Option<Mappable> = if self.private_memory {
391            // Private memory mode: no shared file-backed allocation.
392            // RAM will be backed by anonymous pages in the VaMapper's SparseMapping.
393            None
394        } else if let Some(memory) = self.existing_mapping {
395            Some(memory.guest_ram)
396        } else {
397            let ram_size = ram_size
398                .try_into()
399                .map_err(|_| MemoryBuildError::RamTooLarge(MemorySize(ram_size)))?;
400            let guest_ram = if let Some(hugepage_size) = hugepage_size {
401                sparse_mmap::alloc_shared_memory_hugetlb(ram_size, "guest-ram", Some(hugepage_size))
402                    .map_err(|error| MemoryBuildError::HugepageAllocationFailed {
403                        size: MemorySize(ram_size as u64),
404                        hugepage_size: MemorySize(hugepage_size as u64),
405                        page_count: ram_size / hugepage_size,
406                        error,
407                    })?
408            } else {
409                sparse_mmap::alloc_shared_memory(ram_size, "guest-ram")
410                    .map_err(MemoryBuildError::AllocationFailed)?
411            };
412            Some(guest_ram.into())
413        };
414
415        // Spawn a thread to handle memory requests.
416        //
417        // FUTURE: move this to a task once the GuestMemory deadlocks are resolved.
418        let (thread, spawner) = DefaultPool::spawn_on_thread("memory_manager");
419
420        let max_addr =
421            (mem_layout.end_of_layout()).max(mem_layout.vtl2_range().map_or(0, |r| r.end()));
422
423        let vtl0_alias_map_offset = if let Some(offset) = self.vtl0_alias_map {
424            if max_addr > offset {
425                return Err(MemoryBuildError::AliasMapWontFit);
426            }
427            Some(offset)
428        } else {
429            None
430        };
431
432        let mapping_manager =
433            MappingManager::new(&spawner, max_addr, self.private_memory, hugepage_size);
434        let va_mapper = mapping_manager
435            .client()
436            .new_mapper()
437            .await
438            .map_err(MemoryBuildError::VaMapper)?;
439
440        let region_manager = RegionManager::new(&spawner, mapping_manager.client().clone());
441
442        if self.x86_legacy_support {
443            if ram_ranges[0].start() != 0 || ram_ranges[0].end() < 0x100000 {
444                return Err(MemoryBuildError::InvalidRamForX86);
445            }
446
447            // Split RAM ranges to support PAM registers and VGA RAM.
448            let range_starts = [
449                0,
450                0xa0000,
451                0xc0000,
452                0xc4000,
453                0xc8000,
454                0xcc000,
455                0xd0000,
456                0xd4000,
457                0xd8000,
458                0xdc000,
459                0xe0000,
460                0xe4000,
461                0xe8000,
462                0xec000,
463                0xf0000,
464                0x100000,
465                ram_ranges[0].end(),
466            ];
467
468            ram_ranges.splice(
469                0..1,
470                range_starts
471                    .iter()
472                    .zip(range_starts.iter().skip(1))
473                    .map(|(&start, &end)| MemoryRange::new(start..end)),
474            );
475        }
476
477        // In private memory mode, eagerly commit all RAM ranges with
478        // anonymous memory. alloc_range() handles both Linux (mmap MAP_FIXED)
479        // and Windows (MEM_REPLACE_PLACEHOLDER).
480        if self.private_memory {
481            for range in &ram_ranges {
482                va_mapper
483                    .alloc_range(range.start() as usize, range.len() as usize)
484                    .map_err(|e| MemoryBuildError::PrivateRamAlloc(e, *range))?;
485                va_mapper.set_range_name(
486                    range.start() as usize,
487                    range.len() as usize,
488                    "guest-ram-private",
489                );
490            }
491
492            // Mark private RAM as THP-eligible so khugepaged can collapse
493            // 4K pages into 2MB huge pages.
494            #[cfg(target_os = "linux")]
495            if self.transparent_hugepages {
496                for range in &ram_ranges {
497                    if let Err(e) =
498                        va_mapper.madvise_hugepage(range.start() as usize, range.len() as usize)
499                    {
500                        tracing::warn!(
501                            error = &e as &dyn std::error::Error,
502                            range = %range,
503                            "failed to mark RAM as THP eligible"
504                        );
505                    }
506                }
507            }
508        }
509
510        let mut ram_regions = Vec::new();
511        let mut start = 0;
512        for range in &ram_ranges {
513            let region = region_manager
514                .client()
515                .new_region("ram".into(), *range, RAM_PRIORITY, true)
516                .await
517                .expect("regions cannot overlap yet");
518
519            if let Some(ref memory) = memory {
520                // File-backed mode: add mapping for this RAM range.
521                region
522                    .add_mapping(
523                        MemoryRange::new(0..range.len()),
524                        memory.clone(),
525                        start,
526                        true,
527                    )
528                    .await;
529            }
530            // In private_memory mode, skip add_mapping — no file-backed RAM.
531            // The SparseMapping VA is already committed via alloc_range() above.
532
533            region
534                .map(MapParams {
535                    writable: true,
536                    executable: true,
537                    prefetch: self.prefetch_ram && !self.private_memory,
538                })
539                .await;
540
541            ram_regions.push(RamRegion {
542                range: *range,
543                handle: region,
544            });
545            start += range.len();
546        }
547
548        let gm = GuestMemoryManager {
549            guest_ram: memory,
550            _thread: thread,
551            ram_regions: Arc::new(ram_regions),
552            mapping_manager,
553            region_manager,
554            va_mapper,
555            vtl0_alias_map_offset,
556            pin_mappings: self.pin_mappings,
557        };
558        Ok(gm)
559    }
560}
561
562/// The backing objects used to transfer guest memory between processes.
563#[derive(Debug, MeshPayload)]
564pub struct SharedMemoryBacking {
565    guest_ram: Mappable,
566}
567
568impl SharedMemoryBacking {
569    /// Create a SharedMemoryBacking from a mappable handle/fd.
570    pub fn from_mappable(guest_ram: Mappable) -> Self {
571        Self { guest_ram }
572    }
573}
574
575/// A mesh-serializable object for providing access to guest memory.
576#[derive(Debug, MeshPayload)]
577pub struct GuestMemoryClient {
578    mapping_manager: MappingManagerClient,
579}
580
581impl GuestMemoryClient {
582    /// Retrieves a [`GuestMemory`] object to access guest memory from this
583    /// process.
584    ///
585    /// This call will ensure only one VA mapper is allocated per process, so
586    /// this is safe to call many times without allocating tons of virtual
587    /// address space.
588    pub async fn guest_memory(&self) -> Result<GuestMemory, VaMapperError> {
589        Ok(GuestMemory::new(
590            "ram",
591            self.mapping_manager.new_mapper().await?,
592        ))
593    }
594}
595
596// The region priority for RAM. Overrides anything else.
597const RAM_PRIORITY: u8 = 255;
598
599// The region priority for device memory.
600const DEVICE_PRIORITY: u8 = 0;
601
602impl GuestMemoryManager {
603    /// Returns an object to access guest memory.
604    pub fn client(&self) -> GuestMemoryClient {
605        GuestMemoryClient {
606            mapping_manager: self.mapping_manager.client().clone(),
607        }
608    }
609
610    /// Returns an object to map device memory into the VM.
611    pub fn device_memory_mapper(&self) -> DeviceMemoryMapper {
612        DeviceMemoryMapper::new(self.region_manager.client().clone())
613    }
614
615    /// Returns a client for registering DMA mappers (VFIO, iommufd).
616    pub fn dma_mapper_client(&self) -> crate::region_manager::DmaMapperClient {
617        crate::region_manager::DmaMapperClient::new(self.region_manager.client())
618    }
619
620    /// Returns an object for manipulating the visibility state of different RAM
621    /// regions.
622    pub fn ram_visibility_control(&self) -> RamVisibilityControl {
623        RamVisibilityControl {
624            regions: self.ram_regions.clone(),
625        }
626    }
627
628    /// Returns the shared memory resources that can be used to reconstruct the
629    /// memory backing.
630    ///
631    /// This can be used with [`GuestMemoryBuilder::existing_backing`] to create a
632    /// new memory manager with the same memory state. Only one instance of this
633    /// type should be managing a given memory backing at a time, though, or the
634    /// guest may see unpredictable results.
635    ///
636    /// Returns `None` in private memory mode, where there is no shared
637    /// file-backed allocation.
638    pub fn shared_memory_backing(&self) -> Option<SharedMemoryBacking> {
639        let guest_ram = self.guest_ram.clone()?;
640        Some(SharedMemoryBacking { guest_ram })
641    }
642
643    /// Attaches the guest memory to a partition, mapping it to the guest
644    /// physical address space.
645    ///
646    /// If `process` is provided, then allocate a VA range in that process for
647    /// the guest memory, and map the memory into the partition from that
648    /// process. This is necessary to work around WHP's lack of support for
649    /// mapping multiple partitions from a single process.
650    ///
651    /// TODO: currently, all VTLs will get the same mappings--no support for
652    /// per-VTL memory protections is supported.
653    pub async fn attach_partition(
654        &mut self,
655        vtl: Vtl,
656        partition: &Arc<dyn virt::PartitionMemoryMap>,
657        process: Option<RemoteProcess>,
658    ) -> Result<(), PartitionAttachError> {
659        let va_mapper = if let Some(process) = process {
660            self.mapping_manager
661                .client()
662                .new_remote_mapper(process)
663                .await
664                .map_err(PartitionAttachError::VaMapper)?
665        } else {
666            self.va_mapper.clone()
667        };
668
669        if vtl == Vtl::Vtl2 {
670            if let Some(offset) = self.vtl0_alias_map_offset {
671                let partition =
672                    PartitionMapper::new(partition, va_mapper.clone(), offset, self.pin_mappings);
673                self.region_manager
674                    .client()
675                    .add_partition(partition)
676                    .await
677                    .map_err(PartitionAttachError::PartitionMapper)?;
678            }
679        }
680
681        let partition = PartitionMapper::new(partition, va_mapper, 0, self.pin_mappings);
682        self.region_manager
683            .client()
684            .add_partition(partition)
685            .await
686            .map_err(PartitionAttachError::PartitionMapper)?;
687        Ok(())
688    }
689}
690
691/// A client to the [`GuestMemoryManager`] used to control the visibility of
692/// RAM regions.
693pub struct RamVisibilityControl {
694    regions: Arc<Vec<RamRegion>>,
695}
696
697/// The RAM visibility for use with [`RamVisibilityControl::set_ram_visibility`].
698#[derive(Debug, Copy, Clone, PartialEq, Eq)]
699pub enum RamVisibility {
700    /// RAM is unmapped, so reads and writes will go to device memory or MMIO.
701    Unmapped,
702    /// RAM is read-only. Writes will go to device memory or MMIO.
703    ///
704    /// Note that writes will take exits even if there is mapped device memory.
705    ReadOnly,
706    /// RAM is read-write by the guest.
707    ReadWrite,
708}
709
710/// An error returned by [`RamVisibilityControl::set_ram_visibility`].
711#[derive(Debug, Error)]
712#[error("{0} is not a controllable RAM range")]
713pub struct InvalidRamRegion(MemoryRange);
714
715impl RamVisibilityControl {
716    /// Sets the visibility of a RAM region.
717    ///
718    /// A whole region's visibility must be controlled at once, or an error will
719    /// be returned. [`GuestMemoryBuilder::x86_legacy_support`] can be used to
720    /// ensure that there are RAM regions corresponding to x86 memory ranges
721    /// that need to be controlled.
722    pub async fn set_ram_visibility(
723        &self,
724        range: MemoryRange,
725        visibility: RamVisibility,
726    ) -> Result<(), InvalidRamRegion> {
727        let region = self
728            .regions
729            .iter()
730            .find(|region| region.range == range)
731            .ok_or(InvalidRamRegion(range))?;
732
733        match visibility {
734            RamVisibility::ReadWrite | RamVisibility::ReadOnly => {
735                region
736                    .handle
737                    .map(MapParams {
738                        writable: matches!(visibility, RamVisibility::ReadWrite),
739                        executable: true,
740                        prefetch: false,
741                    })
742                    .await
743            }
744            RamVisibility::Unmapped => region.handle.unmap().await,
745        }
746        Ok(())
747    }
748}
749
750#[cfg(test)]
751mod tests {
752    use super::*;
753    use std::error::Error as _;
754
755    #[test]
756    fn test_validate_hugepage_size() {
757        let page_size = SparseMapping::page_size() as u64;
758        assert!(validate_hugepage_size(page_size).is_ok());
759        assert!(matches!(
760            validate_hugepage_size(page_size / 2),
761            Err(MemoryBuildError::InvalidHugepageSize(_))
762        ));
763        assert!(matches!(
764            validate_hugepage_size(3 * 1024 * 1024),
765            Err(MemoryBuildError::InvalidHugepageSize(_))
766        ));
767    }
768
769    #[test]
770    fn test_validate_hugepage_ram_alignment() {
771        const HUGEPAGE_SIZE: u64 = 2 * 1024 * 1024;
772
773        validate_hugepage_ram_alignment(
774            4 * 1024 * 1024,
775            &[
776                MemoryRange::new(0..HUGEPAGE_SIZE),
777                MemoryRange::new(2 * HUGEPAGE_SIZE..3 * HUGEPAGE_SIZE),
778            ],
779            HUGEPAGE_SIZE,
780        )
781        .unwrap();
782
783        assert!(matches!(
784            validate_hugepage_ram_alignment(3 * 1024 * 1024, &[], HUGEPAGE_SIZE),
785            Err(MemoryBuildError::HugepageRamSizeUnaligned { .. })
786        ));
787        assert!(matches!(
788            validate_hugepage_ram_alignment(
789                HUGEPAGE_SIZE,
790                &[MemoryRange::new(0..1024 * 1024)],
791                HUGEPAGE_SIZE,
792            ),
793            Err(MemoryBuildError::HugepageRamRangeUnaligned { .. })
794        ));
795    }
796
797    #[test]
798    fn test_hugepage_ram_size_alignment_error_message() {
799        let error =
800            validate_hugepage_ram_alignment(257 * 1024 * 1024, &[], 2 * 1024 * 1024).unwrap_err();
801
802        assert_eq!(
803            error.to_string(),
804            "RAM size 257 MB is not aligned to 2 MB hugepages; choose a memory size that is a multiple of the hugepage size"
805        );
806    }
807
808    #[test]
809    fn test_hugepage_ram_range_alignment_error_message() {
810        let error = validate_hugepage_ram_alignment(
811            2 * 1024 * 1024,
812            &[MemoryRange::new(0..1024 * 1024)],
813            2 * 1024 * 1024,
814        )
815        .unwrap_err();
816
817        assert_eq!(
818            error.to_string(),
819            "RAM range 0x0-0x100000 (1 MB) is not aligned to 2 MB hugepages; range start and size must both be multiples of the hugepage size"
820        );
821    }
822
823    #[test]
824    fn test_hugepage_allocation_error_message() {
825        let error = MemoryBuildError::HugepageAllocationFailed {
826            size: MemorySize(1024 * 1024 * 1024),
827            hugepage_size: MemorySize(2 * 1024 * 1024),
828            page_count: 512,
829            error: io::Error::new(io::ErrorKind::OutOfMemory, "Cannot allocate memory"),
830        };
831
832        assert_eq!(
833            error.to_string(),
834            "failed to reserve 512 hugetlb pages of 2 MB each (1 GB total); increase the hugetlb pool or reduce guest memory size"
835        );
836        assert_eq!(
837            error.source().unwrap().to_string(),
838            "Cannot allocate memory"
839        );
840    }
841}