1mod device_memory;
7
8pub use device_memory::DeviceMemoryMapper;
9
10use crate::RemoteProcess;
11use crate::mapping_manager::Mappable;
12use crate::mapping_manager::MappingManager;
13use crate::mapping_manager::MappingManagerClient;
14use crate::mapping_manager::VaMapper;
15use crate::mapping_manager::VaMapperError;
16use crate::partition_mapper::PartitionMapper;
17use crate::region_manager::MapParams;
18use crate::region_manager::RegionHandle;
19use crate::region_manager::RegionManager;
20use guestmem::GuestMemory;
21use hvdef::Vtl;
22use inspect::Inspect;
23use memory_range::MemoryRange;
24use mesh::MeshPayload;
25use pal_async::DefaultPool;
26use sparse_mmap::SparseMapping;
27use std::io;
28use std::sync::Arc;
29use std::thread::JoinHandle;
30use thiserror::Error;
31use vm_topology::memory::MemoryLayout;
32
33#[derive(Debug, Inspect)]
35pub struct GuestMemoryManager {
36 #[inspect(skip)]
38 guest_ram: Option<Mappable>,
39
40 #[inspect(skip)]
41 ram_regions: Arc<Vec<RamRegion>>,
42
43 #[inspect(flatten)]
44 mapping_manager: MappingManager,
45
46 #[inspect(flatten)]
47 region_manager: RegionManager,
48
49 #[inspect(skip)]
50 va_mapper: Arc<VaMapper>,
51
52 #[inspect(skip)]
53 _thread: JoinHandle<()>,
54
55 vtl0_alias_map_offset: Option<u64>,
56 pin_mappings: bool,
57}
58
59#[derive(Debug)]
60struct RamRegion {
61 range: MemoryRange,
62 handle: RegionHandle,
63}
64
65#[derive(Error, Debug)]
67pub enum PartitionAttachError {
68 #[error("failed to reserve VA range for partition mapping")]
70 VaMapper(#[source] VaMapperError),
71 #[error("failed to attach partition to memory manager")]
73 PartitionMapper(#[source] crate::partition_mapper::PartitionMapperError),
74}
75
76#[derive(Error, Debug)]
78pub enum MemoryBuildError {
79 #[error("ram size {0} is too large")]
81 RamTooLarge(MemorySize),
82 #[error("failed to allocate memory")]
84 AllocationFailed(#[source] io::Error),
85 #[error(
87 "failed to reserve {page_count} hugetlb pages of {hugepage_size} each ({size} total); increase the hugetlb pool or reduce guest memory size"
88 )]
89 HugepageAllocationFailed {
90 size: MemorySize,
92 hugepage_size: MemorySize,
94 page_count: usize,
96 #[source]
98 error: io::Error,
99 },
100 #[error("failed to create VA mapper")]
102 VaMapper(#[source] VaMapperError),
103 #[error("not enough guest address space available for the vtl0 alias map")]
105 AliasMapWontFit,
106 #[error("x86 support requires RAM to start at 0 and contain at least 1MB")]
108 InvalidRamForX86,
109 #[error("private memory is incompatible with x86 legacy support")]
111 PrivateMemoryWithLegacy,
112 #[error("private memory is incompatible with existing memory backing")]
114 PrivateMemoryWithExistingBacking,
115 #[error("failed to allocate private RAM range {1}")]
117 PrivateRamAlloc(#[source] io::Error, MemoryRange),
118 #[error("transparent huge pages requires private memory mode")]
120 ThpWithoutPrivateMemory,
121 #[error("transparent huge pages is only supported on Linux")]
123 ThpUnsupportedPlatform,
124 #[error("hugepage size {0} is too large")]
126 HugepageSizeTooLarge(MemorySize),
127 #[error("hugepages are only supported on Linux")]
129 HugepagesUnsupportedPlatform,
130 #[error("hugepages require shared memory mode")]
132 HugepagesWithPrivateMemory,
133 #[error("hugepages are incompatible with existing memory backing")]
135 HugepagesWithExistingBacking,
136 #[error("hugepages are incompatible with x86 legacy RAM splitting")]
138 HugepagesWithLegacy,
139 #[error("hugepage size {0} must be a power of two and at least the host page size")]
141 InvalidHugepageSize(MemorySize),
142 #[error(
144 "RAM size {ram_size} is not aligned to {hugepage_size} hugepages; choose a memory size that is a multiple of the hugepage size"
145 )]
146 HugepageRamSizeUnaligned {
147 ram_size: MemorySize,
149 hugepage_size: MemorySize,
151 },
152 #[error(
154 "RAM range {range} ({range_size}) is not aligned to {hugepage_size} hugepages; range start and size must both be multiples of the hugepage size"
155 )]
156 HugepageRamRangeUnaligned {
157 range: MemoryRange,
159 range_size: MemorySize,
161 hugepage_size: MemorySize,
163 },
164}
165
166const DEFAULT_HUGEPAGE_SIZE: u64 = 2 * 1024 * 1024;
167
168#[derive(Debug, Copy, Clone)]
170struct HugepageConfig {
171 size: Option<u64>,
172}
173
174fn validate_hugepage_size(size: u64) -> Result<usize, MemoryBuildError> {
175 if !size.is_power_of_two() || size < SparseMapping::page_size() as u64 {
176 return Err(MemoryBuildError::InvalidHugepageSize(MemorySize(size)));
177 }
178 size.try_into()
179 .map_err(|_| MemoryBuildError::HugepageSizeTooLarge(MemorySize(size)))
180}
181
182#[derive(Debug, Copy, Clone)]
184pub struct MemorySize(
185 pub u64,
187);
188
189impl std::fmt::Display for MemorySize {
190 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
191 const KB: u64 = 1024;
192 const MB: u64 = 1024 * KB;
193 const GB: u64 = 1024 * MB;
194 const TB: u64 = 1024 * GB;
195
196 for (unit, suffix) in [(TB, "TB"), (GB, "GB"), (MB, "MB"), (KB, "KB")] {
197 if self.0 != 0 && self.0.is_multiple_of(unit) {
198 return write!(f, "{} {suffix}", self.0 / unit);
199 }
200 }
201
202 write!(f, "{} bytes", self.0)
203 }
204}
205
206fn validate_hugepage_ram_alignment(
207 ram_size: u64,
208 ram_ranges: &[MemoryRange],
209 hugepage_size: u64,
210) -> Result<(), MemoryBuildError> {
211 if !ram_size.is_multiple_of(hugepage_size) {
212 return Err(MemoryBuildError::HugepageRamSizeUnaligned {
213 ram_size: MemorySize(ram_size),
214 hugepage_size: MemorySize(hugepage_size),
215 });
216 }
217 for &range in ram_ranges {
218 if !range.start().is_multiple_of(hugepage_size)
219 || !range.len().is_multiple_of(hugepage_size)
220 {
221 return Err(MemoryBuildError::HugepageRamRangeUnaligned {
222 range,
223 range_size: MemorySize(range.len()),
224 hugepage_size: MemorySize(hugepage_size),
225 });
226 }
227 }
228 Ok(())
229}
230
231pub struct GuestMemoryBuilder {
233 existing_mapping: Option<SharedMemoryBacking>,
234 vtl0_alias_map: Option<u64>,
235 prefetch_ram: bool,
236 pin_mappings: bool,
237 x86_legacy_support: bool,
238 private_memory: bool,
239 transparent_hugepages: bool,
240 hugepages: Option<HugepageConfig>,
241}
242
243impl GuestMemoryBuilder {
244 pub fn new() -> Self {
246 Self {
247 existing_mapping: None,
248 vtl0_alias_map: None,
249 pin_mappings: false,
250 prefetch_ram: false,
251 x86_legacy_support: false,
252 private_memory: false,
253 transparent_hugepages: false,
254 hugepages: None,
255 }
256 }
257
258 pub fn existing_backing(mut self, mapping: Option<SharedMemoryBacking>) -> Self {
260 self.existing_mapping = mapping;
261 self
262 }
263
264 pub fn vtl0_alias_map(mut self, offset: Option<u64>) -> Self {
268 self.vtl0_alias_map = offset;
269 self
270 }
271
272 pub fn pin_mappings(mut self, enable: bool) -> Self {
276 self.pin_mappings = enable;
277 self
278 }
279
280 pub fn prefetch_ram(mut self, enable: bool) -> Self {
283 self.prefetch_ram = enable;
284 self
285 }
286
287 pub fn x86_legacy_support(mut self, enable: bool) -> Self {
300 self.x86_legacy_support = enable;
301 self
302 }
303
304 pub fn private_memory(mut self, enable: bool) -> Self {
314 self.private_memory = enable;
315 self
316 }
317
318 pub fn transparent_hugepages(mut self, enable: bool) -> Self {
325 self.transparent_hugepages = enable;
326 self
327 }
328
329 pub fn hugepages(mut self, size: Option<u64>) -> Self {
331 self.hugepages = Some(HugepageConfig { size });
332 self
333 }
334
335 pub async fn build(
338 self,
339 mem_layout: &MemoryLayout,
340 ) -> Result<GuestMemoryManager, MemoryBuildError> {
341 if self.private_memory {
343 if self.x86_legacy_support {
344 return Err(MemoryBuildError::PrivateMemoryWithLegacy);
345 }
346 if self.existing_mapping.is_some() {
347 return Err(MemoryBuildError::PrivateMemoryWithExistingBacking);
348 }
349 }
350
351 if self.transparent_hugepages {
353 if !self.private_memory {
354 return Err(MemoryBuildError::ThpWithoutPrivateMemory);
355 }
356 if !cfg!(target_os = "linux") {
357 return Err(MemoryBuildError::ThpUnsupportedPlatform);
358 }
359 }
360
361 let ram_size = mem_layout.ram_size() + mem_layout.vtl2_range().map_or(0, |r| r.len());
362
363 let mut ram_ranges = mem_layout
364 .ram()
365 .iter()
366 .map(|x| x.range)
367 .chain(mem_layout.vtl2_range())
368 .collect::<Vec<_>>();
369
370 let hugepage_size = if let Some(hugepages) = self.hugepages {
371 if !cfg!(target_os = "linux") {
372 return Err(MemoryBuildError::HugepagesUnsupportedPlatform);
373 }
374 if self.private_memory {
375 return Err(MemoryBuildError::HugepagesWithPrivateMemory);
376 }
377 if self.existing_mapping.is_some() {
378 return Err(MemoryBuildError::HugepagesWithExistingBacking);
379 }
380 if self.x86_legacy_support {
381 return Err(MemoryBuildError::HugepagesWithLegacy);
382 }
383 let size = validate_hugepage_size(hugepages.size.unwrap_or(DEFAULT_HUGEPAGE_SIZE))?;
384 validate_hugepage_ram_alignment(ram_size, &ram_ranges, size as u64)?;
385 Some(size)
386 } else {
387 None
388 };
389
390 let memory: Option<Mappable> = if self.private_memory {
391 None
394 } else if let Some(memory) = self.existing_mapping {
395 Some(memory.guest_ram)
396 } else {
397 let ram_size = ram_size
398 .try_into()
399 .map_err(|_| MemoryBuildError::RamTooLarge(MemorySize(ram_size)))?;
400 let guest_ram = if let Some(hugepage_size) = hugepage_size {
401 sparse_mmap::alloc_shared_memory_hugetlb(ram_size, "guest-ram", Some(hugepage_size))
402 .map_err(|error| MemoryBuildError::HugepageAllocationFailed {
403 size: MemorySize(ram_size as u64),
404 hugepage_size: MemorySize(hugepage_size as u64),
405 page_count: ram_size / hugepage_size,
406 error,
407 })?
408 } else {
409 sparse_mmap::alloc_shared_memory(ram_size, "guest-ram")
410 .map_err(MemoryBuildError::AllocationFailed)?
411 };
412 Some(guest_ram.into())
413 };
414
415 let (thread, spawner) = DefaultPool::spawn_on_thread("memory_manager");
419
420 let max_addr =
421 (mem_layout.end_of_layout()).max(mem_layout.vtl2_range().map_or(0, |r| r.end()));
422
423 let vtl0_alias_map_offset = if let Some(offset) = self.vtl0_alias_map {
424 if max_addr > offset {
425 return Err(MemoryBuildError::AliasMapWontFit);
426 }
427 Some(offset)
428 } else {
429 None
430 };
431
432 let mapping_manager =
433 MappingManager::new(&spawner, max_addr, self.private_memory, hugepage_size);
434 let va_mapper = mapping_manager
435 .client()
436 .new_mapper()
437 .await
438 .map_err(MemoryBuildError::VaMapper)?;
439
440 let region_manager = RegionManager::new(&spawner, mapping_manager.client().clone());
441
442 if self.x86_legacy_support {
443 if ram_ranges[0].start() != 0 || ram_ranges[0].end() < 0x100000 {
444 return Err(MemoryBuildError::InvalidRamForX86);
445 }
446
447 let range_starts = [
449 0,
450 0xa0000,
451 0xc0000,
452 0xc4000,
453 0xc8000,
454 0xcc000,
455 0xd0000,
456 0xd4000,
457 0xd8000,
458 0xdc000,
459 0xe0000,
460 0xe4000,
461 0xe8000,
462 0xec000,
463 0xf0000,
464 0x100000,
465 ram_ranges[0].end(),
466 ];
467
468 ram_ranges.splice(
469 0..1,
470 range_starts
471 .iter()
472 .zip(range_starts.iter().skip(1))
473 .map(|(&start, &end)| MemoryRange::new(start..end)),
474 );
475 }
476
477 if self.private_memory {
481 for range in &ram_ranges {
482 va_mapper
483 .alloc_range(range.start() as usize, range.len() as usize)
484 .map_err(|e| MemoryBuildError::PrivateRamAlloc(e, *range))?;
485 va_mapper.set_range_name(
486 range.start() as usize,
487 range.len() as usize,
488 "guest-ram-private",
489 );
490 }
491
492 #[cfg(target_os = "linux")]
495 if self.transparent_hugepages {
496 for range in &ram_ranges {
497 if let Err(e) =
498 va_mapper.madvise_hugepage(range.start() as usize, range.len() as usize)
499 {
500 tracing::warn!(
501 error = &e as &dyn std::error::Error,
502 range = %range,
503 "failed to mark RAM as THP eligible"
504 );
505 }
506 }
507 }
508 }
509
510 let mut ram_regions = Vec::new();
511 let mut start = 0;
512 for range in &ram_ranges {
513 let region = region_manager
514 .client()
515 .new_region("ram".into(), *range, RAM_PRIORITY, true)
516 .await
517 .expect("regions cannot overlap yet");
518
519 if let Some(ref memory) = memory {
520 region
522 .add_mapping(
523 MemoryRange::new(0..range.len()),
524 memory.clone(),
525 start,
526 true,
527 )
528 .await;
529 }
530 region
534 .map(MapParams {
535 writable: true,
536 executable: true,
537 prefetch: self.prefetch_ram && !self.private_memory,
538 })
539 .await;
540
541 ram_regions.push(RamRegion {
542 range: *range,
543 handle: region,
544 });
545 start += range.len();
546 }
547
548 let gm = GuestMemoryManager {
549 guest_ram: memory,
550 _thread: thread,
551 ram_regions: Arc::new(ram_regions),
552 mapping_manager,
553 region_manager,
554 va_mapper,
555 vtl0_alias_map_offset,
556 pin_mappings: self.pin_mappings,
557 };
558 Ok(gm)
559 }
560}
561
562#[derive(Debug, MeshPayload)]
564pub struct SharedMemoryBacking {
565 guest_ram: Mappable,
566}
567
568impl SharedMemoryBacking {
569 pub fn from_mappable(guest_ram: Mappable) -> Self {
571 Self { guest_ram }
572 }
573}
574
575#[derive(Debug, MeshPayload)]
577pub struct GuestMemoryClient {
578 mapping_manager: MappingManagerClient,
579}
580
581impl GuestMemoryClient {
582 pub async fn guest_memory(&self) -> Result<GuestMemory, VaMapperError> {
589 Ok(GuestMemory::new(
590 "ram",
591 self.mapping_manager.new_mapper().await?,
592 ))
593 }
594}
595
596const RAM_PRIORITY: u8 = 255;
598
599const DEVICE_PRIORITY: u8 = 0;
601
602impl GuestMemoryManager {
603 pub fn client(&self) -> GuestMemoryClient {
605 GuestMemoryClient {
606 mapping_manager: self.mapping_manager.client().clone(),
607 }
608 }
609
610 pub fn device_memory_mapper(&self) -> DeviceMemoryMapper {
612 DeviceMemoryMapper::new(self.region_manager.client().clone())
613 }
614
615 pub fn dma_mapper_client(&self) -> crate::region_manager::DmaMapperClient {
617 crate::region_manager::DmaMapperClient::new(self.region_manager.client())
618 }
619
620 pub fn ram_visibility_control(&self) -> RamVisibilityControl {
623 RamVisibilityControl {
624 regions: self.ram_regions.clone(),
625 }
626 }
627
628 pub fn shared_memory_backing(&self) -> Option<SharedMemoryBacking> {
639 let guest_ram = self.guest_ram.clone()?;
640 Some(SharedMemoryBacking { guest_ram })
641 }
642
643 pub async fn attach_partition(
654 &mut self,
655 vtl: Vtl,
656 partition: &Arc<dyn virt::PartitionMemoryMap>,
657 process: Option<RemoteProcess>,
658 ) -> Result<(), PartitionAttachError> {
659 let va_mapper = if let Some(process) = process {
660 self.mapping_manager
661 .client()
662 .new_remote_mapper(process)
663 .await
664 .map_err(PartitionAttachError::VaMapper)?
665 } else {
666 self.va_mapper.clone()
667 };
668
669 if vtl == Vtl::Vtl2 {
670 if let Some(offset) = self.vtl0_alias_map_offset {
671 let partition =
672 PartitionMapper::new(partition, va_mapper.clone(), offset, self.pin_mappings);
673 self.region_manager
674 .client()
675 .add_partition(partition)
676 .await
677 .map_err(PartitionAttachError::PartitionMapper)?;
678 }
679 }
680
681 let partition = PartitionMapper::new(partition, va_mapper, 0, self.pin_mappings);
682 self.region_manager
683 .client()
684 .add_partition(partition)
685 .await
686 .map_err(PartitionAttachError::PartitionMapper)?;
687 Ok(())
688 }
689}
690
691pub struct RamVisibilityControl {
694 regions: Arc<Vec<RamRegion>>,
695}
696
697#[derive(Debug, Copy, Clone, PartialEq, Eq)]
699pub enum RamVisibility {
700 Unmapped,
702 ReadOnly,
706 ReadWrite,
708}
709
710#[derive(Debug, Error)]
712#[error("{0} is not a controllable RAM range")]
713pub struct InvalidRamRegion(MemoryRange);
714
715impl RamVisibilityControl {
716 pub async fn set_ram_visibility(
723 &self,
724 range: MemoryRange,
725 visibility: RamVisibility,
726 ) -> Result<(), InvalidRamRegion> {
727 let region = self
728 .regions
729 .iter()
730 .find(|region| region.range == range)
731 .ok_or(InvalidRamRegion(range))?;
732
733 match visibility {
734 RamVisibility::ReadWrite | RamVisibility::ReadOnly => {
735 region
736 .handle
737 .map(MapParams {
738 writable: matches!(visibility, RamVisibility::ReadWrite),
739 executable: true,
740 prefetch: false,
741 })
742 .await
743 }
744 RamVisibility::Unmapped => region.handle.unmap().await,
745 }
746 Ok(())
747 }
748}
749
750#[cfg(test)]
751mod tests {
752 use super::*;
753 use std::error::Error as _;
754
755 #[test]
756 fn test_validate_hugepage_size() {
757 let page_size = SparseMapping::page_size() as u64;
758 assert!(validate_hugepage_size(page_size).is_ok());
759 assert!(matches!(
760 validate_hugepage_size(page_size / 2),
761 Err(MemoryBuildError::InvalidHugepageSize(_))
762 ));
763 assert!(matches!(
764 validate_hugepage_size(3 * 1024 * 1024),
765 Err(MemoryBuildError::InvalidHugepageSize(_))
766 ));
767 }
768
769 #[test]
770 fn test_validate_hugepage_ram_alignment() {
771 const HUGEPAGE_SIZE: u64 = 2 * 1024 * 1024;
772
773 validate_hugepage_ram_alignment(
774 4 * 1024 * 1024,
775 &[
776 MemoryRange::new(0..HUGEPAGE_SIZE),
777 MemoryRange::new(2 * HUGEPAGE_SIZE..3 * HUGEPAGE_SIZE),
778 ],
779 HUGEPAGE_SIZE,
780 )
781 .unwrap();
782
783 assert!(matches!(
784 validate_hugepage_ram_alignment(3 * 1024 * 1024, &[], HUGEPAGE_SIZE),
785 Err(MemoryBuildError::HugepageRamSizeUnaligned { .. })
786 ));
787 assert!(matches!(
788 validate_hugepage_ram_alignment(
789 HUGEPAGE_SIZE,
790 &[MemoryRange::new(0..1024 * 1024)],
791 HUGEPAGE_SIZE,
792 ),
793 Err(MemoryBuildError::HugepageRamRangeUnaligned { .. })
794 ));
795 }
796
797 #[test]
798 fn test_hugepage_ram_size_alignment_error_message() {
799 let error =
800 validate_hugepage_ram_alignment(257 * 1024 * 1024, &[], 2 * 1024 * 1024).unwrap_err();
801
802 assert_eq!(
803 error.to_string(),
804 "RAM size 257 MB is not aligned to 2 MB hugepages; choose a memory size that is a multiple of the hugepage size"
805 );
806 }
807
808 #[test]
809 fn test_hugepage_ram_range_alignment_error_message() {
810 let error = validate_hugepage_ram_alignment(
811 2 * 1024 * 1024,
812 &[MemoryRange::new(0..1024 * 1024)],
813 2 * 1024 * 1024,
814 )
815 .unwrap_err();
816
817 assert_eq!(
818 error.to_string(),
819 "RAM range 0x0-0x100000 (1 MB) is not aligned to 2 MB hugepages; range start and size must both be multiples of the hugepage size"
820 );
821 }
822
823 #[test]
824 fn test_hugepage_allocation_error_message() {
825 let error = MemoryBuildError::HugepageAllocationFailed {
826 size: MemorySize(1024 * 1024 * 1024),
827 hugepage_size: MemorySize(2 * 1024 * 1024),
828 page_count: 512,
829 error: io::Error::new(io::ErrorKind::OutOfMemory, "Cannot allocate memory"),
830 };
831
832 assert_eq!(
833 error.to_string(),
834 "failed to reserve 512 hugetlb pages of 2 MB each (1 GB total); increase the hugetlb pool or reduce guest memory size"
835 );
836 assert_eq!(
837 error.source().unwrap().to_string(),
838 "Cannot allocate memory"
839 );
840 }
841}