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::MappingBacking;
13use crate::mapping_manager::MappingManager;
14use crate::mapping_manager::MappingManagerClient;
15use crate::mapping_manager::MemoryPolicy;
16use crate::mapping_manager::VaMapper;
17use crate::mapping_manager::VaMapperError;
18use crate::partition_mapper::PartitionMapper;
19use crate::region_manager::MapParams;
20use crate::region_manager::RegionHandle;
21use crate::region_manager::RegionManager;
22use guestmem::GuestMemory;
23use hvdef::Vtl;
24use inspect::Inspect;
25use memory_range::MemoryRange;
26use mesh::MeshPayload;
27use pal_async::DefaultPool;
28use sparse_mmap::SparseMapping;
29use std::io;
30use std::sync::Arc;
31use std::thread::JoinHandle;
32use thiserror::Error;
33
34/// The OpenVMM memory manager.
35#[derive(Debug, Inspect)]
36pub struct GuestMemoryManager {
37    /// Guest RAM allocations. One per backing request. Empty only when
38    /// there are no backing requests (no RAM at all).
39    #[inspect(skip)]
40    guest_ram: Vec<RamBacking>,
41
42    #[inspect(skip)]
43    ram_regions: Arc<Vec<RamRegion>>,
44
45    #[inspect(flatten)]
46    mapping_manager: MappingManager,
47
48    #[inspect(flatten)]
49    region_manager: RegionManager,
50
51    #[inspect(flatten)]
52    va_mapper: Arc<VaMapper>,
53
54    #[inspect(skip)]
55    _thread: JoinHandle<()>,
56
57    vtl0_alias_map_offset: Option<u64>,
58    pin_mappings: bool,
59    /// Whether the partition delivers guest-memory-access faults to the VMM,
60    /// enabling on-demand fault resolution via [`VaMapper`]. Recorded here for
61    /// lazy-commit gating.
62    supports_memory_fault_resolution: bool,
63}
64
65/// A single RAM backing allocation — one memfd or anonymous region.
66#[derive(Debug)]
67struct RamBacking {
68    /// The file-backed memory handle. `None` for private (anonymous) backings.
69    mappable: Option<Mappable>,
70    /// GPA ranges covered by this backing.
71    ranges: Vec<MemoryRange>,
72    /// Prefetch pages at build time.
73    prefetch: bool,
74    /// THP is enabled for this backing.
75    transparent_hugepages: bool,
76    /// Host NUMA node for this backing. `None` means OS default placement.
77    host_numa_node: Option<u32>,
78}
79
80#[derive(Debug)]
81struct RamRegion {
82    range: MemoryRange,
83    handle: RegionHandle,
84}
85
86/// Errors when attaching a partition to a [`GuestMemoryManager`].
87#[derive(Error, Debug)]
88pub enum PartitionAttachError {
89    /// Failure to allocate a VA mapper.
90    #[error("failed to reserve VA range for partition mapping")]
91    VaMapper(#[source] VaMapperError),
92    /// Failure to map memory into a partition.
93    #[error("failed to attach partition to memory manager")]
94    PartitionMapper(#[source] crate::partition_mapper::PartitionMapperError),
95}
96
97/// Errors creating a [`GuestMemoryManager`].
98#[derive(Error, Debug)]
99pub enum MemoryBuildError {
100    /// RAM too large.
101    #[error("ram size {0} is too large")]
102    RamTooLarge(MemorySize),
103    /// Couldn't allocate RAM.
104    #[error("failed to allocate memory")]
105    AllocationFailed(#[source] io::Error),
106    /// Couldn't allocate hugetlb-backed RAM.
107    #[error(
108        "failed to reserve {page_count} hugetlb pages of {hugepage_size} each ({size} total); increase the hugetlb pool or reduce guest memory size"
109    )]
110    HugepageAllocationFailed {
111        /// Total RAM backing size.
112        size: MemorySize,
113        /// Requested or default hugepage size.
114        hugepage_size: MemorySize,
115        /// Number of hugepages required.
116        page_count: usize,
117        /// The allocation error.
118        #[source]
119        error: io::Error,
120    },
121    /// Couldn't allocate VA mapper.
122    #[error("failed to create VA mapper")]
123    VaMapper(#[source] VaMapperError),
124    /// Failed to map RAM into VA space.
125    #[error("failed to map RAM range {range}")]
126    RamMapping {
127        /// The GPA range that failed to map.
128        range: MemoryRange,
129        /// The mapping error.
130        #[source]
131        error: mesh::error::RemoteError,
132    },
133    /// Failed to enable RAM region.
134    #[error("failed to enable RAM region {range}")]
135    RamRegionEnable {
136        /// The GPA range that failed.
137        range: MemoryRange,
138        /// The error.
139        #[source]
140        error: mesh::error::RemoteError,
141    },
142    /// Memory layout incompatible with VTL0 alias map.
143    #[error("not enough guest address space available for the vtl0 alias map")]
144    AliasMapWontFit,
145    /// Memory layout incompatible with x86 legacy support.
146    #[error("x86 support requires RAM to start at 0 and contain at least 1MB")]
147    InvalidRamForX86,
148    /// Private memory is incompatible with x86 legacy support.
149    #[error("private memory is incompatible with x86 legacy support")]
150    PrivateMemoryWithLegacy,
151    /// Private memory is incompatible with an existing memory backing.
152    #[error("private memory is incompatible with an existing memory backing")]
153    PrivateMemoryWithExistingBacking,
154    /// Hugepage size is too large.
155    #[error("hugepage size {0} is too large")]
156    HugepageSizeTooLarge(MemorySize),
157    /// Hugepages are only supported on Linux and Windows.
158    #[error("hugepages are only supported on Linux and Windows")]
159    HugepagesUnsupportedPlatform,
160    /// Host NUMA node binding is only supported on Linux and Windows.
161    #[error("host NUMA node binding is only supported on Linux and Windows")]
162    HostNumaNodeUnsupportedPlatform,
163    /// Hugepages require shared memory mode.
164    #[error("hugepages require shared memory mode")]
165    HugepagesWithPrivateMemory,
166    /// Hugepages are incompatible with existing memory backing.
167    #[error("hugepages are incompatible with existing memory backing")]
168    HugepagesWithExistingBacking,
169    /// Hugepages are incompatible with x86 legacy RAM splitting.
170    #[error("hugepages are incompatible with x86 legacy RAM splitting")]
171    HugepagesWithLegacy,
172    /// Invalid hugepage size.
173    #[error("hugepage size {0} must be a power of two and at least the host page size")]
174    InvalidHugepageSize(MemorySize),
175    /// RAM size is not aligned to the hugepage size.
176    #[error(
177        "RAM size {ram_size} is not aligned to {hugepage_size} hugepages; choose a memory size that is a multiple of the hugepage size"
178    )]
179    HugepageRamSizeUnaligned {
180        /// Total RAM backing size.
181        ram_size: MemorySize,
182        /// Required hugepage alignment.
183        hugepage_size: MemorySize,
184    },
185    /// A RAM range is not aligned to the hugepage size.
186    #[error(
187        "RAM range {range} ({range_size}) is not aligned to {hugepage_size} hugepages; range start and size must both be multiples of the hugepage size"
188    )]
189    HugepageRamRangeUnaligned {
190        /// The unaligned RAM range.
191        range: MemoryRange,
192        /// The RAM range size.
193        range_size: MemorySize,
194        /// Required hugepage alignment.
195        hugepage_size: MemorySize,
196    },
197}
198
199const DEFAULT_HUGEPAGE_SIZE: u64 = 2 * 1024 * 1024;
200
201/// A request to allocate one RAM backing region (one memfd or anonymous
202/// allocation). For non-NUMA VMs, a single request covers all RAM. For
203/// NUMA VMs, one request per node with memory.
204///
205/// Construct via [`RamBackingRequest::new`].
206#[derive(Debug)]
207pub struct RamBackingRequest {
208    ranges: Vec<MemoryRange>,
209    prefetch: bool,
210    private_memory: bool,
211    transparent_hugepages: bool,
212    hugepages: bool,
213    hugepage_size: Option<u64>,
214    existing_mappable: Option<Mappable>,
215    host_numa_node: Option<u32>,
216}
217
218impl RamBackingRequest {
219    /// Creates a new backing request covering the given GPA ranges.
220    ///
221    /// The backing's allocation size is the sum of the range lengths.
222    /// Defaults to shared file-backed memory with no prefetch.
223    pub fn new(ranges: Vec<MemoryRange>) -> Self {
224        Self {
225            ranges,
226            prefetch: false,
227            private_memory: false,
228            transparent_hugepages: false,
229            hugepages: false,
230            hugepage_size: None,
231            existing_mappable: None,
232            host_numa_node: None,
233        }
234    }
235
236    /// Prefetch (pre-fault) all pages at build time.
237    pub fn prefetch(mut self, enable: bool) -> Self {
238        self.prefetch = enable;
239        self
240    }
241
242    /// Use private anonymous memory instead of shared file-backed memory.
243    pub fn private_memory(mut self, enable: bool) -> Self {
244        self.private_memory = enable;
245        self
246    }
247
248    /// Enable Transparent Huge Pages for guest RAM (Linux only, best-effort).
249    ///
250    /// Applies to both shared (memfd) and private (anonymous) backings. The
251    /// kernel treats `madvise(MADV_HUGEPAGE)` as advisory and may accept it
252    /// without allocating huge pages. Advice failures are logged but do not
253    /// fail the build. This has no effect on non-Linux hosts or explicit
254    /// hugetlb (`hugepages`) backings, which are already huge.
255    pub fn transparent_hugepages(mut self, enable: bool) -> Self {
256        self.transparent_hugepages = enable;
257        self
258    }
259
260    /// Enable explicit hugetlb memfd backing with an optional size
261    /// override (default: 2 MB). Incompatible with `private_memory`.
262    pub fn hugepages(mut self, size: Option<u64>) -> Self {
263        self.hugepages = true;
264        self.hugepage_size = size;
265        self
266    }
267
268    /// Reuse an existing file-backed memory handle (restore path).
269    /// When set, no new allocation is performed for this backing.
270    pub fn existing_mappable(mut self, mappable: Mappable) -> Self {
271        self.existing_mappable = Some(mappable);
272        self
273    }
274
275    /// Bind this backing's memory to a specific host NUMA node
276    /// (Linux: `mbind(MPOL_BIND)`, Windows: `CreateFileMappingNuma` for
277    /// large-page sections and `MemExtendedParameterNumaNode` otherwise).
278    ///
279    /// Only supported on Linux and Windows; returns
280    /// [`MemoryBuildError::HostNumaNodeUnsupportedPlatform`] at build time on
281    /// other targets.
282    pub fn host_numa_node(mut self, node: Option<u32>) -> Self {
283        self.host_numa_node = node;
284        self
285    }
286}
287
288fn validate_hugepage_size(size: u64) -> Result<usize, MemoryBuildError> {
289    if !size.is_power_of_two() || size < SparseMapping::page_size() as u64 {
290        return Err(MemoryBuildError::InvalidHugepageSize(MemorySize(size)));
291    }
292    size.try_into()
293        .map_err(|_| MemoryBuildError::HugepageSizeTooLarge(MemorySize(size)))
294}
295
296/// A byte count displayed in a human-readable format in error messages.
297#[derive(Debug, Copy, Clone)]
298pub struct MemorySize(
299    /// The size in bytes.
300    pub u64,
301);
302
303impl std::fmt::Display for MemorySize {
304    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
305        const KB: u64 = 1024;
306        const MB: u64 = 1024 * KB;
307        const GB: u64 = 1024 * MB;
308        const TB: u64 = 1024 * GB;
309
310        for (unit, suffix) in [(TB, "TB"), (GB, "GB"), (MB, "MB"), (KB, "KB")] {
311            if self.0 != 0 && self.0.is_multiple_of(unit) {
312                return write!(f, "{} {suffix}", self.0 / unit);
313            }
314        }
315
316        write!(f, "{} bytes", self.0)
317    }
318}
319
320fn validate_hugepage_ram_alignment(
321    ram_size: u64,
322    ram_ranges: &[MemoryRange],
323    hugepage_size: u64,
324) -> Result<(), MemoryBuildError> {
325    if !ram_size.is_multiple_of(hugepage_size) {
326        return Err(MemoryBuildError::HugepageRamSizeUnaligned {
327            ram_size: MemorySize(ram_size),
328            hugepage_size: MemorySize(hugepage_size),
329        });
330    }
331    for &range in ram_ranges {
332        if !range.start().is_multiple_of(hugepage_size)
333            || !range.len().is_multiple_of(hugepage_size)
334        {
335            return Err(MemoryBuildError::HugepageRamRangeUnaligned {
336                range,
337                range_size: MemorySize(range.len()),
338                hugepage_size: MemorySize(hugepage_size),
339            });
340        }
341    }
342    Ok(())
343}
344
345/// A builder for [`GuestMemoryManager`].
346pub struct GuestMemoryBuilder {
347    vtl0_alias_map: Option<u64>,
348    pin_mappings: bool,
349    x86_legacy_support: bool,
350    supports_memory_fault_resolution: bool,
351    backing_requests: Vec<RamBackingRequest>,
352}
353
354impl GuestMemoryBuilder {
355    /// Returns a new builder.
356    pub fn new() -> Self {
357        Self {
358            vtl0_alias_map: None,
359            pin_mappings: false,
360            x86_legacy_support: false,
361            supports_memory_fault_resolution: false,
362            backing_requests: Vec::new(),
363        }
364    }
365
366    /// Specifies the offset of the VTL0 alias map, if enabled for VTL2. This is
367    /// a mirror of VTL0 memory into a high portion of the VM's physical address
368    /// space.
369    pub fn vtl0_alias_map(mut self, offset: Option<u64>) -> Self {
370        self.vtl0_alias_map = offset;
371        self
372    }
373
374    /// Specify whether to pin mappings in memory. This is used to support
375    /// device assignment for devices that require the IOMMU to be programmed
376    /// for all addresses.
377    pub fn pin_mappings(mut self, enable: bool) -> Self {
378        self.pin_mappings = enable;
379        self
380    }
381
382    /// Enables legacy x86 support.
383    ///
384    /// When set, create separate RAM regions for the various low memory ranges
385    /// that are special on x86 platforms. Specifically:
386    ///
387    /// 1. Create a separate RAM region for the VGA VRAM window:
388    ///    0xa0000-0xbffff.
389    /// 2. Create separate RAM regions within 0xc0000-0xfffff for control by PAM
390    ///    registers.
391    ///
392    /// The caller can use [`RamVisibilityControl`] to adjust the visibility of
393    /// these ranges.
394    pub fn x86_legacy_support(mut self, enable: bool) -> Self {
395        self.x86_legacy_support = enable;
396        self
397    }
398
399    /// Records whether the partition delivers guest-memory-access faults to the
400    /// VMM, enabling on-demand fault resolution (soft large pages, lazy commit)
401    /// via [`GuestMemoryManager::memory_fault_resolver`].
402    pub fn supports_memory_fault_resolution(mut self, enable: bool) -> Self {
403        self.supports_memory_fault_resolution = enable;
404        self
405    }
406
407    /// Adds a RAM backing request. Call once per backing (one per NUMA node,
408    /// or once for a non-NUMA VM).
409    pub fn add_backing(mut self, request: RamBackingRequest) -> Self {
410        self.backing_requests.push(request);
411        self
412    }
413
414    /// Builds the memory backing, allocating one memfd or anonymous region
415    /// per backing request.
416    ///
417    /// Each [`RamBackingRequest`] produces one RAM backing. File-backed
418    /// requests allocate a memfd (or reuse `existing_mappable` if set);
419    /// private requests use anonymous pages.
420    pub async fn build(self, max_addr: u64) -> Result<GuestMemoryManager, MemoryBuildError> {
421        let backing_requests = self.backing_requests;
422
423        // Validate per-request constraints.
424        for req in &backing_requests {
425            if req.private_memory && self.x86_legacy_support {
426                return Err(MemoryBuildError::PrivateMemoryWithLegacy);
427            }
428            if req.private_memory && req.existing_mappable.is_some() {
429                return Err(MemoryBuildError::PrivateMemoryWithExistingBacking);
430            }
431            if req.host_numa_node.is_some()
432                && cfg!(not(any(target_os = "linux", target_os = "windows")))
433            {
434                return Err(MemoryBuildError::HostNumaNodeUnsupportedPlatform);
435            }
436            if req.hugepages {
437                if !cfg!(any(target_os = "linux", target_os = "windows")) {
438                    return Err(MemoryBuildError::HugepagesUnsupportedPlatform);
439                }
440                if req.private_memory {
441                    return Err(MemoryBuildError::HugepagesWithPrivateMemory);
442                }
443                if req.existing_mappable.is_some() {
444                    return Err(MemoryBuildError::HugepagesWithExistingBacking);
445                }
446                if self.x86_legacy_support {
447                    return Err(MemoryBuildError::HugepagesWithLegacy);
448                }
449            }
450        }
451
452        // Validate x86 legacy support: at least one backing must contain a
453        // range starting at GPA 0 and covering at least 1MB.
454        if self.x86_legacy_support {
455            let has_low_mem = backing_requests.iter().any(|req| {
456                req.ranges
457                    .iter()
458                    .any(|r| r.start() == 0 && r.end() >= 0x100000)
459            });
460            if !has_low_mem {
461                return Err(MemoryBuildError::InvalidRamForX86);
462            }
463        }
464
465        // Compute the maximum hugepage size across all backings (used for
466        // VA alignment in the MappingManager).
467        let max_hugepage_size = {
468            let mut max: Option<usize> = None;
469            for req in &backing_requests {
470                if req.hugepages {
471                    let size =
472                        validate_hugepage_size(req.hugepage_size.unwrap_or(DEFAULT_HUGEPAGE_SIZE))?;
473                    max = Some(max.map_or(size, |m: usize| m.max(size)));
474                }
475            }
476            max
477        };
478
479        // Allocate per-backing memory.
480        let num_backings = backing_requests.len();
481        let mut backings = Vec::with_capacity(num_backings);
482        for (i, req) in backing_requests.into_iter().enumerate() {
483            let size: u64 = req.ranges.iter().map(|r| r.len()).sum();
484
485            if req.private_memory {
486                backings.push(RamBacking {
487                    mappable: None,
488                    ranges: req.ranges,
489                    prefetch: req.prefetch,
490                    transparent_hugepages: req.transparent_hugepages,
491                    host_numa_node: req.host_numa_node,
492                });
493                continue;
494            }
495
496            // Shared (file-backed) backing: reuse existing or allocate fresh.
497            let mappable = if let Some(existing) = req.existing_mappable {
498                existing
499            } else {
500                let backing_size: usize = size
501                    .try_into()
502                    .map_err(|_| MemoryBuildError::RamTooLarge(MemorySize(size)))?;
503                let name = if num_backings == 1 {
504                    "guest-ram".into()
505                } else {
506                    format!("guest-ram-{i}")
507                };
508                if req.hugepages {
509                    let hugepage_size =
510                        validate_hugepage_size(req.hugepage_size.unwrap_or(DEFAULT_HUGEPAGE_SIZE))?;
511                    validate_hugepage_ram_alignment(size, &req.ranges, hugepage_size as u64)?;
512                    // TODO: on Windows, when this large-page (SEC_LARGE_PAGES)
513                    // section is later mapped into the guest VA, we should
514                    // really map it with MEM_LARGE_PAGES so the view itself
515                    // uses large pages. Released versions of Windows don't
516                    // support MEM_LARGE_PAGES together with the placeholder
517                    // reservations that sparse_mmap relies on, so we leave it
518                    // out for now.
519                    sparse_mmap::alloc_shared_memory_hugetlb(
520                        backing_size,
521                        &name,
522                        Some(hugepage_size),
523                        req.host_numa_node,
524                    )
525                    .map_err(|error| MemoryBuildError::HugepageAllocationFailed {
526                        size: MemorySize(size),
527                        hugepage_size: MemorySize(hugepage_size as u64),
528                        page_count: backing_size / hugepage_size,
529                        error,
530                    })?
531                    .into()
532                } else {
533                    sparse_mmap::alloc_shared_memory(backing_size, &name)
534                        .map_err(MemoryBuildError::AllocationFailed)?
535                        .into()
536                }
537            };
538
539            backings.push(RamBacking {
540                mappable: Some(mappable),
541                ranges: req.ranges,
542                // On Windows, hugepage (SEC_LARGE_PAGES) backing only yields 2 MB
543                // SLAT entries when the SLAT is populated in >= 512-page batches;
544                // lazy per-page demand faults produce 4 KB entries. Prefetching
545                // populates each region up front in large contiguous batches, so
546                // force it on for hugepage-backed RAM. (Linux hugetlb faults the
547                // whole large page on first touch, so this is not needed there.)
548                prefetch: req.prefetch || (cfg!(windows) && req.hugepages),
549                // Transparent huge pages are advisory and best-effort; they
550                // apply to shmem (memfd) mappings on Linux. Explicit hugetlb
551                // backings are already huge, so suppress THP there.
552                transparent_hugepages: req.transparent_hugepages && !req.hugepages,
553                host_numa_node: req.host_numa_node,
554            });
555        }
556
557        // Spawn a thread to handle memory requests.
558        //
559        // FUTURE: move this to a task once the GuestMemory deadlocks are resolved.
560        let (thread, spawner) = DefaultPool::spawn_on_thread("memory_manager");
561
562        let vtl0_alias_map_offset = if let Some(offset) = self.vtl0_alias_map {
563            if max_addr > offset {
564                return Err(MemoryBuildError::AliasMapWontFit);
565            }
566            Some(offset)
567        } else {
568            None
569        };
570
571        // The primary mapper is created as part of `MappingManager::new`: it is
572        // the loader's target and the partition's fault resolver.
573        let (mapping_manager, va_mapper) = MappingManager::new(
574            &spawner,
575            max_addr,
576            max_hugepage_size,
577            self.supports_memory_fault_resolution,
578        )
579        .await
580        .map_err(MemoryBuildError::VaMapper)?;
581
582        let region_manager = RegionManager::new(&spawner, mapping_manager.client().clone());
583
584        // Build RAM regions from each backing's ranges.
585        let mut ram_regions = Vec::new();
586        for backing in &backings {
587            let mut file_offset = 0u64;
588            for range in &backing.ranges {
589                // Split for x86 legacy PAM/VGA regions if needed.
590                let sub_ranges =
591                    if self.x86_legacy_support && range.start() == 0 && range.end() >= 0x100000 {
592                        let range_end = range.end();
593                        let range_starts = [
594                            0u64, 0xa0000, 0xc0000, 0xc4000, 0xc8000, 0xcc000, 0xd0000, 0xd4000,
595                            0xd8000, 0xdc000, 0xe0000, 0xe4000, 0xe8000, 0xec000, 0xf0000,
596                            0x100000, range_end,
597                        ];
598                        range_starts
599                            .iter()
600                            .zip(range_starts.iter().skip(1))
601                            .map(|(&s, &e)| MemoryRange::new(s..e))
602                            .collect::<Vec<_>>()
603                    } else {
604                        vec![*range]
605                    };
606
607                for sub_range in &sub_ranges {
608                    let region = region_manager
609                        .client()
610                        .new_region(
611                            "ram".into(),
612                            *sub_range,
613                            RAM_PRIORITY,
614                            crate::region_manager::MappingType::Ram,
615                        )
616                        .await
617                        .expect("regions cannot overlap yet");
618
619                    // Register the mapping with the region. File-backed RAM
620                    // passes its `Mappable` so the mapping manager mmaps it.
621                    // Private/anonymous RAM passes `MappingBacking::Private`:
622                    // the mapping manager commits its anonymous pages directly
623                    // (there is no fd to mmap), but it still participates in the
624                    // region-driven DMA machinery (mapped by host VA). Without
625                    // this, an assigned device DMAing to private RAM would take
626                    // IOMMU faults (silent DMA failure).
627                    let backing_kind = match &backing.mappable {
628                        Some(mappable) => MappingBacking::File {
629                            mappable: mappable.clone(),
630                            file_offset,
631                        },
632                        None => MappingBacking::Private,
633                    };
634                    region
635                        .add_mapping(
636                            MemoryRange::new(0..sub_range.len()),
637                            backing_kind,
638                            true,
639                            MemoryPolicy {
640                                numa_node: backing.host_numa_node,
641                                transparent_hugepages: backing.transparent_hugepages,
642                                prefetch: backing.prefetch,
643                            },
644                        )
645                        .await
646                        .map_err(|error| MemoryBuildError::RamMapping {
647                            range: *sub_range,
648                            error,
649                        })?;
650
651                    region
652                        .map(MapParams {
653                            writable: true,
654                            executable: true,
655                            prefetch: backing.prefetch,
656                        })
657                        .await
658                        .map_err(|error| MemoryBuildError::RamRegionEnable {
659                            range: *sub_range,
660                            error,
661                        })?;
662
663                    ram_regions.push(RamRegion {
664                        range: *sub_range,
665                        handle: region,
666                    });
667                    file_offset += sub_range.len();
668                }
669            }
670        }
671
672        let gm = GuestMemoryManager {
673            guest_ram: backings,
674            _thread: thread,
675            ram_regions: Arc::new(ram_regions),
676            mapping_manager,
677            region_manager,
678            va_mapper,
679            vtl0_alias_map_offset,
680            pin_mappings: self.pin_mappings,
681            supports_memory_fault_resolution: self.supports_memory_fault_resolution,
682        };
683        Ok(gm)
684    }
685}
686
687/// The backing objects used to transfer guest memory between processes.
688#[derive(Debug, MeshPayload)]
689pub struct SharedMemoryBacking {
690    guest_ram: Mappable,
691}
692
693impl SharedMemoryBacking {
694    /// Create a SharedMemoryBacking from a mappable handle/fd.
695    pub fn from_mappable(guest_ram: Mappable) -> Self {
696        Self { guest_ram }
697    }
698
699    /// Returns the mappable, consuming this backing.
700    pub fn into_mappable(self) -> Mappable {
701        self.guest_ram
702    }
703}
704
705/// A mesh-serializable object for providing access to guest memory.
706#[derive(Debug, MeshPayload)]
707pub struct GuestMemoryClient {
708    mapping_manager: MappingManagerClient,
709}
710
711impl GuestMemoryClient {
712    /// Retrieves a [`GuestMemory`] object to access guest memory from this
713    /// process.
714    ///
715    /// This call will ensure only one VA mapper is allocated per process, so
716    /// this is safe to call many times without allocating tons of virtual
717    /// address space.
718    pub async fn guest_memory(&self) -> Result<GuestMemory, VaMapperError> {
719        Ok(GuestMemory::new(
720            "ram",
721            self.mapping_manager.new_mapper(false).await?,
722        ))
723    }
724}
725
726// The region priority for RAM. Overrides anything else.
727const RAM_PRIORITY: u8 = 255;
728
729// The region priority for device memory.
730const DEVICE_PRIORITY: u8 = 0;
731
732impl GuestMemoryManager {
733    /// Returns an object to access guest memory.
734    pub fn client(&self) -> GuestMemoryClient {
735        GuestMemoryClient {
736            mapping_manager: self.mapping_manager.client().clone(),
737        }
738    }
739
740    /// Returns a resolver that prepares guest-memory backing on demand in
741    /// response to partition memory-access faults (soft large pages, lazy
742    /// commit).
743    ///
744    /// Intended for backends that report
745    /// [`virt::ProtoPartition::supports_memory_fault_resolution`]; supply the
746    /// returned resolver via [`virt::PartitionConfig::fault_resolver`].
747    pub fn memory_fault_resolver(&self) -> Arc<dyn virt::ResolveMemoryFault> {
748        self.va_mapper.clone()
749    }
750
751    /// Returns an object to map device memory into the VM.
752    pub fn device_memory_mapper(&self) -> DeviceMemoryMapper {
753        DeviceMemoryMapper::new(self.region_manager.client().clone())
754    }
755
756    /// Returns a client for registering DMA mappers (VFIO, iommufd).
757    pub fn dma_mapper_client(&self) -> crate::region_manager::DmaMapperClient {
758        crate::region_manager::DmaMapperClient::new(self.region_manager.client())
759    }
760
761    /// Returns an object for manipulating the visibility state of different RAM
762    /// regions.
763    pub fn ram_visibility_control(&self) -> RamVisibilityControl {
764        RamVisibilityControl {
765            regions: self.ram_regions.clone(),
766        }
767    }
768
769    /// Returns the shared memory resources that can be used to reconstruct the
770    /// memory backing.
771    ///
772    /// The returned mappable can be passed back via
773    /// [`RamBackingRequest::existing_mappable`] to create a new memory
774    /// manager with the same memory state. Only one instance of this type
775    /// should be managing a given memory backing at a time, though, or the
776    /// guest may see unpredictable results.
777    ///
778    /// Returns `None` unless there is exactly one backing and it is
779    /// file-backed. This currently means multi-backing and private-memory
780    /// configurations cannot be restarted.
781    pub fn shared_memory_backing(&self) -> Option<SharedMemoryBacking> {
782        // Require exactly one backing, and it must be file-backed.
783        if self.guest_ram.len() != 1 {
784            return None;
785        }
786        Some(SharedMemoryBacking {
787            guest_ram: self.guest_ram[0].mappable.clone()?,
788        })
789    }
790
791    /// Attaches the guest memory to a partition, mapping it to the guest
792    /// physical address space.
793    ///
794    /// If `process` is provided, then allocate a VA range in that process for
795    /// the guest memory, and map the memory into the partition from that
796    /// process. This is necessary to work around WHP's lack of support for
797    /// mapping multiple partitions from a single process.
798    ///
799    /// TODO: currently, all VTLs will get the same mappings--no support for
800    /// per-VTL memory protections is supported.
801    pub async fn attach_partition(
802        &mut self,
803        vtl: Vtl,
804        partition: &Arc<dyn virt::PartitionMemoryMap>,
805        process: Option<RemoteProcess>,
806    ) -> Result<(), PartitionAttachError> {
807        let va_mapper = if let Some(process) = process {
808            self.mapping_manager
809                .client()
810                .new_remote_mapper(process)
811                .await
812                .map_err(PartitionAttachError::VaMapper)?
813        } else {
814            self.va_mapper.clone()
815        };
816
817        if vtl == Vtl::Vtl2 {
818            if let Some(offset) = self.vtl0_alias_map_offset {
819                let partition =
820                    PartitionMapper::new(partition, va_mapper.clone(), offset, self.pin_mappings);
821                self.region_manager
822                    .client()
823                    .add_partition(partition)
824                    .await
825                    .map_err(PartitionAttachError::PartitionMapper)?;
826            }
827        }
828
829        let partition = PartitionMapper::new(partition, va_mapper, 0, self.pin_mappings);
830        self.region_manager
831            .client()
832            .add_partition(partition)
833            .await
834            .map_err(PartitionAttachError::PartitionMapper)?;
835        Ok(())
836    }
837}
838
839/// A client to the [`GuestMemoryManager`] used to control the visibility of
840/// RAM regions.
841#[derive(Clone)]
842pub struct RamVisibilityControl {
843    regions: Arc<Vec<RamRegion>>,
844}
845
846/// The RAM visibility for use with [`RamVisibilityControl::set_ram_visibility`].
847#[derive(Debug, Copy, Clone, PartialEq, Eq)]
848pub enum RamVisibility {
849    /// RAM is unmapped, so reads and writes will go to device memory or MMIO.
850    Unmapped,
851    /// RAM is read-only. Writes will go to device memory or MMIO.
852    ///
853    /// Note that writes will take exits even if there is mapped device memory.
854    ReadOnly,
855    /// RAM is read-write by the guest.
856    ReadWrite,
857}
858
859/// An error returned by [`RamVisibilityControl::set_ram_visibility`].
860#[derive(Debug, Error)]
861pub enum RamVisibilityError {
862    /// The range is not a controllable RAM region.
863    #[error("{0} is not a controllable RAM range")]
864    InvalidRange(MemoryRange),
865    /// Failed to map the region.
866    #[error("failed to map RAM range {range}")]
867    Map {
868        /// The range that failed.
869        range: MemoryRange,
870        /// The error.
871        #[source]
872        error: mesh::error::RemoteError,
873    },
874}
875
876impl RamVisibilityControl {
877    /// Sets the visibility of a RAM region.
878    ///
879    /// A whole region's visibility must be controlled at once, or an error will
880    /// be returned. [`GuestMemoryBuilder::x86_legacy_support`] can be used to
881    /// ensure that there are RAM regions corresponding to x86 memory ranges
882    /// that need to be controlled.
883    pub async fn set_ram_visibility(
884        &self,
885        range: MemoryRange,
886        visibility: RamVisibility,
887    ) -> Result<(), RamVisibilityError> {
888        let region = self
889            .regions
890            .iter()
891            .find(|region| region.range == range)
892            .ok_or(RamVisibilityError::InvalidRange(range))?;
893
894        match visibility {
895            RamVisibility::ReadWrite | RamVisibility::ReadOnly => {
896                region
897                    .handle
898                    .map(MapParams {
899                        writable: matches!(visibility, RamVisibility::ReadWrite),
900                        executable: true,
901                        prefetch: false,
902                    })
903                    .await
904                    .map_err(|error| RamVisibilityError::Map { range, error })?;
905            }
906            RamVisibility::Unmapped => region.handle.unmap().await,
907        }
908        Ok(())
909    }
910}
911
912#[cfg(test)]
913mod tests {
914    use super::*;
915    use pal_async::async_test;
916    use std::error::Error as _;
917
918    /// Build a GuestMemoryManager with the given backing range groups,
919    /// and return a GuestMemory handle for read/write testing.
920    async fn build_and_get_memory(
921        backing_ranges: &[&[MemoryRange]],
922    ) -> (GuestMemoryManager, GuestMemory) {
923        let max_addr = backing_ranges
924            .iter()
925            .flat_map(|ranges| ranges.iter())
926            .map(|r| r.end())
927            .max()
928            .unwrap_or(0);
929
930        let mut builder = GuestMemoryBuilder::new();
931        for ranges in backing_ranges {
932            builder = builder.add_backing(RamBackingRequest::new(ranges.to_vec()));
933        }
934        let mgr = builder.build(max_addr).await.unwrap();
935        let gm = mgr.client().guest_memory().await.unwrap();
936        (mgr, gm)
937    }
938
939    #[async_test]
940    async fn test_hugepages_with_existing_backing_rejected() {
941        const SIZE: u64 = 2 * 1024 * 1024;
942        let mappable = sparse_mmap::alloc_shared_memory(SIZE as usize, "test").unwrap();
943        let backing = RamBackingRequest::new(vec![MemoryRange::new(0..SIZE)])
944            .hugepages(None)
945            .existing_mappable(mappable.into());
946        let err = GuestMemoryBuilder::new()
947            .add_backing(backing)
948            .build(SIZE)
949            .await
950            .unwrap_err();
951        assert!(matches!(
952            err,
953            MemoryBuildError::HugepagesWithExistingBacking
954        ));
955    }
956
957    #[test]
958    fn test_validate_hugepage_size() {
959        let page_size = SparseMapping::page_size() as u64;
960        assert!(validate_hugepage_size(page_size).is_ok());
961        assert!(matches!(
962            validate_hugepage_size(page_size / 2),
963            Err(MemoryBuildError::InvalidHugepageSize(_))
964        ));
965        assert!(matches!(
966            validate_hugepage_size(3 * 1024 * 1024),
967            Err(MemoryBuildError::InvalidHugepageSize(_))
968        ));
969    }
970
971    #[test]
972    fn test_validate_hugepage_ram_alignment() {
973        const HUGEPAGE_SIZE: u64 = 2 * 1024 * 1024;
974
975        validate_hugepage_ram_alignment(
976            4 * 1024 * 1024,
977            &[
978                MemoryRange::new(0..HUGEPAGE_SIZE),
979                MemoryRange::new(2 * HUGEPAGE_SIZE..3 * HUGEPAGE_SIZE),
980            ],
981            HUGEPAGE_SIZE,
982        )
983        .unwrap();
984
985        assert!(matches!(
986            validate_hugepage_ram_alignment(3 * 1024 * 1024, &[], HUGEPAGE_SIZE),
987            Err(MemoryBuildError::HugepageRamSizeUnaligned { .. })
988        ));
989        assert!(matches!(
990            validate_hugepage_ram_alignment(
991                HUGEPAGE_SIZE,
992                &[MemoryRange::new(0..1024 * 1024)],
993                HUGEPAGE_SIZE,
994            ),
995            Err(MemoryBuildError::HugepageRamRangeUnaligned { .. })
996        ));
997    }
998
999    #[test]
1000    fn test_hugepage_ram_size_alignment_error_message() {
1001        let error =
1002            validate_hugepage_ram_alignment(257 * 1024 * 1024, &[], 2 * 1024 * 1024).unwrap_err();
1003
1004        assert_eq!(
1005            error.to_string(),
1006            "RAM size 257 MB is not aligned to 2 MB hugepages; choose a memory size that is a multiple of the hugepage size"
1007        );
1008    }
1009
1010    #[test]
1011    fn test_hugepage_ram_range_alignment_error_message() {
1012        let error = validate_hugepage_ram_alignment(
1013            2 * 1024 * 1024,
1014            &[MemoryRange::new(0..1024 * 1024)],
1015            2 * 1024 * 1024,
1016        )
1017        .unwrap_err();
1018
1019        assert_eq!(
1020            error.to_string(),
1021            "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"
1022        );
1023    }
1024
1025    #[test]
1026    fn test_hugepage_allocation_error_message() {
1027        let error = MemoryBuildError::HugepageAllocationFailed {
1028            size: MemorySize(1024 * 1024 * 1024),
1029            hugepage_size: MemorySize(2 * 1024 * 1024),
1030            page_count: 512,
1031            error: io::Error::new(io::ErrorKind::OutOfMemory, "Cannot allocate memory"),
1032        };
1033
1034        assert_eq!(
1035            error.to_string(),
1036            "failed to reserve 512 hugetlb pages of 2 MB each (1 GB total); increase the hugetlb pool or reduce guest memory size"
1037        );
1038        assert_eq!(
1039            error.source().unwrap().to_string(),
1040            "Cannot allocate memory"
1041        );
1042    }
1043
1044    #[test]
1045    fn test_single_backing() {
1046        DefaultPool::run_with(|_| async {
1047            let page = SparseMapping::page_size() as u64;
1048            let r = MemoryRange::new(0..4 * page);
1049            let (_mgr, gm) = build_and_get_memory(&[&[r]]).await;
1050
1051            let pattern = vec![0xAB; page as usize];
1052            gm.write_at(0, &pattern).unwrap();
1053            let mut buf = vec![0u8; page as usize];
1054            gm.read_at(0, &mut buf).unwrap();
1055            assert_eq!(buf, pattern);
1056
1057            // Second page should be zeroed.
1058            gm.read_at(page, &mut buf).unwrap();
1059            assert_eq!(buf, vec![0u8; page as usize]);
1060        });
1061    }
1062
1063    #[test]
1064    fn test_two_backings() {
1065        DefaultPool::run_with(|_| async {
1066            let page = SparseMapping::page_size() as u64;
1067            let r0 = MemoryRange::new(0..2 * page);
1068            let r1 = MemoryRange::new(2 * page..4 * page);
1069            let (_mgr, gm) = build_and_get_memory(&[&[r0], &[r1]]).await;
1070
1071            // Write distinct patterns into each backing's region.
1072            let pattern_a = vec![0xAA; page as usize];
1073            let pattern_b = vec![0xBB; page as usize];
1074            gm.write_at(0, &pattern_a).unwrap();
1075            gm.write_at(2 * page, &pattern_b).unwrap();
1076
1077            let mut buf = vec![0u8; page as usize];
1078            gm.read_at(0, &mut buf).unwrap();
1079            assert_eq!(buf, pattern_a, "backing 0 should have pattern_a");
1080
1081            gm.read_at(2 * page, &mut buf).unwrap();
1082            assert_eq!(buf, pattern_b, "backing 1 should have pattern_b");
1083
1084            // Unwritten pages within each backing should be zeroed.
1085            gm.read_at(page, &mut buf).unwrap();
1086            assert_eq!(buf, vec![0u8; page as usize]);
1087            gm.read_at(3 * page, &mut buf).unwrap();
1088            assert_eq!(buf, vec![0u8; page as usize]);
1089        });
1090    }
1091
1092    #[test]
1093    fn test_two_backings_different_sizes() {
1094        DefaultPool::run_with(|_| async {
1095            let page = SparseMapping::page_size() as u64;
1096            let r0 = MemoryRange::new(0..page);
1097            let r1 = MemoryRange::new(page..4 * page);
1098            let (_mgr, gm) = build_and_get_memory(&[&[r0], &[r1]]).await;
1099
1100            let pattern_a = vec![0x11; page as usize];
1101            let pattern_b = vec![0x22; page as usize];
1102            gm.write_at(0, &pattern_a).unwrap();
1103            gm.write_at(page, &pattern_b).unwrap();
1104
1105            let mut buf = vec![0u8; page as usize];
1106            gm.read_at(0, &mut buf).unwrap();
1107            assert_eq!(buf, pattern_a);
1108            gm.read_at(page, &mut buf).unwrap();
1109            assert_eq!(buf, pattern_b);
1110
1111            // Last page of backing 1.
1112            let pattern_c = vec![0x33; page as usize];
1113            gm.write_at(3 * page, &pattern_c).unwrap();
1114            gm.read_at(3 * page, &mut buf).unwrap();
1115            assert_eq!(buf, pattern_c);
1116
1117            // Middle page of backing 1 should be zeroed.
1118            gm.read_at(2 * page, &mut buf).unwrap();
1119            assert_eq!(buf, vec![0u8; page as usize]);
1120        });
1121    }
1122
1123    #[test]
1124    fn test_two_backings_with_gap() {
1125        DefaultPool::run_with(|_| async {
1126            let page = SparseMapping::page_size() as u64;
1127            let r0 = MemoryRange::new(0..2 * page);
1128            let r1 = MemoryRange::new(4 * page..6 * page);
1129
1130            let mgr = GuestMemoryBuilder::new()
1131                .add_backing(RamBackingRequest::new(vec![r0]))
1132                .add_backing(RamBackingRequest::new(vec![r1]))
1133                .build(r1.end())
1134                .await
1135                .unwrap();
1136            let gm = mgr.client().guest_memory().await.unwrap();
1137
1138            let pattern_a = vec![0xCC; page as usize];
1139            let pattern_b = vec![0xDD; page as usize];
1140            gm.write_at(0, &pattern_a).unwrap();
1141            gm.write_at(4 * page, &pattern_b).unwrap();
1142
1143            let mut buf = vec![0u8; page as usize];
1144            gm.read_at(0, &mut buf).unwrap();
1145            assert_eq!(buf, pattern_a);
1146            gm.read_at(4 * page, &mut buf).unwrap();
1147            assert_eq!(buf, pattern_b);
1148        });
1149    }
1150
1151    /// Builds a manager with a single THP-enabled shared RAM backing and
1152    /// returns a [`GuestMemory`] over the **primary** mapper. Soft large pages
1153    /// (the Windows deferred-protect scheme that maps guest RAM read-only until
1154    /// the first write) apply only to the primary mapper, so locking behavior
1155    /// must be exercised through it rather than through
1156    /// [`GuestMemoryClient::guest_memory`], which hands out a secondary mapper.
1157    async fn build_thp_primary_memory(size: u64) -> (GuestMemoryManager, GuestMemory) {
1158        let mgr = GuestMemoryBuilder::new()
1159            .add_backing(
1160                RamBackingRequest::new(vec![MemoryRange::new(0..size)]).transparent_hugepages(true),
1161            )
1162            .build(size)
1163            .await
1164            .unwrap();
1165        let primary = GuestMemory::new("test-primary", mgr.va_mapper.clone());
1166        (mgr, primary)
1167    }
1168
1169    /// Locking guest RAM for write must make it writable through the returned
1170    /// raw pointer, even when the backing is only lazily made writable on the
1171    /// first write (Windows soft large pages map primary-mapper guest RAM
1172    /// read-only until then). The write here goes through the locked pointer
1173    /// directly, bypassing the fault-handling `write_*` path, so if the lock
1174    /// had only faulted the page in for read the store would access-violate.
1175    /// This is the regression guard for read-only-locking a page that is then
1176    /// written via zero-copy DMA.
1177    #[async_test]
1178    async fn test_lock_for_write_makes_page_writable() {
1179        use std::sync::atomic::Ordering;
1180
1181        const SIZE: u64 = 2 * 1024 * 1024;
1182        let (_mgr, gm) = build_thp_primary_memory(SIZE).await;
1183
1184        let locked = gm
1185            .lock_gpns(guestmem::AccessType::Write, false, &[0])
1186            .unwrap();
1187        // Store directly through the locked pointer (not via `write_at`, which
1188        // would fault the page in on its own).
1189        locked.pages()[0][0].store(0xAB, Ordering::SeqCst);
1190        locked.pages()[0][1].store(0xCD, Ordering::SeqCst);
1191        drop(locked);
1192
1193        // The stores must be visible through a normal read.
1194        assert_eq!(gm.read_plain::<u8>(0).unwrap(), 0xAB);
1195        assert_eq!(gm.read_plain::<u8>(1).unwrap(), 0xCD);
1196    }
1197
1198    /// A read-only lock succeeds and reads back the freshly zeroed page without
1199    /// forcing the page writable.
1200    #[async_test]
1201    async fn test_lock_for_read_succeeds() {
1202        use std::sync::atomic::Ordering;
1203
1204        const SIZE: u64 = 2 * 1024 * 1024;
1205        let (_mgr, gm) = build_thp_primary_memory(SIZE).await;
1206
1207        let locked = gm
1208            .lock_gpns(guestmem::AccessType::Read, false, &[0])
1209            .unwrap();
1210        assert_eq!(locked.pages()[0][0].load(Ordering::SeqCst), 0);
1211        drop(locked);
1212    }
1213}