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    /// `host_access` configures fault-driven host access on the primary VTL0
800    /// mapper. It must be `None` when attaching any other VTL.
801    ///
802    /// TODO: currently, all VTLs will get the same mappings--no support for
803    /// per-VTL memory protections is supported.
804    pub async fn attach_partition(
805        &mut self,
806        vtl: Vtl,
807        partition: &Arc<dyn virt::PartitionMemoryMap>,
808        process: Option<RemoteProcess>,
809        host_access: Option<Arc<dyn virt::PartitionHostAccess>>,
810    ) -> Result<(), PartitionAttachError> {
811        if let Some(host_access) = host_access {
812            assert_eq!(
813                vtl,
814                Vtl::Vtl0,
815                "host access must be installed while attaching VTL0"
816            );
817            self.va_mapper.install_host_access(host_access);
818        }
819
820        let va_mapper = if let Some(process) = process {
821            self.mapping_manager
822                .client()
823                .new_remote_mapper(process)
824                .await
825                .map_err(PartitionAttachError::VaMapper)?
826        } else {
827            self.va_mapper.clone()
828        };
829
830        if vtl == Vtl::Vtl2 {
831            if let Some(offset) = self.vtl0_alias_map_offset {
832                let partition =
833                    PartitionMapper::new(partition, va_mapper.clone(), offset, self.pin_mappings);
834                self.region_manager
835                    .client()
836                    .add_partition(partition)
837                    .await
838                    .map_err(PartitionAttachError::PartitionMapper)?;
839            }
840        }
841
842        let partition = PartitionMapper::new(partition, va_mapper, 0, self.pin_mappings);
843        self.region_manager
844            .client()
845            .add_partition(partition)
846            .await
847            .map_err(PartitionAttachError::PartitionMapper)?;
848        Ok(())
849    }
850}
851
852/// A client to the [`GuestMemoryManager`] used to control the visibility of
853/// RAM regions.
854#[derive(Clone)]
855pub struct RamVisibilityControl {
856    regions: Arc<Vec<RamRegion>>,
857}
858
859/// The RAM visibility for use with [`RamVisibilityControl::set_ram_visibility`].
860#[derive(Debug, Copy, Clone, PartialEq, Eq)]
861pub enum RamVisibility {
862    /// RAM is unmapped, so reads and writes will go to device memory or MMIO.
863    Unmapped,
864    /// RAM is read-only. Writes will go to device memory or MMIO.
865    ///
866    /// Note that writes will take exits even if there is mapped device memory.
867    ReadOnly,
868    /// RAM is read-write by the guest.
869    ReadWrite,
870}
871
872/// An error returned by [`RamVisibilityControl::set_ram_visibility`].
873#[derive(Debug, Error)]
874pub enum RamVisibilityError {
875    /// The range is not a controllable RAM region.
876    #[error("{0} is not a controllable RAM range")]
877    InvalidRange(MemoryRange),
878    /// Failed to map the region.
879    #[error("failed to map RAM range {range}")]
880    Map {
881        /// The range that failed.
882        range: MemoryRange,
883        /// The error.
884        #[source]
885        error: mesh::error::RemoteError,
886    },
887}
888
889impl RamVisibilityControl {
890    /// Sets the visibility of a RAM region.
891    ///
892    /// A whole region's visibility must be controlled at once, or an error will
893    /// be returned. [`GuestMemoryBuilder::x86_legacy_support`] can be used to
894    /// ensure that there are RAM regions corresponding to x86 memory ranges
895    /// that need to be controlled.
896    pub async fn set_ram_visibility(
897        &self,
898        range: MemoryRange,
899        visibility: RamVisibility,
900    ) -> Result<(), RamVisibilityError> {
901        let region = self
902            .regions
903            .iter()
904            .find(|region| region.range == range)
905            .ok_or(RamVisibilityError::InvalidRange(range))?;
906
907        match visibility {
908            RamVisibility::ReadWrite | RamVisibility::ReadOnly => {
909                region
910                    .handle
911                    .map(MapParams {
912                        writable: matches!(visibility, RamVisibility::ReadWrite),
913                        executable: true,
914                        prefetch: false,
915                    })
916                    .await
917                    .map_err(|error| RamVisibilityError::Map { range, error })?;
918            }
919            RamVisibility::Unmapped => region.handle.unmap().await,
920        }
921        Ok(())
922    }
923}
924
925#[cfg(test)]
926mod tests {
927    use super::*;
928    use pal_async::async_test;
929    use std::error::Error as _;
930
931    /// Build a GuestMemoryManager with the given backing range groups,
932    /// and return a GuestMemory handle for read/write testing.
933    async fn build_and_get_memory(
934        backing_ranges: &[&[MemoryRange]],
935    ) -> (GuestMemoryManager, GuestMemory) {
936        let max_addr = backing_ranges
937            .iter()
938            .flat_map(|ranges| ranges.iter())
939            .map(|r| r.end())
940            .max()
941            .unwrap_or(0);
942
943        let mut builder = GuestMemoryBuilder::new();
944        for ranges in backing_ranges {
945            builder = builder.add_backing(RamBackingRequest::new(ranges.to_vec()));
946        }
947        let mgr = builder.build(max_addr).await.unwrap();
948        let gm = mgr.client().guest_memory().await.unwrap();
949        (mgr, gm)
950    }
951
952    #[async_test]
953    async fn test_hugepages_with_existing_backing_rejected() {
954        const SIZE: u64 = 2 * 1024 * 1024;
955        let mappable = sparse_mmap::alloc_shared_memory(SIZE as usize, "test").unwrap();
956        let backing = RamBackingRequest::new(vec![MemoryRange::new(0..SIZE)])
957            .hugepages(None)
958            .existing_mappable(mappable.into());
959        let err = GuestMemoryBuilder::new()
960            .add_backing(backing)
961            .build(SIZE)
962            .await
963            .unwrap_err();
964        assert!(matches!(
965            err,
966            MemoryBuildError::HugepagesWithExistingBacking
967        ));
968    }
969
970    #[test]
971    fn test_validate_hugepage_size() {
972        let page_size = SparseMapping::page_size() as u64;
973        assert!(validate_hugepage_size(page_size).is_ok());
974        assert!(matches!(
975            validate_hugepage_size(page_size / 2),
976            Err(MemoryBuildError::InvalidHugepageSize(_))
977        ));
978        assert!(matches!(
979            validate_hugepage_size(3 * 1024 * 1024),
980            Err(MemoryBuildError::InvalidHugepageSize(_))
981        ));
982    }
983
984    #[test]
985    fn test_validate_hugepage_ram_alignment() {
986        const HUGEPAGE_SIZE: u64 = 2 * 1024 * 1024;
987
988        validate_hugepage_ram_alignment(
989            4 * 1024 * 1024,
990            &[
991                MemoryRange::new(0..HUGEPAGE_SIZE),
992                MemoryRange::new(2 * HUGEPAGE_SIZE..3 * HUGEPAGE_SIZE),
993            ],
994            HUGEPAGE_SIZE,
995        )
996        .unwrap();
997
998        assert!(matches!(
999            validate_hugepage_ram_alignment(3 * 1024 * 1024, &[], HUGEPAGE_SIZE),
1000            Err(MemoryBuildError::HugepageRamSizeUnaligned { .. })
1001        ));
1002        assert!(matches!(
1003            validate_hugepage_ram_alignment(
1004                HUGEPAGE_SIZE,
1005                &[MemoryRange::new(0..1024 * 1024)],
1006                HUGEPAGE_SIZE,
1007            ),
1008            Err(MemoryBuildError::HugepageRamRangeUnaligned { .. })
1009        ));
1010    }
1011
1012    #[test]
1013    fn test_hugepage_ram_size_alignment_error_message() {
1014        let error =
1015            validate_hugepage_ram_alignment(257 * 1024 * 1024, &[], 2 * 1024 * 1024).unwrap_err();
1016
1017        assert_eq!(
1018            error.to_string(),
1019            "RAM size 257 MB is not aligned to 2 MB hugepages; choose a memory size that is a multiple of the hugepage size"
1020        );
1021    }
1022
1023    #[test]
1024    fn test_hugepage_ram_range_alignment_error_message() {
1025        let error = validate_hugepage_ram_alignment(
1026            2 * 1024 * 1024,
1027            &[MemoryRange::new(0..1024 * 1024)],
1028            2 * 1024 * 1024,
1029        )
1030        .unwrap_err();
1031
1032        assert_eq!(
1033            error.to_string(),
1034            "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"
1035        );
1036    }
1037
1038    #[test]
1039    fn test_hugepage_allocation_error_message() {
1040        let error = MemoryBuildError::HugepageAllocationFailed {
1041            size: MemorySize(1024 * 1024 * 1024),
1042            hugepage_size: MemorySize(2 * 1024 * 1024),
1043            page_count: 512,
1044            error: io::Error::new(io::ErrorKind::OutOfMemory, "Cannot allocate memory"),
1045        };
1046
1047        assert_eq!(
1048            error.to_string(),
1049            "failed to reserve 512 hugetlb pages of 2 MB each (1 GB total); increase the hugetlb pool or reduce guest memory size"
1050        );
1051        assert_eq!(
1052            error.source().unwrap().to_string(),
1053            "Cannot allocate memory"
1054        );
1055    }
1056
1057    #[test]
1058    fn test_single_backing() {
1059        DefaultPool::run_with(|_| async {
1060            let page = SparseMapping::page_size() as u64;
1061            let r = MemoryRange::new(0..4 * page);
1062            let (_mgr, gm) = build_and_get_memory(&[&[r]]).await;
1063
1064            let pattern = vec![0xAB; page as usize];
1065            gm.write_at(0, &pattern).unwrap();
1066            let mut buf = vec![0u8; page as usize];
1067            gm.read_at(0, &mut buf).unwrap();
1068            assert_eq!(buf, pattern);
1069
1070            // Second page should be zeroed.
1071            gm.read_at(page, &mut buf).unwrap();
1072            assert_eq!(buf, vec![0u8; page as usize]);
1073        });
1074    }
1075
1076    #[test]
1077    fn test_two_backings() {
1078        DefaultPool::run_with(|_| async {
1079            let page = SparseMapping::page_size() as u64;
1080            let r0 = MemoryRange::new(0..2 * page);
1081            let r1 = MemoryRange::new(2 * page..4 * page);
1082            let (_mgr, gm) = build_and_get_memory(&[&[r0], &[r1]]).await;
1083
1084            // Write distinct patterns into each backing's region.
1085            let pattern_a = vec![0xAA; page as usize];
1086            let pattern_b = vec![0xBB; page as usize];
1087            gm.write_at(0, &pattern_a).unwrap();
1088            gm.write_at(2 * page, &pattern_b).unwrap();
1089
1090            let mut buf = vec![0u8; page as usize];
1091            gm.read_at(0, &mut buf).unwrap();
1092            assert_eq!(buf, pattern_a, "backing 0 should have pattern_a");
1093
1094            gm.read_at(2 * page, &mut buf).unwrap();
1095            assert_eq!(buf, pattern_b, "backing 1 should have pattern_b");
1096
1097            // Unwritten pages within each backing should be zeroed.
1098            gm.read_at(page, &mut buf).unwrap();
1099            assert_eq!(buf, vec![0u8; page as usize]);
1100            gm.read_at(3 * page, &mut buf).unwrap();
1101            assert_eq!(buf, vec![0u8; page as usize]);
1102        });
1103    }
1104
1105    #[test]
1106    fn test_two_backings_different_sizes() {
1107        DefaultPool::run_with(|_| async {
1108            let page = SparseMapping::page_size() as u64;
1109            let r0 = MemoryRange::new(0..page);
1110            let r1 = MemoryRange::new(page..4 * page);
1111            let (_mgr, gm) = build_and_get_memory(&[&[r0], &[r1]]).await;
1112
1113            let pattern_a = vec![0x11; page as usize];
1114            let pattern_b = vec![0x22; page as usize];
1115            gm.write_at(0, &pattern_a).unwrap();
1116            gm.write_at(page, &pattern_b).unwrap();
1117
1118            let mut buf = vec![0u8; page as usize];
1119            gm.read_at(0, &mut buf).unwrap();
1120            assert_eq!(buf, pattern_a);
1121            gm.read_at(page, &mut buf).unwrap();
1122            assert_eq!(buf, pattern_b);
1123
1124            // Last page of backing 1.
1125            let pattern_c = vec![0x33; page as usize];
1126            gm.write_at(3 * page, &pattern_c).unwrap();
1127            gm.read_at(3 * page, &mut buf).unwrap();
1128            assert_eq!(buf, pattern_c);
1129
1130            // Middle page of backing 1 should be zeroed.
1131            gm.read_at(2 * page, &mut buf).unwrap();
1132            assert_eq!(buf, vec![0u8; page as usize]);
1133        });
1134    }
1135
1136    #[test]
1137    fn test_two_backings_with_gap() {
1138        DefaultPool::run_with(|_| async {
1139            let page = SparseMapping::page_size() as u64;
1140            let r0 = MemoryRange::new(0..2 * page);
1141            let r1 = MemoryRange::new(4 * page..6 * page);
1142
1143            let mgr = GuestMemoryBuilder::new()
1144                .add_backing(RamBackingRequest::new(vec![r0]))
1145                .add_backing(RamBackingRequest::new(vec![r1]))
1146                .build(r1.end())
1147                .await
1148                .unwrap();
1149            let gm = mgr.client().guest_memory().await.unwrap();
1150
1151            let pattern_a = vec![0xCC; page as usize];
1152            let pattern_b = vec![0xDD; page as usize];
1153            gm.write_at(0, &pattern_a).unwrap();
1154            gm.write_at(4 * page, &pattern_b).unwrap();
1155
1156            let mut buf = vec![0u8; page as usize];
1157            gm.read_at(0, &mut buf).unwrap();
1158            assert_eq!(buf, pattern_a);
1159            gm.read_at(4 * page, &mut buf).unwrap();
1160            assert_eq!(buf, pattern_b);
1161        });
1162    }
1163
1164    /// Builds a manager with a single THP-enabled shared RAM backing and
1165    /// returns a [`GuestMemory`] over the **primary** mapper. Soft large pages
1166    /// (the Windows deferred-protect scheme that maps guest RAM read-only until
1167    /// the first write) apply only to the primary mapper, so locking behavior
1168    /// must be exercised through it rather than through
1169    /// [`GuestMemoryClient::guest_memory`], which hands out a secondary mapper.
1170    async fn build_thp_primary_memory(size: u64) -> (GuestMemoryManager, GuestMemory) {
1171        let mgr = GuestMemoryBuilder::new()
1172            .add_backing(
1173                RamBackingRequest::new(vec![MemoryRange::new(0..size)]).transparent_hugepages(true),
1174            )
1175            .build(size)
1176            .await
1177            .unwrap();
1178        let primary = GuestMemory::new("test-primary", mgr.va_mapper.clone());
1179        (mgr, primary)
1180    }
1181
1182    /// Locking guest RAM for write must make it writable through the returned
1183    /// raw pointer, even when the backing is only lazily made writable on the
1184    /// first write (Windows soft large pages map primary-mapper guest RAM
1185    /// read-only until then). The write here goes through the locked pointer
1186    /// directly, bypassing the fault-handling `write_*` path, so if the lock
1187    /// had only faulted the page in for read the store would access-violate.
1188    /// This is the regression guard for read-only-locking a page that is then
1189    /// written via zero-copy DMA.
1190    #[async_test]
1191    async fn test_lock_for_write_makes_page_writable() {
1192        use std::sync::atomic::Ordering;
1193
1194        const SIZE: u64 = 2 * 1024 * 1024;
1195        let (_mgr, gm) = build_thp_primary_memory(SIZE).await;
1196
1197        let locked = gm
1198            .lock_gpns(guestmem::AccessType::Write, false, &[0])
1199            .unwrap();
1200        // Store directly through the locked pointer (not via `write_at`, which
1201        // would fault the page in on its own).
1202        locked.pages()[0][0].store(0xAB, Ordering::SeqCst);
1203        locked.pages()[0][1].store(0xCD, Ordering::SeqCst);
1204        drop(locked);
1205
1206        // The stores must be visible through a normal read.
1207        assert_eq!(gm.read_plain::<u8>(0).unwrap(), 0xAB);
1208        assert_eq!(gm.read_plain::<u8>(1).unwrap(), 0xCD);
1209    }
1210
1211    /// A read-only lock succeeds and reads back the freshly zeroed page without
1212    /// forcing the page writable.
1213    #[async_test]
1214    async fn test_lock_for_read_succeeds() {
1215        use std::sync::atomic::Ordering;
1216
1217        const SIZE: u64 = 2 * 1024 * 1024;
1218        let (_mgr, gm) = build_thp_primary_memory(SIZE).await;
1219
1220        let locked = gm
1221            .lock_gpns(guestmem::AccessType::Read, false, &[0])
1222            .unwrap();
1223        assert_eq!(locked.pages()[0][0].load(Ordering::SeqCst), 0);
1224        drop(locked);
1225    }
1226}