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