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::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#[derive(Debug, Inspect)]
35pub struct GuestMemoryManager {
36 #[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#[derive(Debug)]
62struct RamBacking {
63 mappable: Option<Mappable>,
65 ranges: Vec<MemoryRange>,
67 prefetch: bool,
69 transparent_hugepages: bool,
71 host_numa_node: Option<u32>,
73}
74
75#[derive(Debug)]
76struct RamRegion {
77 range: MemoryRange,
78 handle: RegionHandle,
79}
80
81#[derive(Error, Debug)]
83pub enum PartitionAttachError {
84 #[error("failed to reserve VA range for partition mapping")]
86 VaMapper(#[source] VaMapperError),
87 #[error("failed to attach partition to memory manager")]
89 PartitionMapper(#[source] crate::partition_mapper::PartitionMapperError),
90}
91
92#[derive(Error, Debug)]
94pub enum MemoryBuildError {
95 #[error("ram size {0} is too large")]
97 RamTooLarge(MemorySize),
98 #[error("failed to allocate memory")]
100 AllocationFailed(#[source] io::Error),
101 #[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 size: MemorySize,
108 hugepage_size: MemorySize,
110 page_count: usize,
112 #[source]
114 error: io::Error,
115 },
116 #[error("failed to create VA mapper")]
118 VaMapper(#[source] VaMapperError),
119 #[error("failed to map RAM range {range}")]
121 RamMapping {
122 range: MemoryRange,
124 #[source]
126 error: mesh::error::RemoteError,
127 },
128 #[error("failed to enable RAM region {range}")]
130 RamRegionEnable {
131 range: MemoryRange,
133 #[source]
135 error: mesh::error::RemoteError,
136 },
137 #[error("not enough guest address space available for the vtl0 alias map")]
139 AliasMapWontFit,
140 #[error("x86 support requires RAM to start at 0 and contain at least 1MB")]
142 InvalidRamForX86,
143 #[error("private memory is incompatible with x86 legacy support")]
145 PrivateMemoryWithLegacy,
146 #[error("private memory is incompatible with an existing memory backing")]
148 PrivateMemoryWithExistingBacking,
149 #[error("transparent huge pages requires private memory mode")]
151 ThpWithoutPrivateMemory,
152 #[error("transparent huge pages is only supported on Linux")]
154 ThpUnsupportedPlatform,
155 #[error("hugepage size {0} is too large")]
157 HugepageSizeTooLarge(MemorySize),
158 #[error("hugepages are only supported on Linux and Windows")]
160 HugepagesUnsupportedPlatform,
161 #[error("host NUMA node binding is only supported on Linux and Windows")]
163 HostNumaNodeUnsupportedPlatform,
164 #[error("hugepages require shared memory mode")]
166 HugepagesWithPrivateMemory,
167 #[error("hugepages are incompatible with existing memory backing")]
169 HugepagesWithExistingBacking,
170 #[error("hugepages are incompatible with x86 legacy RAM splitting")]
172 HugepagesWithLegacy,
173 #[error("hugepage size {0} must be a power of two and at least the host page size")]
175 InvalidHugepageSize(MemorySize),
176 #[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 ram_size: MemorySize,
183 hugepage_size: MemorySize,
185 },
186 #[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 range: MemoryRange,
193 range_size: MemorySize,
195 hugepage_size: MemorySize,
197 },
198}
199
200const DEFAULT_HUGEPAGE_SIZE: u64 = 2 * 1024 * 1024;
201
202#[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 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 pub fn prefetch(mut self, enable: bool) -> Self {
239 self.prefetch = enable;
240 self
241 }
242
243 pub fn private_memory(mut self, enable: bool) -> Self {
245 self.private_memory = enable;
246 self
247 }
248
249 pub fn transparent_hugepages(mut self, enable: bool) -> Self {
251 self.transparent_hugepages = enable;
252 self
253 }
254
255 pub fn hugepages(mut self, size: Option<u64>) -> Self {
258 self.hugepages = true;
259 self.hugepage_size = size;
260 self
261 }
262
263 pub fn existing_mappable(mut self, mappable: Mappable) -> Self {
266 self.existing_mappable = Some(mappable);
267 self
268 }
269
270 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#[derive(Debug, Copy, Clone)]
293pub struct MemorySize(
294 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
340pub 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 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 pub fn vtl0_alias_map(mut self, offset: Option<u64>) -> Self {
363 self.vtl0_alias_map = offset;
364 self
365 }
366
367 pub fn pin_mappings(mut self, enable: bool) -> Self {
371 self.pin_mappings = enable;
372 self
373 }
374
375 pub fn x86_legacy_support(mut self, enable: bool) -> Self {
388 self.x86_legacy_support = enable;
389 self
390 }
391
392 pub fn add_backing(mut self, request: RamBackingRequest) -> Self {
395 self.backing_requests.push(request);
396 self
397 }
398
399 pub async fn build(self, max_addr: u64) -> Result<GuestMemoryManager, MemoryBuildError> {
406 let backing_requests = self.backing_requests;
407
408 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 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 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 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 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 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 prefetch: req.prefetch || (cfg!(windows) && req.hugepages),
544 transparent_hugepages: false,
545 host_numa_node: req.host_numa_node,
546 });
547 }
548
549 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 let mut ram_regions = Vec::new();
576 for backing in &backings {
577 let mut file_offset = 0u64;
578 for range in &backing.ranges {
579 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 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#[derive(Debug, MeshPayload)]
676pub struct SharedMemoryBacking {
677 guest_ram: Mappable,
678}
679
680impl SharedMemoryBacking {
681 pub fn from_mappable(guest_ram: Mappable) -> Self {
683 Self { guest_ram }
684 }
685
686 pub fn into_mappable(self) -> Mappable {
688 self.guest_ram
689 }
690}
691
692#[derive(Debug, MeshPayload)]
694pub struct GuestMemoryClient {
695 mapping_manager: MappingManagerClient,
696}
697
698impl GuestMemoryClient {
699 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
713const RAM_PRIORITY: u8 = 255;
715
716const DEVICE_PRIORITY: u8 = 0;
718
719impl GuestMemoryManager {
720 pub fn client(&self) -> GuestMemoryClient {
722 GuestMemoryClient {
723 mapping_manager: self.mapping_manager.client().clone(),
724 }
725 }
726
727 pub fn device_memory_mapper(&self) -> DeviceMemoryMapper {
729 DeviceMemoryMapper::new(self.region_manager.client().clone())
730 }
731
732 pub fn dma_mapper_client(&self) -> crate::region_manager::DmaMapperClient {
734 crate::region_manager::DmaMapperClient::new(self.region_manager.client())
735 }
736
737 pub fn ram_visibility_control(&self) -> RamVisibilityControl {
740 RamVisibilityControl {
741 regions: self.ram_regions.clone(),
742 }
743 }
744
745 pub fn shared_memory_backing(&self) -> Option<SharedMemoryBacking> {
758 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 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#[derive(Clone)]
818pub struct RamVisibilityControl {
819 regions: Arc<Vec<RamRegion>>,
820}
821
822#[derive(Debug, Copy, Clone, PartialEq, Eq)]
824pub enum RamVisibility {
825 Unmapped,
827 ReadOnly,
831 ReadWrite,
833}
834
835#[derive(Debug, Error)]
837pub enum RamVisibilityError {
838 #[error("{0} is not a controllable RAM range")]
840 InvalidRange(MemoryRange),
841 #[error("failed to map RAM range {range}")]
843 Map {
844 range: MemoryRange,
846 #[source]
848 error: mesh::error::RemoteError,
849 },
850}
851
852impl RamVisibilityControl {
853 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 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 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 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 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 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 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}