Skip to main content

rvm_memory/
region.rs

1//! Region management for guest physical address space (ADR-136, ADR-138).
2//!
3//! A `RegionManager` maintains a fixed-capacity table of `OwnedRegion` entries,
4//! each mapping a contiguous range of guest physical addresses to host physical
5//! addresses with associated metadata (tier, permissions, ownership).
6//!
7//! ## Design Principles
8//!
9//! - **Move semantics**: Region transfer conceptually moves ownership from one
10//!   partition to another. The old entry is invalidated and a new entry is created.
11//! - **Bounds checking**: All operations validate that addresses and page counts
12//!   do not exceed the region's bounds.
13//! - **Overlap detection**: Creating a region that overlaps an existing one in the
14//!   same partition is rejected.
15
16use rvm_types::{GuestPhysAddr, OwnedRegionId, PartitionId, PhysAddr, RvmError, RvmResult};
17
18use crate::tier::Tier;
19use crate::{MemoryPermissions, PAGE_SIZE};
20
21/// An owned memory region entry in the region table.
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub struct OwnedRegion {
24    /// Unique region identifier.
25    pub id: OwnedRegionId,
26    /// Owning partition.
27    pub owner: PartitionId,
28    /// Guest physical base address (page-aligned).
29    pub guest_base: GuestPhysAddr,
30    /// Host physical base address (page-aligned).
31    pub host_base: PhysAddr,
32    /// Number of pages in this region.
33    pub page_count: u32,
34    /// Current memory tier.
35    pub tier: Tier,
36    /// Access permissions.
37    pub permissions: MemoryPermissions,
38    /// Whether this slot is occupied.
39    occupied: bool,
40}
41
42impl OwnedRegion {
43    /// An empty (unoccupied) region slot.
44    const EMPTY: Self = Self {
45        id: OwnedRegionId::new(0),
46        owner: PartitionId::new(0),
47        guest_base: GuestPhysAddr::new(0),
48        host_base: PhysAddr::new(0),
49        page_count: 0,
50        tier: Tier::Warm,
51        permissions: MemoryPermissions::READ_ONLY,
52        occupied: false,
53    };
54
55    /// Return the size of this region in bytes.
56    #[must_use]
57    pub const fn size_bytes(&self) -> u64 {
58        self.page_count as u64 * PAGE_SIZE as u64
59    }
60
61    /// Return the guest physical end address (exclusive).
62    #[must_use]
63    pub const fn guest_end(&self) -> u64 {
64        self.guest_base.as_u64() + self.size_bytes()
65    }
66
67    /// Return the host physical end address (exclusive).
68    #[must_use]
69    pub const fn host_end(&self) -> u64 {
70        self.host_base.as_u64() + self.size_bytes()
71    }
72
73    /// Check if a guest physical address falls within this region.
74    #[must_use]
75    pub const fn contains_guest(&self, addr: GuestPhysAddr) -> bool {
76        addr.as_u64() >= self.guest_base.as_u64() && addr.as_u64() < self.guest_end()
77    }
78}
79
80/// Configuration for creating a new region.
81#[derive(Debug, Clone, Copy)]
82pub struct RegionConfig {
83    /// Unique region identifier.
84    pub id: OwnedRegionId,
85    /// Owning partition.
86    pub owner: PartitionId,
87    /// Guest physical base address (must be page-aligned).
88    pub guest_base: GuestPhysAddr,
89    /// Host physical base address (must be page-aligned).
90    pub host_base: PhysAddr,
91    /// Number of pages.
92    pub page_count: u32,
93    /// Initial memory tier.
94    pub tier: Tier,
95    /// Access permissions.
96    pub permissions: MemoryPermissions,
97}
98
99/// Guest-to-host address mapping entry.
100#[derive(Debug, Clone, Copy)]
101pub struct AddressMapping {
102    /// Guest physical address.
103    pub guest: GuestPhysAddr,
104    /// Corresponding host physical address.
105    pub host: PhysAddr,
106    /// Permissions for this mapping.
107    pub permissions: MemoryPermissions,
108}
109
110/// Manages a fixed-capacity table of owned memory regions.
111///
112/// `MAX` is the compile-time upper bound on the number of regions.
113pub struct RegionManager<const MAX: usize> {
114    /// The region table.
115    regions: [OwnedRegion; MAX],
116    /// Number of occupied slots.
117    count: usize,
118    /// Next region ID to assign (monotonically increasing).
119    next_id: u64,
120}
121
122impl<const MAX: usize> Default for RegionManager<MAX> {
123    fn default() -> Self {
124        Self::new()
125    }
126}
127
128impl<const MAX: usize> RegionManager<MAX> {
129    /// Create a new empty region manager.
130    #[must_use]
131    pub const fn new() -> Self {
132        Self {
133            regions: [OwnedRegion::EMPTY; MAX],
134            count: 0,
135            next_id: 1,
136        }
137    }
138
139    /// Return the number of active regions.
140    #[must_use]
141    pub const fn count(&self) -> usize {
142        self.count
143    }
144
145    /// Return the maximum capacity.
146    #[must_use]
147    pub const fn capacity(&self) -> usize {
148        MAX
149    }
150
151    /// Create a new memory region from the given configuration.
152    ///
153    /// Validates alignment, non-zero page count, and overlap with existing
154    /// regions in the same partition.
155    ///
156    /// # Errors
157    ///
158    /// Returns [`RvmError::AlignmentError`] if addresses are not page-aligned.
159    /// Returns [`RvmError::ResourceLimitExceeded`] if page count is zero or
160    /// the manager is at capacity.
161    /// Returns [`RvmError::MemoryOverlap`] if the region overlaps an existing
162    /// region in the same partition.
163    pub fn create(&mut self, config: RegionConfig) -> RvmResult<OwnedRegionId> {
164        // Validate alignment.
165        if !config.guest_base.is_page_aligned() {
166            return Err(RvmError::AlignmentError);
167        }
168        if !config.host_base.is_page_aligned() {
169            return Err(RvmError::AlignmentError);
170        }
171        if config.page_count == 0 {
172            return Err(RvmError::ResourceLimitExceeded);
173        }
174
175        // Check capacity.
176        if self.count >= MAX {
177            return Err(RvmError::ResourceLimitExceeded);
178        }
179
180        // Combined single-pass: check for overlap AND find the first free slot.
181        let new_start = config.guest_base.as_u64();
182        let new_end = new_start + u64::from(config.page_count) * PAGE_SIZE as u64;
183        let new_host_start = config.host_base.as_u64();
184        let new_host_end = new_host_start + u64::from(config.page_count) * PAGE_SIZE as u64;
185        let mut first_free_slot: Option<usize> = None;
186
187        for (i, region) in self.regions.iter().enumerate() {
188            if !region.occupied {
189                if first_free_slot.is_none() {
190                    first_free_slot = Some(i);
191                }
192                continue;
193            }
194            // Guest overlap check: only within the same partition.
195            if region.owner == config.owner {
196                let existing_start = region.guest_base.as_u64();
197                let existing_end = region.guest_end();
198                if new_start < existing_end && existing_start < new_end {
199                    return Err(RvmError::MemoryOverlap);
200                }
201            }
202            // Host-physical overlap check: across ALL partitions.
203            // Two partitions mapping the same host physical pages would
204            // break isolation -- a partition could read/write another's memory.
205            let existing_host_start = region.host_base.as_u64();
206            let existing_host_end = region.host_end();
207            if new_host_start < existing_host_end && existing_host_start < new_host_end {
208                return Err(RvmError::MemoryOverlap);
209            }
210        }
211
212        // Use the free slot found during the overlap scan.
213        match first_free_slot {
214            Some(idx) => {
215                self.regions[idx] = OwnedRegion {
216                    id: config.id,
217                    owner: config.owner,
218                    guest_base: config.guest_base,
219                    host_base: config.host_base,
220                    page_count: config.page_count,
221                    tier: config.tier,
222                    permissions: config.permissions,
223                    occupied: true,
224                };
225                self.count += 1;
226                Ok(config.id)
227            }
228            None => Err(RvmError::ResourceLimitExceeded),
229        }
230    }
231
232    /// Allocate a fresh `OwnedRegionId` and create the region.
233    ///
234    /// # Errors
235    ///
236    /// See [`RegionManager::create`] for error conditions.
237    pub fn create_auto_id(
238        &mut self,
239        owner: PartitionId,
240        guest_base: GuestPhysAddr,
241        host_base: PhysAddr,
242        page_count: u32,
243        tier: Tier,
244        permissions: MemoryPermissions,
245    ) -> RvmResult<OwnedRegionId> {
246        let id = OwnedRegionId::new(self.next_id);
247        self.next_id += 1;
248        self.create(RegionConfig {
249            id,
250            owner,
251            guest_base,
252            host_base,
253            page_count,
254            tier,
255            permissions,
256        })
257    }
258
259    /// Destroy a region, freeing its slot.
260    ///
261    /// # Errors
262    ///
263    /// Returns [`RvmError::PartitionNotFound`] if the region does not exist.
264    pub fn destroy(&mut self, region_id: OwnedRegionId) -> RvmResult<OwnedRegion> {
265        match self.find_slot(region_id) {
266            Some(idx) => {
267                let region = self.regions[idx];
268                self.regions[idx] = OwnedRegion::EMPTY;
269                self.count -= 1;
270                Ok(region)
271            }
272            None => Err(RvmError::PartitionNotFound),
273        }
274    }
275
276    /// Transfer ownership of a region to a new partition.
277    ///
278    /// This conceptually moves the region: the old owner loses access and
279    /// the new owner gains it. The guest-physical mapping remains the same
280    /// (the new partition sees the region at the same guest address).
281    ///
282    /// # Errors
283    ///
284    /// Returns [`RvmError::PartitionNotFound`] if the region does not exist.
285    /// Returns [`RvmError::MemoryOverlap`] if the new owner already has a
286    /// region at the same guest address range.
287    pub fn transfer(&mut self, region_id: OwnedRegionId, new_owner: PartitionId) -> RvmResult<()> {
288        let idx = self
289            .find_slot(region_id)
290            .ok_or(RvmError::PartitionNotFound)?;
291
292        // Check that the new owner doesn't have an overlapping region.
293        let r = &self.regions[idx];
294        let xfer_start = r.guest_base.as_u64();
295        let xfer_end = r.guest_end();
296        for (i, region) in self.regions.iter().enumerate() {
297            if i == idx || !region.occupied || region.owner != new_owner {
298                continue;
299            }
300            let existing_start = region.guest_base.as_u64();
301            let existing_end = region.guest_end();
302            if xfer_start < existing_end && existing_start < xfer_end {
303                return Err(RvmError::MemoryOverlap);
304            }
305        }
306
307        self.regions[idx].owner = new_owner;
308        Ok(())
309    }
310
311    /// Look up a region by its identifier.
312    #[must_use]
313    pub fn get(&self, region_id: OwnedRegionId) -> Option<&OwnedRegion> {
314        self.find_slot(region_id).map(|idx| &self.regions[idx])
315    }
316
317    /// Look up a region by its identifier (mutable).
318    pub fn get_mut(&mut self, region_id: OwnedRegionId) -> Option<&mut OwnedRegion> {
319        self.find_slot(region_id).map(|idx| &mut self.regions[idx])
320    }
321
322    /// Translate a guest physical address to a host physical address
323    /// within the given partition.
324    #[must_use]
325    pub fn translate(&self, owner: PartitionId, guest: GuestPhysAddr) -> Option<AddressMapping> {
326        for region in &self.regions {
327            if !region.occupied || region.owner != owner {
328                continue;
329            }
330            if region.contains_guest(guest) {
331                let offset = guest.as_u64() - region.guest_base.as_u64();
332                return Some(AddressMapping {
333                    guest,
334                    host: PhysAddr::new(region.host_base.as_u64() + offset),
335                    permissions: region.permissions,
336                });
337            }
338        }
339        None
340    }
341
342    /// Count how many regions are owned by a given partition.
343    #[must_use]
344    pub fn count_for_partition(&self, owner: PartitionId) -> usize {
345        self.regions
346            .iter()
347            .filter(|r| r.occupied && r.owner == owner)
348            .count()
349    }
350
351    /// Iterate over the region IDs owned by a given partition.
352    /// Writes matching IDs into `out` and returns the count written.
353    pub fn regions_for_partition(&self, owner: PartitionId, out: &mut [OwnedRegionId]) -> usize {
354        let mut written = 0;
355        for region in &self.regions {
356            if written >= out.len() {
357                break;
358            }
359            if region.occupied && region.owner == owner {
360                out[written] = region.id;
361                written += 1;
362            }
363        }
364        written
365    }
366
367    // --- Private helpers ---
368
369    /// Find the slot index for a given region ID.
370    fn find_slot(&self, region_id: OwnedRegionId) -> Option<usize> {
371        self.regions
372            .iter()
373            .position(|r| r.occupied && r.id == region_id)
374    }
375}
376
377#[cfg(test)]
378mod tests {
379    use super::*;
380
381    fn pid(id: u32) -> PartitionId {
382        PartitionId::new(id)
383    }
384
385    fn rid(id: u64) -> OwnedRegionId {
386        OwnedRegionId::new(id)
387    }
388
389    fn gpa(addr: u64) -> GuestPhysAddr {
390        GuestPhysAddr::new(addr)
391    }
392
393    fn pa(addr: u64) -> PhysAddr {
394        PhysAddr::new(addr)
395    }
396
397    fn default_config(id: u64, owner: u32, guest: u64, host: u64) -> RegionConfig {
398        RegionConfig {
399            id: rid(id),
400            owner: pid(owner),
401            guest_base: gpa(guest),
402            host_base: pa(host),
403            page_count: 4,
404            tier: Tier::Warm,
405            permissions: MemoryPermissions::READ_WRITE,
406        }
407    }
408
409    #[test]
410    fn create_and_get() {
411        let mut mgr = RegionManager::<8>::new();
412        let config = default_config(1, 1, 0x1000, 0x2000_0000);
413        let id = mgr.create(config).unwrap();
414        assert_eq!(id, rid(1));
415        assert_eq!(mgr.count(), 1);
416
417        let region = mgr.get(id).unwrap();
418        assert_eq!(region.owner, pid(1));
419        assert_eq!(region.guest_base, gpa(0x1000));
420        assert_eq!(region.host_base, pa(0x2000_0000));
421        assert_eq!(region.page_count, 4);
422        assert_eq!(region.tier, Tier::Warm);
423    }
424
425    #[test]
426    fn create_unaligned_guest_fails() {
427        let mut mgr = RegionManager::<8>::new();
428        let config = RegionConfig {
429            guest_base: gpa(0x1001), // Not page-aligned
430            ..default_config(1, 1, 0x1000, 0x2000_0000)
431        };
432        assert_eq!(mgr.create(config), Err(RvmError::AlignmentError));
433    }
434
435    #[test]
436    fn create_unaligned_host_fails() {
437        let mut mgr = RegionManager::<8>::new();
438        let config = RegionConfig {
439            host_base: pa(0x2000_0001), // Not page-aligned
440            ..default_config(1, 1, 0x1000, 0x2000_0000)
441        };
442        assert_eq!(mgr.create(config), Err(RvmError::AlignmentError));
443    }
444
445    #[test]
446    fn create_zero_pages_fails() {
447        let mut mgr = RegionManager::<8>::new();
448        let config = RegionConfig {
449            page_count: 0,
450            ..default_config(1, 1, 0x1000, 0x2000_0000)
451        };
452        assert_eq!(mgr.create(config), Err(RvmError::ResourceLimitExceeded));
453    }
454
455    #[test]
456    fn create_at_capacity_fails() {
457        let mut mgr = RegionManager::<2>::new();
458        mgr.create(default_config(1, 1, 0x1000, 0x1_0000)).unwrap();
459        mgr.create(default_config(2, 2, 0x1000, 0x2_0000)).unwrap();
460        assert_eq!(
461            mgr.create(default_config(3, 3, 0x1000, 0x3_0000)),
462            Err(RvmError::ResourceLimitExceeded)
463        );
464    }
465
466    #[test]
467    fn overlap_same_partition_fails() {
468        let mut mgr = RegionManager::<8>::new();
469        // Region 1: pages at guest 0x1000..0x5000 (4 pages).
470        mgr.create(default_config(1, 1, 0x1000, 0x1_0000)).unwrap();
471        // Region 2: pages at guest 0x3000..0x7000 -- overlaps.
472        assert_eq!(
473            mgr.create(default_config(2, 1, 0x3000, 0x2_0000)),
474            Err(RvmError::MemoryOverlap)
475        );
476    }
477
478    #[test]
479    fn no_overlap_different_partitions() {
480        let mut mgr = RegionManager::<8>::new();
481        // Same guest range but different owners AND different host ranges -- no overlap.
482        mgr.create(default_config(1, 1, 0x1000, 0x1_0000)).unwrap();
483        mgr.create(default_config(2, 2, 0x1000, 0x2_0000)).unwrap();
484        assert_eq!(mgr.count(), 2);
485    }
486
487    #[test]
488    fn host_overlap_cross_partition_rejected() {
489        let mut mgr = RegionManager::<8>::new();
490        // Different owners but SAME host physical range -- must be rejected.
491        mgr.create(default_config(1, 1, 0x1000, 0x10_0000)).unwrap();
492        assert_eq!(
493            mgr.create(default_config(2, 2, 0x5000, 0x10_0000)),
494            Err(RvmError::MemoryOverlap)
495        );
496    }
497
498    #[test]
499    fn destroy_region() {
500        let mut mgr = RegionManager::<8>::new();
501        let id = mgr.create(default_config(1, 1, 0x1000, 0x1_0000)).unwrap();
502        let destroyed = mgr.destroy(id).unwrap();
503        assert_eq!(destroyed.id, rid(1));
504        assert_eq!(mgr.count(), 0);
505        assert!(mgr.get(id).is_none());
506    }
507
508    #[test]
509    fn destroy_nonexistent_fails() {
510        let mut mgr = RegionManager::<8>::new();
511        assert_eq!(mgr.destroy(rid(99)), Err(RvmError::PartitionNotFound));
512    }
513
514    #[test]
515    fn transfer_ownership() {
516        let mut mgr = RegionManager::<8>::new();
517        let id = mgr.create(default_config(1, 1, 0x1000, 0x1_0000)).unwrap();
518        assert_eq!(mgr.get(id).unwrap().owner, pid(1));
519
520        mgr.transfer(id, pid(2)).unwrap();
521        assert_eq!(mgr.get(id).unwrap().owner, pid(2));
522    }
523
524    #[test]
525    fn transfer_overlap_fails() {
526        let mut mgr = RegionManager::<8>::new();
527        let id = mgr.create(default_config(1, 1, 0x1000, 0x1_0000)).unwrap();
528        // Partition 2 already has a region at the same guest range.
529        mgr.create(default_config(2, 2, 0x1000, 0x2_0000)).unwrap();
530        assert_eq!(mgr.transfer(id, pid(2)), Err(RvmError::MemoryOverlap));
531    }
532
533    #[test]
534    fn translate_guest_to_host() {
535        let mut mgr = RegionManager::<8>::new();
536        // Region at guest 0x1000, host 0x2000_0000, 4 pages (16 KiB).
537        mgr.create(default_config(1, 1, 0x1000, 0x2000_0000))
538            .unwrap();
539
540        // Translate guest 0x1000 (start of region).
541        let m = mgr.translate(pid(1), gpa(0x1000)).unwrap();
542        assert_eq!(m.host, pa(0x2000_0000));
543
544        // Translate guest 0x2000 (offset 0x1000 into region).
545        let m = mgr.translate(pid(1), gpa(0x2000)).unwrap();
546        assert_eq!(m.host, pa(0x2000_1000));
547
548        // Translate guest 0x5000 (past end of region) -- should return None.
549        assert!(mgr.translate(pid(1), gpa(0x5000)).is_none());
550
551        // Translate in wrong partition -- should return None.
552        assert!(mgr.translate(pid(2), gpa(0x1000)).is_none());
553    }
554
555    #[test]
556    fn region_contains_guest() {
557        let region = OwnedRegion {
558            id: rid(1),
559            owner: pid(1),
560            guest_base: gpa(0x1000),
561            host_base: pa(0x2000_0000),
562            page_count: 4,
563            tier: Tier::Warm,
564            permissions: MemoryPermissions::READ_WRITE,
565            occupied: true,
566        };
567        // 4 pages = 0x4000 bytes. Range: [0x1000, 0x5000).
568        assert!(region.contains_guest(gpa(0x1000)));
569        assert!(region.contains_guest(gpa(0x4FFF)));
570        assert!(!region.contains_guest(gpa(0x5000)));
571        assert!(!region.contains_guest(gpa(0x0FFF)));
572    }
573
574    #[test]
575    fn create_auto_id() {
576        let mut mgr = RegionManager::<8>::new();
577        let id1 = mgr
578            .create_auto_id(
579                pid(1),
580                gpa(0x1000),
581                pa(0x1_0000),
582                4,
583                Tier::Warm,
584                MemoryPermissions::READ_WRITE,
585            )
586            .unwrap();
587        let id2 = mgr
588            .create_auto_id(
589                pid(1),
590                gpa(0x5000),
591                pa(0x2_0000),
592                2,
593                Tier::Hot,
594                MemoryPermissions::READ_ONLY,
595            )
596            .unwrap();
597        assert_ne!(id1, id2);
598        assert_eq!(mgr.count(), 2);
599    }
600
601    #[test]
602    fn count_for_partition() {
603        let mut mgr = RegionManager::<8>::new();
604        mgr.create(default_config(1, 1, 0x1000, 0x1_0000)).unwrap();
605        mgr.create(default_config(2, 1, 0x5000, 0x2_0000)).unwrap();
606        mgr.create(default_config(3, 2, 0x1000, 0x3_0000)).unwrap();
607
608        assert_eq!(mgr.count_for_partition(pid(1)), 2);
609        assert_eq!(mgr.count_for_partition(pid(2)), 1);
610        assert_eq!(mgr.count_for_partition(pid(3)), 0);
611    }
612
613    #[test]
614    fn regions_for_partition() {
615        let mut mgr = RegionManager::<8>::new();
616        mgr.create(default_config(1, 1, 0x1000, 0x1_0000)).unwrap();
617        mgr.create(default_config(2, 1, 0x5000, 0x2_0000)).unwrap();
618        mgr.create(default_config(3, 2, 0x1000, 0x3_0000)).unwrap();
619
620        let mut buf = [OwnedRegionId::new(0); 4];
621        let n = mgr.regions_for_partition(pid(1), &mut buf);
622        assert_eq!(n, 2);
623        assert!(buf[..n].contains(&rid(1)));
624        assert!(buf[..n].contains(&rid(2)));
625    }
626
627    #[test]
628    fn destroy_then_create_reuses_slot() {
629        let mut mgr = RegionManager::<2>::new();
630        let id1 = mgr.create(default_config(1, 1, 0x1000, 0x1_0000)).unwrap();
631        mgr.create(default_config(2, 2, 0x1000, 0x2_0000)).unwrap();
632        // At capacity.
633        assert!(mgr.create(default_config(3, 3, 0x1000, 0x3_0000)).is_err());
634
635        // Destroy first, then create should succeed.
636        mgr.destroy(id1).unwrap();
637        mgr.create(default_config(3, 3, 0x1000, 0x3_0000)).unwrap();
638        assert_eq!(mgr.count(), 2);
639    }
640
641    #[test]
642    fn size_bytes_and_ends() {
643        let region = OwnedRegion {
644            id: rid(1),
645            owner: pid(1),
646            guest_base: gpa(0x1000),
647            host_base: pa(0x2000_0000),
648            page_count: 4,
649            tier: Tier::Warm,
650            permissions: MemoryPermissions::READ_WRITE,
651            occupied: true,
652        };
653        assert_eq!(region.size_bytes(), 4 * PAGE_SIZE as u64);
654        assert_eq!(region.guest_end(), 0x1000 + 4 * PAGE_SIZE as u64);
655        assert_eq!(region.host_end(), 0x2000_0000 + 4 * PAGE_SIZE as u64);
656    }
657}