1mod 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#[derive(Debug, Inspect)]
36pub struct GuestMemoryManager {
37 #[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 supports_memory_fault_resolution: bool,
63}
64
65#[derive(Debug)]
67struct RamBacking {
68 mappable: Option<Mappable>,
70 ranges: Vec<MemoryRange>,
72 prefetch: bool,
74 transparent_hugepages: bool,
76 host_numa_node: Option<u32>,
78}
79
80#[derive(Debug)]
81struct RamRegion {
82 range: MemoryRange,
83 handle: RegionHandle,
84}
85
86#[derive(Error, Debug)]
88pub enum PartitionAttachError {
89 #[error("failed to reserve VA range for partition mapping")]
91 VaMapper(#[source] VaMapperError),
92 #[error("failed to attach partition to memory manager")]
94 PartitionMapper(#[source] crate::partition_mapper::PartitionMapperError),
95}
96
97#[derive(Error, Debug)]
99pub enum MemoryBuildError {
100 #[error("ram size {0} is too large")]
102 RamTooLarge(MemorySize),
103 #[error("failed to allocate memory")]
105 AllocationFailed(#[source] io::Error),
106 #[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 size: MemorySize,
113 hugepage_size: MemorySize,
115 page_count: usize,
117 #[source]
119 error: io::Error,
120 },
121 #[error("failed to create VA mapper")]
123 VaMapper(#[source] VaMapperError),
124 #[error("failed to map RAM range {range}")]
126 RamMapping {
127 range: MemoryRange,
129 #[source]
131 error: mesh::error::RemoteError,
132 },
133 #[error("failed to enable RAM region {range}")]
135 RamRegionEnable {
136 range: MemoryRange,
138 #[source]
140 error: mesh::error::RemoteError,
141 },
142 #[error("not enough guest address space available for the vtl0 alias map")]
144 AliasMapWontFit,
145 #[error("x86 support requires RAM to start at 0 and contain at least 1MB")]
147 InvalidRamForX86,
148 #[error("private memory is incompatible with x86 legacy support")]
150 PrivateMemoryWithLegacy,
151 #[error("private memory is incompatible with an existing memory backing")]
153 PrivateMemoryWithExistingBacking,
154 #[error("hugepage size {0} is too large")]
156 HugepageSizeTooLarge(MemorySize),
157 #[error("hugepages are only supported on Linux and Windows")]
159 HugepagesUnsupportedPlatform,
160 #[error("host NUMA node binding is only supported on Linux and Windows")]
162 HostNumaNodeUnsupportedPlatform,
163 #[error("hugepages require shared memory mode")]
165 HugepagesWithPrivateMemory,
166 #[error("hugepages are incompatible with existing memory backing")]
168 HugepagesWithExistingBacking,
169 #[error("hugepages are incompatible with x86 legacy RAM splitting")]
171 HugepagesWithLegacy,
172 #[error("hugepage size {0} must be a power of two and at least the host page size")]
174 InvalidHugepageSize(MemorySize),
175 #[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 ram_size: MemorySize,
182 hugepage_size: MemorySize,
184 },
185 #[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 range: MemoryRange,
192 range_size: MemorySize,
194 hugepage_size: MemorySize,
196 },
197}
198
199const DEFAULT_HUGEPAGE_SIZE: u64 = 2 * 1024 * 1024;
200
201#[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 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 pub fn prefetch(mut self, enable: bool) -> Self {
238 self.prefetch = enable;
239 self
240 }
241
242 pub fn private_memory(mut self, enable: bool) -> Self {
244 self.private_memory = enable;
245 self
246 }
247
248 pub fn transparent_hugepages(mut self, enable: bool) -> Self {
256 self.transparent_hugepages = enable;
257 self
258 }
259
260 pub fn hugepages(mut self, size: Option<u64>) -> Self {
263 self.hugepages = true;
264 self.hugepage_size = size;
265 self
266 }
267
268 pub fn existing_mappable(mut self, mappable: Mappable) -> Self {
271 self.existing_mappable = Some(mappable);
272 self
273 }
274
275 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#[derive(Debug, Copy, Clone)]
298pub struct MemorySize(
299 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
345pub 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 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 pub fn vtl0_alias_map(mut self, offset: Option<u64>) -> Self {
370 self.vtl0_alias_map = offset;
371 self
372 }
373
374 pub fn pin_mappings(mut self, enable: bool) -> Self {
378 self.pin_mappings = enable;
379 self
380 }
381
382 pub fn x86_legacy_support(mut self, enable: bool) -> Self {
395 self.x86_legacy_support = enable;
396 self
397 }
398
399 pub fn supports_memory_fault_resolution(mut self, enable: bool) -> Self {
403 self.supports_memory_fault_resolution = enable;
404 self
405 }
406
407 pub fn add_backing(mut self, request: RamBackingRequest) -> Self {
410 self.backing_requests.push(request);
411 self
412 }
413
414 pub async fn build(self, max_addr: u64) -> Result<GuestMemoryManager, MemoryBuildError> {
421 let backing_requests = self.backing_requests;
422
423 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 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 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 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 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 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 prefetch: req.prefetch || (cfg!(windows) && req.hugepages),
549 transparent_hugepages: req.transparent_hugepages && !req.hugepages,
553 host_numa_node: req.host_numa_node,
554 });
555 }
556
557 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 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 let mut ram_regions = Vec::new();
586 for backing in &backings {
587 let mut file_offset = 0u64;
588 for range in &backing.ranges {
589 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 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#[derive(Debug, MeshPayload)]
689pub struct SharedMemoryBacking {
690 guest_ram: Mappable,
691}
692
693impl SharedMemoryBacking {
694 pub fn from_mappable(guest_ram: Mappable) -> Self {
696 Self { guest_ram }
697 }
698
699 pub fn into_mappable(self) -> Mappable {
701 self.guest_ram
702 }
703}
704
705#[derive(Debug, MeshPayload)]
707pub struct GuestMemoryClient {
708 mapping_manager: MappingManagerClient,
709}
710
711impl GuestMemoryClient {
712 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
726const RAM_PRIORITY: u8 = 255;
728
729const DEVICE_PRIORITY: u8 = 0;
731
732impl GuestMemoryManager {
733 pub fn client(&self) -> GuestMemoryClient {
735 GuestMemoryClient {
736 mapping_manager: self.mapping_manager.client().clone(),
737 }
738 }
739
740 pub fn memory_fault_resolver(&self) -> Arc<dyn virt::ResolveMemoryFault> {
748 self.va_mapper.clone()
749 }
750
751 pub fn device_memory_mapper(&self) -> DeviceMemoryMapper {
753 DeviceMemoryMapper::new(self.region_manager.client().clone())
754 }
755
756 pub fn dma_mapper_client(&self) -> crate::region_manager::DmaMapperClient {
758 crate::region_manager::DmaMapperClient::new(self.region_manager.client())
759 }
760
761 pub fn ram_visibility_control(&self) -> RamVisibilityControl {
764 RamVisibilityControl {
765 regions: self.ram_regions.clone(),
766 }
767 }
768
769 pub fn shared_memory_backing(&self) -> Option<SharedMemoryBacking> {
782 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 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#[derive(Clone)]
855pub struct RamVisibilityControl {
856 regions: Arc<Vec<RamRegion>>,
857}
858
859#[derive(Debug, Copy, Clone, PartialEq, Eq)]
861pub enum RamVisibility {
862 Unmapped,
864 ReadOnly,
868 ReadWrite,
870}
871
872#[derive(Debug, Error)]
874pub enum RamVisibilityError {
875 #[error("{0} is not a controllable RAM range")]
877 InvalidRange(MemoryRange),
878 #[error("failed to map RAM range {range}")]
880 Map {
881 range: MemoryRange,
883 #[source]
885 error: mesh::error::RemoteError,
886 },
887}
888
889impl RamVisibilityControl {
890 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 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 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 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 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 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 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 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 #[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 locked.pages()[0][0].store(0xAB, Ordering::SeqCst);
1203 locked.pages()[0][1].store(0xCD, Ordering::SeqCst);
1204 drop(locked);
1205
1206 assert_eq!(gm.read_plain::<u8>(0).unwrap(), 0xAB);
1208 assert_eq!(gm.read_plain::<u8>(1).unwrap(), 0xCD);
1209 }
1210
1211 #[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}