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(
802 &mut self,
803 vtl: Vtl,
804 partition: &Arc<dyn virt::PartitionMemoryMap>,
805 process: Option<RemoteProcess>,
806 ) -> Result<(), PartitionAttachError> {
807 let va_mapper = if let Some(process) = process {
808 self.mapping_manager
809 .client()
810 .new_remote_mapper(process)
811 .await
812 .map_err(PartitionAttachError::VaMapper)?
813 } else {
814 self.va_mapper.clone()
815 };
816
817 if vtl == Vtl::Vtl2 {
818 if let Some(offset) = self.vtl0_alias_map_offset {
819 let partition =
820 PartitionMapper::new(partition, va_mapper.clone(), offset, self.pin_mappings);
821 self.region_manager
822 .client()
823 .add_partition(partition)
824 .await
825 .map_err(PartitionAttachError::PartitionMapper)?;
826 }
827 }
828
829 let partition = PartitionMapper::new(partition, va_mapper, 0, self.pin_mappings);
830 self.region_manager
831 .client()
832 .add_partition(partition)
833 .await
834 .map_err(PartitionAttachError::PartitionMapper)?;
835 Ok(())
836 }
837}
838
839#[derive(Clone)]
842pub struct RamVisibilityControl {
843 regions: Arc<Vec<RamRegion>>,
844}
845
846#[derive(Debug, Copy, Clone, PartialEq, Eq)]
848pub enum RamVisibility {
849 Unmapped,
851 ReadOnly,
855 ReadWrite,
857}
858
859#[derive(Debug, Error)]
861pub enum RamVisibilityError {
862 #[error("{0} is not a controllable RAM range")]
864 InvalidRange(MemoryRange),
865 #[error("failed to map RAM range {range}")]
867 Map {
868 range: MemoryRange,
870 #[source]
872 error: mesh::error::RemoteError,
873 },
874}
875
876impl RamVisibilityControl {
877 pub async fn set_ram_visibility(
884 &self,
885 range: MemoryRange,
886 visibility: RamVisibility,
887 ) -> Result<(), RamVisibilityError> {
888 let region = self
889 .regions
890 .iter()
891 .find(|region| region.range == range)
892 .ok_or(RamVisibilityError::InvalidRange(range))?;
893
894 match visibility {
895 RamVisibility::ReadWrite | RamVisibility::ReadOnly => {
896 region
897 .handle
898 .map(MapParams {
899 writable: matches!(visibility, RamVisibility::ReadWrite),
900 executable: true,
901 prefetch: false,
902 })
903 .await
904 .map_err(|error| RamVisibilityError::Map { range, error })?;
905 }
906 RamVisibility::Unmapped => region.handle.unmap().await,
907 }
908 Ok(())
909 }
910}
911
912#[cfg(test)]
913mod tests {
914 use super::*;
915 use pal_async::async_test;
916 use std::error::Error as _;
917
918 async fn build_and_get_memory(
921 backing_ranges: &[&[MemoryRange]],
922 ) -> (GuestMemoryManager, GuestMemory) {
923 let max_addr = backing_ranges
924 .iter()
925 .flat_map(|ranges| ranges.iter())
926 .map(|r| r.end())
927 .max()
928 .unwrap_or(0);
929
930 let mut builder = GuestMemoryBuilder::new();
931 for ranges in backing_ranges {
932 builder = builder.add_backing(RamBackingRequest::new(ranges.to_vec()));
933 }
934 let mgr = builder.build(max_addr).await.unwrap();
935 let gm = mgr.client().guest_memory().await.unwrap();
936 (mgr, gm)
937 }
938
939 #[async_test]
940 async fn test_hugepages_with_existing_backing_rejected() {
941 const SIZE: u64 = 2 * 1024 * 1024;
942 let mappable = sparse_mmap::alloc_shared_memory(SIZE as usize, "test").unwrap();
943 let backing = RamBackingRequest::new(vec![MemoryRange::new(0..SIZE)])
944 .hugepages(None)
945 .existing_mappable(mappable.into());
946 let err = GuestMemoryBuilder::new()
947 .add_backing(backing)
948 .build(SIZE)
949 .await
950 .unwrap_err();
951 assert!(matches!(
952 err,
953 MemoryBuildError::HugepagesWithExistingBacking
954 ));
955 }
956
957 #[test]
958 fn test_validate_hugepage_size() {
959 let page_size = SparseMapping::page_size() as u64;
960 assert!(validate_hugepage_size(page_size).is_ok());
961 assert!(matches!(
962 validate_hugepage_size(page_size / 2),
963 Err(MemoryBuildError::InvalidHugepageSize(_))
964 ));
965 assert!(matches!(
966 validate_hugepage_size(3 * 1024 * 1024),
967 Err(MemoryBuildError::InvalidHugepageSize(_))
968 ));
969 }
970
971 #[test]
972 fn test_validate_hugepage_ram_alignment() {
973 const HUGEPAGE_SIZE: u64 = 2 * 1024 * 1024;
974
975 validate_hugepage_ram_alignment(
976 4 * 1024 * 1024,
977 &[
978 MemoryRange::new(0..HUGEPAGE_SIZE),
979 MemoryRange::new(2 * HUGEPAGE_SIZE..3 * HUGEPAGE_SIZE),
980 ],
981 HUGEPAGE_SIZE,
982 )
983 .unwrap();
984
985 assert!(matches!(
986 validate_hugepage_ram_alignment(3 * 1024 * 1024, &[], HUGEPAGE_SIZE),
987 Err(MemoryBuildError::HugepageRamSizeUnaligned { .. })
988 ));
989 assert!(matches!(
990 validate_hugepage_ram_alignment(
991 HUGEPAGE_SIZE,
992 &[MemoryRange::new(0..1024 * 1024)],
993 HUGEPAGE_SIZE,
994 ),
995 Err(MemoryBuildError::HugepageRamRangeUnaligned { .. })
996 ));
997 }
998
999 #[test]
1000 fn test_hugepage_ram_size_alignment_error_message() {
1001 let error =
1002 validate_hugepage_ram_alignment(257 * 1024 * 1024, &[], 2 * 1024 * 1024).unwrap_err();
1003
1004 assert_eq!(
1005 error.to_string(),
1006 "RAM size 257 MB is not aligned to 2 MB hugepages; choose a memory size that is a multiple of the hugepage size"
1007 );
1008 }
1009
1010 #[test]
1011 fn test_hugepage_ram_range_alignment_error_message() {
1012 let error = validate_hugepage_ram_alignment(
1013 2 * 1024 * 1024,
1014 &[MemoryRange::new(0..1024 * 1024)],
1015 2 * 1024 * 1024,
1016 )
1017 .unwrap_err();
1018
1019 assert_eq!(
1020 error.to_string(),
1021 "RAM range 0x0-0x100000 (1 MB) is not aligned to 2 MB hugepages; range start and size must both be multiples of the hugepage size"
1022 );
1023 }
1024
1025 #[test]
1026 fn test_hugepage_allocation_error_message() {
1027 let error = MemoryBuildError::HugepageAllocationFailed {
1028 size: MemorySize(1024 * 1024 * 1024),
1029 hugepage_size: MemorySize(2 * 1024 * 1024),
1030 page_count: 512,
1031 error: io::Error::new(io::ErrorKind::OutOfMemory, "Cannot allocate memory"),
1032 };
1033
1034 assert_eq!(
1035 error.to_string(),
1036 "failed to reserve 512 hugetlb pages of 2 MB each (1 GB total); increase the hugetlb pool or reduce guest memory size"
1037 );
1038 assert_eq!(
1039 error.source().unwrap().to_string(),
1040 "Cannot allocate memory"
1041 );
1042 }
1043
1044 #[test]
1045 fn test_single_backing() {
1046 DefaultPool::run_with(|_| async {
1047 let page = SparseMapping::page_size() as u64;
1048 let r = MemoryRange::new(0..4 * page);
1049 let (_mgr, gm) = build_and_get_memory(&[&[r]]).await;
1050
1051 let pattern = vec![0xAB; page as usize];
1052 gm.write_at(0, &pattern).unwrap();
1053 let mut buf = vec![0u8; page as usize];
1054 gm.read_at(0, &mut buf).unwrap();
1055 assert_eq!(buf, pattern);
1056
1057 gm.read_at(page, &mut buf).unwrap();
1059 assert_eq!(buf, vec![0u8; page as usize]);
1060 });
1061 }
1062
1063 #[test]
1064 fn test_two_backings() {
1065 DefaultPool::run_with(|_| async {
1066 let page = SparseMapping::page_size() as u64;
1067 let r0 = MemoryRange::new(0..2 * page);
1068 let r1 = MemoryRange::new(2 * page..4 * page);
1069 let (_mgr, gm) = build_and_get_memory(&[&[r0], &[r1]]).await;
1070
1071 let pattern_a = vec![0xAA; page as usize];
1073 let pattern_b = vec![0xBB; page as usize];
1074 gm.write_at(0, &pattern_a).unwrap();
1075 gm.write_at(2 * page, &pattern_b).unwrap();
1076
1077 let mut buf = vec![0u8; page as usize];
1078 gm.read_at(0, &mut buf).unwrap();
1079 assert_eq!(buf, pattern_a, "backing 0 should have pattern_a");
1080
1081 gm.read_at(2 * page, &mut buf).unwrap();
1082 assert_eq!(buf, pattern_b, "backing 1 should have pattern_b");
1083
1084 gm.read_at(page, &mut buf).unwrap();
1086 assert_eq!(buf, vec![0u8; page as usize]);
1087 gm.read_at(3 * page, &mut buf).unwrap();
1088 assert_eq!(buf, vec![0u8; page as usize]);
1089 });
1090 }
1091
1092 #[test]
1093 fn test_two_backings_different_sizes() {
1094 DefaultPool::run_with(|_| async {
1095 let page = SparseMapping::page_size() as u64;
1096 let r0 = MemoryRange::new(0..page);
1097 let r1 = MemoryRange::new(page..4 * page);
1098 let (_mgr, gm) = build_and_get_memory(&[&[r0], &[r1]]).await;
1099
1100 let pattern_a = vec![0x11; page as usize];
1101 let pattern_b = vec![0x22; page as usize];
1102 gm.write_at(0, &pattern_a).unwrap();
1103 gm.write_at(page, &pattern_b).unwrap();
1104
1105 let mut buf = vec![0u8; page as usize];
1106 gm.read_at(0, &mut buf).unwrap();
1107 assert_eq!(buf, pattern_a);
1108 gm.read_at(page, &mut buf).unwrap();
1109 assert_eq!(buf, pattern_b);
1110
1111 let pattern_c = vec![0x33; page as usize];
1113 gm.write_at(3 * page, &pattern_c).unwrap();
1114 gm.read_at(3 * page, &mut buf).unwrap();
1115 assert_eq!(buf, pattern_c);
1116
1117 gm.read_at(2 * page, &mut buf).unwrap();
1119 assert_eq!(buf, vec![0u8; page as usize]);
1120 });
1121 }
1122
1123 #[test]
1124 fn test_two_backings_with_gap() {
1125 DefaultPool::run_with(|_| async {
1126 let page = SparseMapping::page_size() as u64;
1127 let r0 = MemoryRange::new(0..2 * page);
1128 let r1 = MemoryRange::new(4 * page..6 * page);
1129
1130 let mgr = GuestMemoryBuilder::new()
1131 .add_backing(RamBackingRequest::new(vec![r0]))
1132 .add_backing(RamBackingRequest::new(vec![r1]))
1133 .build(r1.end())
1134 .await
1135 .unwrap();
1136 let gm = mgr.client().guest_memory().await.unwrap();
1137
1138 let pattern_a = vec![0xCC; page as usize];
1139 let pattern_b = vec![0xDD; page as usize];
1140 gm.write_at(0, &pattern_a).unwrap();
1141 gm.write_at(4 * page, &pattern_b).unwrap();
1142
1143 let mut buf = vec![0u8; page as usize];
1144 gm.read_at(0, &mut buf).unwrap();
1145 assert_eq!(buf, pattern_a);
1146 gm.read_at(4 * page, &mut buf).unwrap();
1147 assert_eq!(buf, pattern_b);
1148 });
1149 }
1150
1151 async fn build_thp_primary_memory(size: u64) -> (GuestMemoryManager, GuestMemory) {
1158 let mgr = GuestMemoryBuilder::new()
1159 .add_backing(
1160 RamBackingRequest::new(vec![MemoryRange::new(0..size)]).transparent_hugepages(true),
1161 )
1162 .build(size)
1163 .await
1164 .unwrap();
1165 let primary = GuestMemory::new("test-primary", mgr.va_mapper.clone());
1166 (mgr, primary)
1167 }
1168
1169 #[async_test]
1178 async fn test_lock_for_write_makes_page_writable() {
1179 use std::sync::atomic::Ordering;
1180
1181 const SIZE: u64 = 2 * 1024 * 1024;
1182 let (_mgr, gm) = build_thp_primary_memory(SIZE).await;
1183
1184 let locked = gm
1185 .lock_gpns(guestmem::AccessType::Write, false, &[0])
1186 .unwrap();
1187 locked.pages()[0][0].store(0xAB, Ordering::SeqCst);
1190 locked.pages()[0][1].store(0xCD, Ordering::SeqCst);
1191 drop(locked);
1192
1193 assert_eq!(gm.read_plain::<u8>(0).unwrap(), 0xAB);
1195 assert_eq!(gm.read_plain::<u8>(1).unwrap(), 0xCD);
1196 }
1197
1198 #[async_test]
1201 async fn test_lock_for_read_succeeds() {
1202 use std::sync::atomic::Ordering;
1203
1204 const SIZE: u64 = 2 * 1024 * 1024;
1205 let (_mgr, gm) = build_thp_primary_memory(SIZE).await;
1206
1207 let locked = gm
1208 .lock_gpns(guestmem::AccessType::Read, false, &[0])
1209 .unwrap();
1210 assert_eq!(locked.pages()[0][0].load(Ordering::SeqCst), 0);
1211 drop(locked);
1212 }
1213}