Skip to main content

membacking/
region_manager.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Implements the region manager, which tracks regions and their mappings, as
5//! well as partitions to map the regions into.
6
7// UNSAFETY: Calling unsafe DmaTarget::map_dma with validated VA pointers.
8#![expect(unsafe_code)]
9
10use crate::mapping_manager::Mappable;
11use crate::mapping_manager::MappingBacking;
12use crate::mapping_manager::MappingManagerClient;
13use crate::mapping_manager::MappingParams;
14use crate::mapping_manager::MemoryPolicy;
15use crate::mapping_manager::VaMapper;
16use crate::partition_mapper::PartitionMapper;
17use anyhow::Context as _;
18use futures::StreamExt;
19use inspect::Inspect;
20use inspect::InspectMut;
21use memory_range::MemoryRange;
22use mesh::MeshPayload;
23use mesh::error::RemoteError;
24use mesh::rpc::FailableRpc;
25use mesh::rpc::Rpc;
26use mesh::rpc::RpcSend;
27use pal_async::task::Spawn;
28use std::cmp::Ordering;
29use std::sync::Arc;
30use thiserror::Error;
31use vmcore::local_only::LocalOnly;
32
33/// The type of memory backing a region or mapping.
34#[derive(Debug, Copy, Clone, PartialEq, Eq, Inspect, MeshPayload)]
35pub enum MappingType {
36    /// Guest RAM or similar shareable memory. IOMMU mapping failures are
37    /// fatal. Exposed via `GuestMemorySharing` (vhost-user).
38    Ram,
39    /// Device memory (e.g., a PCI BAR). IOMMU mapping failures are
40    /// non-fatal — they only affect peer-to-peer DMA to this region.
41    /// Not exposed via `GuestMemorySharing`.
42    Device,
43}
44
45/// Parameters for a DMA mapping request.
46pub struct DmaMapRequest<'a> {
47    /// The guest physical address range to map.
48    pub range: MemoryRange,
49    /// Host virtual address of the mapping.
50    pub host_va: *const u8,
51    /// The backing object (fd or handle) for the mapping, when one exists.
52    /// `None` for anonymous/private RAM, which has no backing fd and is
53    /// mapped purely by host VA.
54    pub mappable: Option<&'a Mappable>,
55    /// Offset within `mappable` where the mapping starts. Ignored (and always
56    /// zero) when `mappable` is `None`, since private/anonymous RAM has no
57    /// backing object to offset into.
58    pub file_offset: u64,
59    /// Whether the mapping should allow writes. When `false`, the IOMMU
60    /// entry should be read-only.
61    pub writable: bool,
62    /// The type of memory being mapped.
63    pub mapping_type: MappingType,
64}
65
66/// A consumer of IOMMU-granularity DMA mapping events.
67///
68/// Unlike [`PartitionMemoryMap`](virt::PartitionMemoryMap), which maps entire
69/// regions by VA pointer for lazy SLAT resolution, this trait receives
70/// individual sub-mapping events with the backing fd + offset, suitable for
71/// explicit IOMMU programming (VFIO type1, iommufd, etc.).
72///
73/// DMA targets receive notifications for **all** active sub-mappings,
74/// including device BAR memory ([`MappingType::Device`] regions). The
75/// mapping type controls whether a region is exposed via
76/// `GuestMemorySharing` (for vhost-user) and whether IOMMU mapping
77/// failures are fatal; IOMMU consumers need the full GPA→backing map
78/// to program identity mappings for all guest-visible memory.
79///
80/// Implementations must be `Send + Sync` because they are stored behind `Arc`
81/// in the region manager task.
82pub trait DmaTarget: Send + Sync {
83    /// Program an IOMMU mapping.
84    ///
85    /// # Safety
86    /// `request.host_va` always points to backed memory that must not be
87    /// unmapped for the duration of the resulting IOMMU mapping. The caller
88    /// (the crate-internal `DmaMapper`) guarantees this by holding an
89    /// [`Arc<VaMapper>`] whose mappings are established eagerly by the mapping
90    /// manager. The IOMMU mapping will be torn down (via `unmap_dma`) before
91    /// the `VaMapper` releases the VA range.
92    unsafe fn map_dma(&self, request: DmaMapRequest<'_>) -> anyhow::Result<()>;
93
94    /// Remove IOMMU mappings within `range`.
95    ///
96    /// The region manager may call this with a range that covers multiple
97    /// prior `map_dma` calls (e.g., unmapping an entire region at once even
98    /// though individual sub-mappings were mapped separately). The range
99    /// will always be aligned to mapping boundaries — it will not bisect
100    /// any prior mapping. Gaps within the range (unmapped sub-ranges) are
101    /// expected and must not cause errors.
102    fn unmap_dma(&self, range: MemoryRange) -> anyhow::Result<()>;
103}
104
105/// Wraps a [`DmaTarget`] for use by the region manager.
106///
107/// Holds the [`VaMapper`] used to provide host VA pointers for IOMMU
108/// programming. Mappings in the VaMapper are established eagerly by the
109/// mapping manager.
110struct DmaMapper {
111    id: DmaMapperId,
112    target: Arc<dyn DmaTarget>,
113    va_mapper: Arc<VaMapper>,
114    /// When `true`, every mapping presented to this target must have a backing
115    /// fd. Backing-less mappings (private/anonymous RAM, exposed only by host
116    /// VA) are rejected at registration and mapping-creation time, since such
117    /// a target cannot consume a VA-only mapping.
118    needs_fd: bool,
119}
120
121#[derive(Debug, Copy, Clone, PartialEq, Eq)]
122struct DmaMapperId(u64);
123
124/// A sub-mapping the region manager hands to a [`DmaMapper`]. The host VA is
125/// deliberately absent: it is filled in by [`DmaMapper::map_dma`] from the
126/// mapper's eager `VaMapper`.
127struct SubMapping<'a> {
128    range: MemoryRange,
129    backing: &'a MappingBacking,
130    writable: bool,
131    mapping_type: MappingType,
132}
133
134impl DmaMapper {
135    /// Map a sub-mapping into the IOMMU.
136    fn map_dma(&self, mapping: SubMapping<'_>) -> anyhow::Result<()> {
137        // The region manager always maintains an eager VaMapper, so a host VA
138        // is always available and free to hand out (the backing is already
139        // established).
140        //
141        // SAFETY: range.start() is within the VA reservation, and the eager
142        // mapper has already established the backing.
143        let host_va = unsafe {
144            self.va_mapper
145                .as_ptr()
146                .add(mapping.range.start() as usize)
147                .cast_const()
148        };
149        let request = DmaMapRequest {
150            range: mapping.range,
151            host_va,
152            mappable: mapping.backing.mappable(),
153            file_offset: mapping.backing.file_offset(),
154            writable: mapping.writable,
155            mapping_type: mapping.mapping_type,
156        };
157        // SAFETY: The VaMapper is eager and the mapping has been established.
158        // The VaMapper is held alive by this DmaMapper (via Arc). The IOMMU
159        // mapping will be torn down (via unmap_dma) before the VaMapper
160        // releases the VA range.
161        unsafe { self.target.map_dma(request) }
162    }
163
164    /// Unmap a range from the IOMMU.
165    fn unmap_dma(&self, range: MemoryRange) {
166        if let Err(e) = self.target.unmap_dma(range) {
167            tracing::warn!(
168                error = &*e as &dyn std::error::Error,
169                %range,
170                "DMA unmap failed"
171            );
172        }
173    }
174}
175
176/// The region manager.
177#[derive(Debug, Inspect)]
178pub struct RegionManager {
179    #[inspect(
180        flatten,
181        with = "|x| inspect::send(&x.req_send, RegionRequest::Inspect)"
182    )]
183    client: RegionManagerClient,
184}
185
186/// Provides access to the region manager.
187#[derive(Debug, MeshPayload, Clone)]
188pub struct RegionManagerClient {
189    req_send: mesh::Sender<RegionRequest>,
190}
191
192struct Region {
193    id: RegionId,
194    map_params: Option<MapParams>,
195    is_active: bool,
196    params: RegionParams,
197    mappings: Vec<RegionMapping>,
198}
199
200#[derive(Debug, MeshPayload)]
201struct RegionParams {
202    name: String,
203    range: MemoryRange,
204    priority: u8,
205    /// The type of memory in this region.
206    mapping_type: MappingType,
207}
208
209#[derive(Copy, Clone, Debug, MeshPayload, PartialEq, Eq, Inspect)]
210pub struct MapParams {
211    pub writable: bool,
212    pub executable: bool,
213    pub prefetch: bool,
214}
215
216impl Region {
217    fn active_range(&self) -> Option<MemoryRange> {
218        if self.is_active {
219            Some(self.params.range)
220        } else {
221            None
222        }
223    }
224}
225
226/// The task object for the region manager.
227#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, MeshPayload)]
228pub struct RegionId(u64);
229
230#[derive(InspectMut)]
231struct RegionManagerTask {
232    #[inspect(with = "inspect_regions")]
233    regions: Vec<Region>,
234    #[inspect(skip)]
235    next_region_id: u64,
236    #[inspect(skip)]
237    inner: RegionManagerTaskInner,
238}
239
240fn inspect_regions(regions: &Vec<Region>) -> impl '_ + Inspect {
241    inspect::adhoc(move |req| {
242        let mut resp = req.respond();
243        for region in regions {
244            resp.field(
245                &format!("{}:{}", region.params.range, region.params.name),
246                inspect::adhoc(|req| {
247                    req.respond()
248                        .field("map_params", region.map_params)
249                        .field("is_active", region.is_active)
250                        .field("priority", region.params.priority)
251                        .field(
252                            "mappings",
253                            inspect::adhoc(|req| {
254                                inspect_mappings(req, region.params.range.start(), &region.mappings)
255                            }),
256                        );
257                }),
258            );
259        }
260    })
261}
262
263fn inspect_mappings(req: inspect::Request<'_>, region_start: u64, mappings: &[RegionMapping]) {
264    let mut resp = req.respond();
265    for mapping in mappings {
266        let range = MemoryRange::new(
267            region_start + mapping.params.range_in_region.start()
268                ..region_start + mapping.params.range_in_region.end(),
269        )
270        .to_string();
271
272        resp.field(
273            &range,
274            inspect::adhoc(|req| {
275                req.respond()
276                    .field("writable", mapping.params.writable)
277                    .field("backed_by_fd", mapping.params.backing.mappable().is_some())
278                    .hex("file_offset", mapping.params.backing.file_offset());
279            }),
280        );
281    }
282}
283
284struct RegionManagerTaskInner {
285    partitions: Vec<PartitionMapper>,
286    dma_mappers: Vec<DmaMapper>,
287    next_dma_mapper_id: u64,
288    mapping_manager: MappingManagerClient,
289}
290
291#[derive(MeshPayload)]
292enum RegionRequest {
293    AddRegion(Rpc<RegionParams, Result<RegionId, AddRegionError>>),
294    RemoveRegion(Rpc<RegionId, ()>),
295    MapRegion(FailableRpc<(RegionId, MapParams), ()>),
296    UnmapRegion(Rpc<RegionId, ()>),
297    AddMapping(FailableRpc<(RegionId, RegionMappingParams), ()>),
298    RemoveMappings(Rpc<(RegionId, MemoryRange), ()>),
299    AddPartition(
300        LocalOnly<Rpc<PartitionMapper, Result<(), crate::partition_mapper::PartitionMapperError>>>,
301    ),
302    AddDmaMapper(LocalOnly<Rpc<(Arc<dyn DmaTarget>, bool), anyhow::Result<DmaMapperId>>>),
303    RemoveDmaMapper(LocalOnly<DmaMapperId>),
304    Inspect(inspect::Deferred),
305}
306
307struct RegionMapping {
308    params: RegionMappingParams,
309}
310
311#[derive(MeshPayload)]
312struct RegionMappingParams {
313    range_in_region: MemoryRange,
314    /// How the mapping is backed by host memory. File-backed RAM carries a
315    /// [`Mappable`]; private/anonymous RAM uses [`MappingBacking::Private`],
316    /// which has no backing fd (the mapping manager commits its anonymous
317    /// pages directly and it is exposed to DMA targets only by host VA).
318    backing: MappingBacking,
319    writable: bool,
320    policy: MemoryPolicy,
321}
322
323fn range_within(outer: MemoryRange, inner: MemoryRange) -> MemoryRange {
324    assert!(inner.end() <= outer.len());
325    MemoryRange::new(outer.start() + inner.start()..outer.start() + inner.end())
326}
327
328#[derive(Debug, Error, MeshPayload)]
329pub enum AddRegionError {
330    #[error("memory region {new} overlaps with existing region {existing}")]
331    OverlapError { existing: String, new: String },
332}
333
334impl RegionManagerTask {
335    fn new(mapping_manager: MappingManagerClient) -> Self {
336        Self {
337            regions: Vec::new(),
338            next_region_id: 1,
339            inner: RegionManagerTaskInner {
340                mapping_manager,
341                partitions: Vec::new(),
342                dma_mappers: Vec::new(),
343                next_dma_mapper_id: 0,
344            },
345        }
346    }
347
348    async fn run(&mut self, req_recv: &mut mesh::Receiver<RegionRequest>) {
349        while let Some(req) = req_recv.next().await {
350            match req {
351                RegionRequest::AddMapping(rpc) => {
352                    rpc.handle_failable(async |(id, params)| self.add_mapping(id, params).await)
353                        .await
354                }
355                RegionRequest::RemoveMappings(rpc) => {
356                    rpc.handle(async |(id, range)| self.remove_mappings(id, range).await)
357                        .await
358                }
359                RegionRequest::AddPartition(LocalOnly(rpc)) => {
360                    rpc.handle(async |partition| self.add_partition(partition).await)
361                        .await
362                }
363                RegionRequest::AddDmaMapper(LocalOnly(rpc)) => {
364                    let ((target, needs_fd), rpc) = rpc.split();
365                    let result = self.add_dma_mapper(target, needs_fd).await;
366                    rpc.complete(result);
367                }
368                RegionRequest::RemoveDmaMapper(LocalOnly(id)) => {
369                    self.remove_dma_mapper(id);
370                }
371                RegionRequest::AddRegion(rpc) => rpc.handle_sync(|params| self.add_region(params)),
372                RegionRequest::RemoveRegion(rpc) => {
373                    rpc.handle(async |id| self.unmap_region(id, true).await)
374                        .await
375                }
376                RegionRequest::MapRegion(rpc) => {
377                    rpc.handle_failable(async |(id, params)| self.map_region(id, params).await)
378                        .await
379                }
380                RegionRequest::UnmapRegion(rpc) => {
381                    rpc.handle(async |id| self.unmap_region(id, false).await)
382                        .await
383                }
384                RegionRequest::Inspect(deferred) => {
385                    deferred.inspect(&mut *self);
386                }
387            }
388        }
389    }
390
391    async fn add_partition(
392        &mut self,
393        partition: PartitionMapper,
394    ) -> Result<(), crate::partition_mapper::PartitionMapperError> {
395        // Map existing regions. On failure, all regions will be unmapped by the
396        // region mapper's drop impl, so don't worry about that.
397        for region in &self.regions {
398            if region.is_active {
399                partition
400                    .map_region(region.params.range, region.map_params.unwrap())
401                    .await?;
402            }
403        }
404        self.inner.partitions.push(partition);
405        Ok(())
406    }
407
408    async fn add_dma_mapper(
409        &mut self,
410        target: Arc<dyn DmaTarget>,
411        needs_fd: bool,
412    ) -> anyhow::Result<DmaMapperId> {
413        // A host VA is always available and free to hand out, so always
414        // maintain an eager VaMapper for this target.
415        let va_mapper = self.inner.mapping_manager.new_mapper(true).await?;
416        assert!(
417            va_mapper.is_eager(),
418            "DMA mapper requires an eager VaMapper"
419        );
420
421        // A target that requires a backing fd cannot consume backing-less
422        // mappings (private/anonymous RAM, which is exposed only by host VA).
423        // Reject registration if any such mapping already exists.
424        if needs_fd {
425            if let Some(region) = self.regions.iter().find(|r| {
426                r.mappings
427                    .iter()
428                    .any(|m| m.params.backing.mappable().is_none())
429            }) {
430                anyhow::bail!(
431                    "cannot register a DMA mapper that requires a backing fd: \
432                     region {} contains a mapping with no backing fd (private RAM)",
433                    region.params.range
434                );
435            }
436        }
437
438        let id = DmaMapperId(self.inner.next_dma_mapper_id);
439        self.inner.next_dma_mapper_id += 1;
440
441        let mapper = DmaMapper {
442            id,
443            target,
444            va_mapper,
445            needs_fd,
446        };
447
448        // Replay existing active sub-mappings so the new IOMMU consumer
449        // gets the current state.
450        for region in &self.regions {
451            if region.is_active {
452                for mapping in &region.mappings {
453                    let range = range_within(region.params.range, mapping.params.range_in_region);
454                    let writable = mapping.params.writable && region.map_params.unwrap().writable;
455                    mapper.map_dma(SubMapping {
456                        range,
457                        backing: &mapping.params.backing,
458                        writable,
459                        mapping_type: region.params.mapping_type,
460                    })?;
461                }
462            }
463        }
464
465        self.inner.dma_mappers.push(mapper);
466        Ok(id)
467    }
468
469    fn remove_dma_mapper(&mut self, id: DmaMapperId) {
470        if let Some(pos) = self.inner.dma_mappers.iter().position(|m| m.id == id) {
471            let mapper = &self.inner.dma_mappers[pos];
472            // Unmap all active sub-mappings from this mapper before removing it.
473            for region in &self.regions {
474                if region.is_active {
475                    for mapping in &region.mappings {
476                        let range =
477                            range_within(region.params.range, mapping.params.range_in_region);
478                        mapper.unmap_dma(range);
479                    }
480                }
481            }
482            self.inner.dma_mappers.swap_remove(pos);
483        }
484    }
485
486    fn region_index(&self, id: RegionId) -> usize {
487        self.regions.iter().position(|r| r.id == id).unwrap()
488    }
489
490    fn add_region(&mut self, params: RegionParams) -> Result<RegionId, AddRegionError> {
491        // Ensure that this fully overlaps everything at lower priority, and
492        // everything at higher priority fully overlaps this.
493        let range = params.range;
494        for other_region in &self.regions {
495            let other_range = other_region.params.range;
496            if !range.overlaps(&other_range) {
497                continue;
498            };
499            let ok = match params.priority.cmp(&other_region.params.priority) {
500                Ordering::Less => other_range.contains(&range),
501                Ordering::Equal => other_range == range,
502                Ordering::Greater => range.contains(&other_range),
503            };
504            if !ok {
505                return Err(AddRegionError::OverlapError {
506                    existing: other_region.params.name.clone(),
507                    new: params.name,
508                });
509            }
510        }
511
512        tracing::debug!(
513            range = %params.range,
514            name = params.name,
515            priority = params.priority,
516            "new region"
517        );
518
519        let id = RegionId(self.next_region_id);
520        self.next_region_id += 1;
521        self.regions.push(Region {
522            id,
523            map_params: None,
524            is_active: false,
525            params,
526            mappings: Vec::new(),
527        });
528        Ok(id)
529    }
530
531    /// Enables the highest priority region in `range`. Panics if any regions in
532    /// `range` are already enabled.
533    async fn enable_best_region(&mut self, mut range: MemoryRange) -> anyhow::Result<()> {
534        while !range.is_empty() {
535            // Pick the highest priority region with the lowest startest address
536            // in the range. Since lower priority ranges must be fully contained
537            // in higher priority ones, we can make the chosen region without
538            // overlapping with a higher priority region.
539            if let Some(region) = self
540                .regions
541                .iter_mut()
542                .filter_map(|region| {
543                    region.map_params?;
544                    if !range.contains(&region.params.range) {
545                        assert!(
546                            !range.overlaps(&region.params.range),
547                            "no overlap invariant violated"
548                        );
549                        return None;
550                    }
551                    assert!(!region.is_active);
552                    Some(region)
553                })
554                .min_by_key(|region| {
555                    (
556                        region.params.range.start(),
557                        u8::MAX - region.params.priority,
558                    )
559                })
560            {
561                self.inner.enable_region(region).await?;
562                range = MemoryRange::new(region.params.range.end()..range.end());
563            } else {
564                range = MemoryRange::EMPTY;
565            }
566        }
567        Ok(())
568    }
569
570    async fn map_region(&mut self, id: RegionId, map_params: MapParams) -> anyhow::Result<()> {
571        let index = self.region_index(id);
572        let region = &mut self.regions[index];
573        let range = region.params.range;
574        let priority = region.params.priority;
575        if region.map_params == Some(map_params) {
576            return Ok(());
577        }
578
579        tracing::debug!(
580            name = region.params.name,
581            range = %region.params.range,
582            writable = map_params.writable,
583            "mapping region"
584        );
585
586        // Disable any overlapping active regions if they are lower priority. If
587        // they are higher priority, stop now since the active mappings won't change.
588        let mut enable = true;
589        for (other_index, other_region) in self.regions.iter_mut().enumerate() {
590            if !other_region.is_active || !other_region.params.range.overlaps(&range) {
591                continue;
592            }
593            if other_region.params.priority > priority
594                || (other_region.params.priority == priority && other_index < index)
595            {
596                enable = false;
597            } else {
598                assert!(enable);
599                // Overlay disable: a higher/equal-priority region is taking
600                // over this range, so the disabled region may be re-enabled
601                // later. This is transient.
602                self.inner.disable_region(other_region, true).await;
603            }
604        }
605
606        self.regions[index].map_params = Some(map_params);
607        if enable {
608            self.enable_best_region(range).await?;
609        }
610        Ok(())
611    }
612
613    async fn unmap_region(&mut self, id: RegionId, remove: bool) {
614        let index = self.region_index(id);
615        let region = &mut self.regions[index];
616        tracing::debug!(
617            name = region.params.name,
618            range = %region.params.range,
619            remove,
620            "unmapping region"
621        );
622
623        let active_range = region.is_active.then_some(region.params.range);
624        if active_range.is_some() {
625            // A removal is a permanent teardown; keeping the region registered
626            // (remove == false) is transient, since a re-map can re-enable it.
627            self.inner.disable_region(region, !remove).await;
628        }
629
630        if remove {
631            self.regions.remove(index);
632        } else {
633            region.map_params = None;
634        }
635        if let Some(range) = active_range {
636            self.enable_best_region(range).await.expect(
637                "failed to re-enable region after unmap; \
638                 this should not fail because the region was previously active",
639            );
640        }
641    }
642
643    async fn add_mapping(
644        &mut self,
645        id: RegionId,
646        params: RegionMappingParams,
647    ) -> anyhow::Result<()> {
648        // A backing-less mapping (private/anonymous RAM) is exposed to DMA
649        // targets only by host VA. A target registered with `needs_fd` cannot
650        // consume it, so reject the mapping up front rather than silently
651        // skipping that target.
652        if params.backing.mappable().is_none() && self.inner.dma_mappers.iter().any(|m| m.needs_fd)
653        {
654            anyhow::bail!(
655                "cannot add a mapping with no backing fd: a registered DMA mapper requires one"
656            );
657        }
658
659        let index = self.region_index(id);
660        let region = &mut self.regions[index];
661
662        // TODO: split and remove existing mappings, atomically. This is
663        // technically required by virtiofs DAX support.
664        assert!(
665            !region
666                .mappings
667                .iter()
668                .any(|m| m.params.range_in_region.overlaps(&params.range_in_region))
669        );
670
671        if let Some(region_range) = region.active_range() {
672            let range = range_within(region_range, params.range_in_region);
673            let writable = params.writable && region.map_params.unwrap().writable;
674            // Register the mapping with the mapping manager. File-backed RAM is
675            // mmap'd; private/anonymous RAM has its anonymous pages committed
676            // directly. Either way the eager VaMapper ends up with backed pages
677            // that drive the DMA targets below by host VA.
678            self.inner
679                .mapping_manager
680                .add_mapping(MappingParams {
681                    range,
682                    backing: params.backing.clone(),
683                    writable,
684                    mapping_type: region.params.mapping_type,
685                    policy: params.policy,
686                })
687                .await?;
688
689            for (dma_idx, dma_mapper) in self.inner.dma_mappers.iter().enumerate() {
690                if let Err(e) = dma_mapper.map_dma(SubMapping {
691                    range,
692                    backing: &params.backing,
693                    writable,
694                    mapping_type: region.params.mapping_type,
695                }) {
696                    // Roll back: unmap from DMA mappers that already
697                    // succeeded, then remove the VA mapping.
698                    for dm in &self.inner.dma_mappers[..dma_idx] {
699                        dm.unmap_dma(range);
700                    }
701                    self.inner.mapping_manager.remove_mappings(range).await;
702                    return Err(e);
703                }
704            }
705        }
706
707        region.mappings.push(RegionMapping { params });
708        Ok(())
709    }
710
711    async fn remove_mappings(&mut self, id: RegionId, range_in_region: MemoryRange) {
712        let index = self.region_index(id);
713        let region = &mut self.regions[index];
714        let active_range = region.active_range();
715
716        // Collect absolute GPA ranges of mappings being removed (before
717        // mutating the vec) so we can notify DMA mappers.
718        let removed_ranges: Vec<MemoryRange> = if active_range.is_some() {
719            let region_range = region.params.range;
720            region
721                .mappings
722                .iter()
723                .filter(|m| range_in_region.contains(&m.params.range_in_region))
724                .map(|m| range_within(region_range, m.params.range_in_region))
725                .collect()
726        } else {
727            Vec::new()
728        };
729
730        region.mappings.retain_mut(|mapping| {
731            if !range_in_region.contains(&mapping.params.range_in_region) {
732                assert!(
733                    !range_in_region.overlaps(&mapping.params.range_in_region),
734                    "no partial unmappings allowed"
735                );
736                return true;
737            }
738            false
739        });
740        if let Some(region_range) = active_range {
741            // Unmap DMA mappers first — IOMMU entries must be removed before
742            // the VA mappings are torn down (same ordering as disable_region).
743            for &removed in &removed_ranges {
744                for dma_mapper in &self.inner.dma_mappers {
745                    dma_mapper.unmap_dma(removed);
746                }
747            }
748
749            self.inner
750                .mapping_manager
751                .remove_mappings(range_within(region_range, range_in_region))
752                .await;
753
754            // Currently there is no need to tell the partitions about the
755            // removed mappings; they will find out when the underlying VA is
756            // invalidated by the kernel.
757        }
758    }
759}
760
761impl RegionManagerTaskInner {
762    async fn enable_region(&mut self, region: &mut Region) -> anyhow::Result<()> {
763        assert!(!region.is_active);
764        let map_params = region.map_params.unwrap();
765
766        tracing::debug!(
767            name = region.params.name,
768            range = %region.params.range,
769            writable = map_params.writable,
770            "enabling region"
771        );
772
773        // Add the mappings for the region. On failure, roll back any
774        // sub-mappings that were successfully added.
775        for (mapped_count, mapping) in region.mappings.iter().enumerate() {
776            // Register the mapping with the mapping manager. File-backed RAM is
777            // mmap'd; private/anonymous RAM has its anonymous pages committed
778            // directly. Either way the eager VaMapper ends up with backed pages
779            // that drive the DMA targets below by host VA.
780            if let Err(e) = self
781                .mapping_manager
782                .add_mapping(MappingParams {
783                    range: range_within(region.params.range, mapping.params.range_in_region),
784                    backing: mapping.params.backing.clone(),
785                    writable: mapping.params.writable && map_params.writable,
786                    mapping_type: region.params.mapping_type,
787                    policy: mapping.params.policy,
788                })
789                .await
790            {
791                // Roll back: remove sub-mappings that were already added.
792                for prev in &region.mappings[..mapped_count] {
793                    let range = range_within(region.params.range, prev.params.range_in_region);
794                    for dma_mapper in &self.dma_mappers {
795                        dma_mapper.unmap_dma(range);
796                    }
797                }
798                self.mapping_manager
799                    .remove_mappings(region.params.range)
800                    .await;
801                return Err(e).context(format!(
802                    "failed to map {} during region enable",
803                    range_within(region.params.range, mapping.params.range_in_region),
804                ));
805            }
806
807            // Map into DMA mappers.
808            let range = range_within(region.params.range, mapping.params.range_in_region);
809            let writable = mapping.params.writable && map_params.writable;
810            for (dma_idx, dma_mapper) in self.dma_mappers.iter().enumerate() {
811                if let Err(e) = dma_mapper.map_dma(SubMapping {
812                    range,
813                    backing: &mapping.params.backing,
814                    writable,
815                    mapping_type: region.params.mapping_type,
816                }) {
817                    // Roll back the current sub-mapping from DMA mappers
818                    // that already succeeded (before the failing one).
819                    for dm in &self.dma_mappers[..dma_idx] {
820                        dm.unmap_dma(range);
821                    }
822                    // Roll back all previous sub-mappings from all DMA
823                    // mappers.
824                    for prev in &region.mappings[..mapped_count] {
825                        let prev_range =
826                            range_within(region.params.range, prev.params.range_in_region);
827                        for dm in &self.dma_mappers {
828                            dm.unmap_dma(prev_range);
829                        }
830                    }
831                    self.mapping_manager
832                        .remove_mappings(region.params.range)
833                        .await;
834                    return Err(e).context(format!(
835                        "DMA mapper failed to map {range} during region enable"
836                    ));
837                }
838            }
839        }
840
841        // Map the region into the partitions.
842        for partition in &mut self.partitions {
843            partition
844                .map_region(region.params.range, map_params)
845                .await
846                .expect("cannot recover from failed mapping");
847        }
848
849        region.is_active = true;
850        Ok(())
851    }
852
853    /// Disables an active region, tearing down its mappings.
854    ///
855    /// `transient` indicates the region may later be re-enabled (an overlay
856    /// disable, or an unmap that keeps the region registered) rather than being
857    /// permanently removed. Private/anonymous RAM is backed solely by the VA
858    /// mapping itself, so tearing it down decommits and zeroes its pages — a
859    /// transient disable of such a region would silently lose guest memory.
860    /// That path is not reachable today (nothing transiently disables a RAM
861    /// region, which is the highest priority), so guard it with an assert to
862    /// turn a future regression into an immediate, located panic rather than
863    /// silent corruption.
864    async fn disable_region(&mut self, region: &mut Region, transient: bool) {
865        assert!(region.is_active);
866        assert!(
867            !transient
868                || region
869                    .mappings
870                    .iter()
871                    .all(|m| m.params.backing.mappable().is_some()),
872            "transiently disabling region {} would decommit private RAM and lose its contents",
873            region.params.range,
874        );
875
876        tracing::debug!(
877            name = region.params.name,
878            range = %region.params.range,
879            "disabling region"
880        );
881
882        // Unmap DMA mappers first — IOMMU entries must be removed before
883        // the VA mappings are torn down (type1's pin_user_pages pins are
884        // released by unmap_dma, and the underlying pages must still be
885        // valid at that point).
886        let region_range = region.params.range;
887        for dma_mapper in &mut self.dma_mappers {
888            dma_mapper.unmap_dma(region_range);
889        }
890
891        for partition in &mut self.partitions {
892            partition.unmap_region(region_range);
893        }
894        self.mapping_manager.remove_mappings(region_range).await;
895        region.is_active = false;
896    }
897}
898
899impl RegionManager {
900    /// Returns a new region manager that sends mappings to `mapping_manager`.
901    pub fn new(spawn: impl Spawn, mapping_manager: MappingManagerClient) -> Self {
902        let (req_send, mut req_recv) = mesh::mpsc_channel();
903        spawn
904            .spawn("region_manager", {
905                let mut task = RegionManagerTask::new(mapping_manager);
906                async move {
907                    task.run(&mut req_recv).await;
908                }
909            })
910            .detach();
911        Self {
912            client: RegionManagerClient { req_send },
913        }
914    }
915
916    /// Gets access to the region manager.
917    pub fn client(&self) -> &RegionManagerClient {
918        &self.client
919    }
920}
921
922impl RegionManagerClient {
923    /// Adds a partition mapper.
924    ///
925    /// This may only be called in the same process as the region manager.
926    pub async fn add_partition(
927        &self,
928        partition: PartitionMapper,
929    ) -> Result<(), crate::partition_mapper::PartitionMapperError> {
930        self.req_send
931            .call(|x| RegionRequest::AddPartition(LocalOnly(x)), partition)
932            .await
933            .unwrap()
934    }
935
936    /// Creates a new, empty, unmapped region.
937    ///
938    /// Returns a handle that will remove the region on drop.
939    pub async fn new_region(
940        &self,
941        name: String,
942        range: MemoryRange,
943        priority: u8,
944        mapping_type: MappingType,
945    ) -> Result<RegionHandle, AddRegionError> {
946        let params = RegionParams {
947            name,
948            range,
949            priority,
950            mapping_type,
951        };
952
953        let id = self
954            .req_send
955            .call(RegionRequest::AddRegion, params)
956            .await
957            .unwrap()?;
958
959        Ok(RegionHandle {
960            id: Some(id),
961            req_send: self.req_send.clone(),
962        })
963    }
964}
965
966/// Client for registering DMA mappers with the region manager.
967///
968/// This is the public-facing handle for IOMMU consumers (VFIO, iommufd)
969/// to register themselves. It exposes only `add_dma_mapper`, hiding the
970/// rest of the region manager API.
971#[derive(Clone)]
972pub struct DmaMapperClient {
973    req_send: mesh::Sender<RegionRequest>,
974}
975
976impl DmaMapperClient {
977    pub(crate) fn new(region_manager: &RegionManagerClient) -> Self {
978        Self {
979            req_send: region_manager.req_send.clone(),
980        }
981    }
982
983    /// Register a DMA target to receive sub-mapping events.
984    ///
985    /// This may only be called in the same process as the region manager.
986    ///
987    /// A host VA is always provided to [`DmaTarget::map_dma`] (the region
988    /// manager always maintains an eager `VaMapper` — handing out a VA is
989    /// free), so targets that program the IOMMU by VA (VFIO type1, iommufd)
990    /// need no special opt-in.
991    ///
992    /// If `needs_fd` is `true`, the target additionally requires every mapping
993    /// to carry a backing fd. Such a target is incompatible with backing-less
994    /// mappings (private/anonymous RAM, exposed only by host VA): registration
995    /// fails if any such mapping already exists, and subsequent attempts to
996    /// create one fail. Use this for backends that must map from an fd (e.g., a
997    /// virtio-user frontend driven via the DMA mapper rather than the shared
998    /// guest-memory infrastructure).
999    ///
1000    /// The replay loop maps all existing active sub-mappings into the new
1001    /// consumer. On failure, already-mapped entries are **not** rolled back;
1002    /// the caller must clean up by dropping the [`DmaTarget`] (e.g., closing
1003    /// the VFIO container fd).
1004    ///
1005    /// Returns a [`DmaMapperHandle`] that removes the mapper when dropped.
1006    pub async fn add_dma_mapper(
1007        &self,
1008        target: Arc<dyn DmaTarget>,
1009        needs_fd: bool,
1010    ) -> anyhow::Result<DmaMapperHandle> {
1011        let id = self
1012            .req_send
1013            .call(
1014                |x| RegionRequest::AddDmaMapper(LocalOnly(x)),
1015                (target, needs_fd),
1016            )
1017            .await
1018            .unwrap()?;
1019        Ok(DmaMapperHandle {
1020            id: Some(id),
1021            req_send: self.req_send.clone(),
1022        })
1023    }
1024}
1025
1026/// Handle to a registered DMA mapper.
1027///
1028/// Removes the mapper from the region manager on drop, unmapping all
1029/// active IOMMU entries.
1030pub struct DmaMapperHandle {
1031    id: Option<DmaMapperId>,
1032    req_send: mesh::Sender<RegionRequest>,
1033}
1034
1035impl Drop for DmaMapperHandle {
1036    fn drop(&mut self) {
1037        if let Some(id) = self.id {
1038            self.req_send
1039                .send(RegionRequest::RemoveDmaMapper(LocalOnly(id)));
1040        }
1041    }
1042}
1043
1044/// A handle to a region.
1045///
1046/// Removes the region on drop.
1047#[derive(Debug)]
1048#[must_use]
1049pub struct RegionHandle {
1050    id: Option<RegionId>,
1051    req_send: mesh::Sender<RegionRequest>,
1052}
1053
1054impl RegionHandle {
1055    /// Maps this region to a guest address.
1056    pub async fn map(&self, params: MapParams) -> Result<(), RemoteError> {
1057        self.req_send
1058            .call(RegionRequest::MapRegion, (self.id.unwrap(), params))
1059            .await
1060            .map_err(RemoteError::new)?
1061    }
1062
1063    /// Unmaps this region.
1064    pub async fn unmap(&self) {
1065        let _ = self
1066            .req_send
1067            .call(RegionRequest::UnmapRegion, self.id.unwrap())
1068            .await;
1069    }
1070
1071    /// Adds a mapping to the region.
1072    ///
1073    /// `backing` describes how the mapping is backed by host memory:
1074    /// [`MappingBacking::File`] for file/shared-memory-backed RAM (mmap'd by
1075    /// the mapping manager) or [`MappingBacking::Private`] for anonymous RAM
1076    /// (whose pages the mapping manager commits directly). In both cases the
1077    /// mapping manager establishes the backing on the eager `VaMapper`; private
1078    /// mappings additionally drive DMA targets only by host VA, since they have
1079    /// no backing fd.
1080    ///
1081    /// TODO: allow this to split+overwrite existing mappings.
1082    pub async fn add_mapping(
1083        &self,
1084        range_in_region: MemoryRange,
1085        backing: MappingBacking,
1086        writable: bool,
1087        policy: MemoryPolicy,
1088    ) -> Result<(), RemoteError> {
1089        self.req_send
1090            .call(
1091                RegionRequest::AddMapping,
1092                (
1093                    self.id.unwrap(),
1094                    RegionMappingParams {
1095                        range_in_region,
1096                        backing,
1097                        writable,
1098                        policy,
1099                    },
1100                ),
1101            )
1102            .await
1103            .map_err(RemoteError::new)?
1104    }
1105
1106    /// Removes the mappings in `range` within this region.
1107    ///
1108    /// TODO: allow this to split mappings.
1109    pub async fn remove_mappings(&self, range: MemoryRange) {
1110        let _ = self
1111            .req_send
1112            .call(RegionRequest::RemoveMappings, (self.id.unwrap(), range))
1113            .await;
1114    }
1115
1116    /// Tears the region down, waiting for all mappings to be unreferenced.
1117    pub async fn teardown(mut self) {
1118        let _ = self
1119            .req_send
1120            .call(RegionRequest::RemoveRegion, self.id.take().unwrap())
1121            .await;
1122    }
1123}
1124
1125impl Drop for RegionHandle {
1126    fn drop(&mut self) {
1127        if let Some(id) = self.id {
1128            let _recv = self.req_send.call(RegionRequest::RemoveRegion, id);
1129            // Don't wait for the response.
1130        }
1131    }
1132}
1133
1134#[cfg(test)]
1135mod tests {
1136    use super::MapParams;
1137    use super::RegionManagerTask;
1138    use crate::mapping_manager::Mappable;
1139    use crate::mapping_manager::MappingBacking;
1140    use crate::mapping_manager::MappingManager;
1141    use crate::mapping_manager::MemoryPolicy;
1142    use crate::region_manager::AddRegionError;
1143    use crate::region_manager::DmaMapRequest;
1144    use crate::region_manager::DmaTarget;
1145    use crate::region_manager::MappingType;
1146    use crate::region_manager::RegionId;
1147    use crate::region_manager::RegionMappingParams;
1148    use crate::region_manager::RegionParams;
1149    use memory_range::MemoryRange;
1150    use pal_async::async_test;
1151    use pal_async::task::Spawn;
1152    use parking_lot::Mutex;
1153    use std::ops::Range;
1154    use std::sync::Arc;
1155
1156    /// Records map/unmap calls for test assertions.
1157    #[derive(Default)]
1158    struct RecordingDmaTarget {
1159        events: Mutex<Vec<DmaEvent>>,
1160        /// Ranges for which `map_dma` was called with no backing fd
1161        /// (`mappable: None`), i.e., the anonymous/private-RAM VA path.
1162        backingless_maps: Mutex<Vec<MemoryRange>>,
1163    }
1164
1165    #[derive(Debug, Clone, PartialEq, Eq)]
1166    enum DmaEvent {
1167        Map(MemoryRange),
1168        Unmap(MemoryRange),
1169    }
1170
1171    impl DmaTarget for RecordingDmaTarget {
1172        unsafe fn map_dma(&self, request: DmaMapRequest<'_>) -> anyhow::Result<()> {
1173            if request.mappable.is_none() {
1174                self.backingless_maps.lock().push(request.range);
1175            }
1176            self.events.lock().push(DmaEvent::Map(request.range));
1177            Ok(())
1178        }
1179
1180        fn unmap_dma(&self, range: MemoryRange) -> anyhow::Result<()> {
1181            self.events.lock().push(DmaEvent::Unmap(range));
1182            Ok(())
1183        }
1184    }
1185
1186    impl RecordingDmaTarget {
1187        fn take_events(&self) -> Vec<DmaEvent> {
1188            std::mem::take(&mut self.events.lock())
1189        }
1190
1191        fn take_backingless_maps(&self) -> Vec<MemoryRange> {
1192            std::mem::take(&mut self.backingless_maps.lock())
1193        }
1194    }
1195
1196    /// Create a dummy Mappable for tests (cross-platform).
1197    fn test_mappable() -> Mappable {
1198        sparse_mmap::alloc_shared_memory(0x10000, "test-dma")
1199            .unwrap()
1200            .into()
1201    }
1202
1203    #[async_test]
1204    async fn test_region_overlap(spawn: impl Spawn) {
1205        struct TestTask(RegionManagerTask);
1206        impl TestTask {
1207            async fn add(
1208                &mut self,
1209                priority: u8,
1210                range: Range<u64>,
1211            ) -> Result<RegionId, AddRegionError> {
1212                let id = self.0.add_region(RegionParams {
1213                    priority,
1214                    name: priority.to_string(),
1215                    range: MemoryRange::new(range),
1216                    mapping_type: MappingType::Device,
1217                })?;
1218                self.0
1219                    .map_region(
1220                        id,
1221                        MapParams {
1222                            executable: true,
1223                            writable: true,
1224                            prefetch: false,
1225                        },
1226                    )
1227                    .await
1228                    .unwrap();
1229                Ok(id)
1230            }
1231
1232            async fn remove(&mut self, id: RegionId) {
1233                self.0.unmap_region(id, true).await;
1234            }
1235        }
1236
1237        let mm = MappingManager::new_without_primary(spawn, 0x200000, None);
1238        let mut task = TestTask(RegionManagerTask::new(mm.client().clone()));
1239
1240        let high = task.add(1, 0x1000..0x3000).await.unwrap();
1241
1242        task.add(0, 0x2000..0x4000).await.unwrap_err();
1243
1244        let low = task.add(0, 0x1000..0x3000).await.unwrap();
1245
1246        task.remove(high).await;
1247
1248        task.add(1, 0x2000..0x4000).await.unwrap_err();
1249        task.add(1, 0x2000..0x3000).await.unwrap_err();
1250
1251        let _high = task.add(1, 0..0x10000).await.unwrap();
1252
1253        task.remove(low).await;
1254
1255        task.add(0, 0..0x20000).await.unwrap_err();
1256
1257        let _low = task.add(0, 0x1000..0x8000).await.unwrap();
1258    }
1259
1260    /// Helper that wraps RegionManagerTask for DMA tests.
1261    struct DmaTestTask {
1262        task: RegionManagerTask,
1263        mappable: Mappable,
1264    }
1265
1266    impl DmaTestTask {
1267        fn new(spawn: impl Spawn) -> Self {
1268            let mm = MappingManager::new_without_primary(spawn, 0x200000, None);
1269            Self {
1270                task: RegionManagerTask::new(mm.client().clone()),
1271                mappable: test_mappable(),
1272            }
1273        }
1274
1275        async fn add_region(&mut self, range: Range<u64>) -> RegionId {
1276            let id = self
1277                .task
1278                .add_region(RegionParams {
1279                    priority: 0,
1280                    name: format!("{range:x?}"),
1281                    range: MemoryRange::new(range),
1282                    mapping_type: MappingType::Device,
1283                })
1284                .unwrap();
1285            self.task
1286                .map_region(
1287                    id,
1288                    MapParams {
1289                        executable: true,
1290                        writable: true,
1291                        prefetch: false,
1292                    },
1293                )
1294                .await
1295                .unwrap();
1296            id
1297        }
1298
1299        async fn add_mapping(&mut self, id: RegionId, range_in_region: Range<u64>) {
1300            self.task
1301                .add_mapping(
1302                    id,
1303                    RegionMappingParams {
1304                        range_in_region: MemoryRange::new(range_in_region),
1305                        backing: MappingBacking::File {
1306                            mappable: self.mappable.clone(),
1307                            file_offset: 0,
1308                        },
1309                        writable: true,
1310                        policy: MemoryPolicy::none(),
1311                    },
1312                )
1313                .await
1314                .unwrap();
1315        }
1316
1317        /// Adds a backing-less (private/anonymous RAM) mapping.
1318        async fn add_private_mapping(&mut self, id: RegionId, range_in_region: Range<u64>) {
1319            self.task
1320                .add_mapping(
1321                    id,
1322                    RegionMappingParams {
1323                        range_in_region: MemoryRange::new(range_in_region),
1324                        backing: MappingBacking::Private,
1325                        writable: true,
1326                        policy: MemoryPolicy::none(),
1327                    },
1328                )
1329                .await
1330                .unwrap();
1331        }
1332    }
1333
1334    #[async_test]
1335    async fn test_dma_replay_on_registration(spawn: impl Spawn) {
1336        let mut t = DmaTestTask::new(&spawn);
1337        let r = t.add_region(0x0..0x10000).await;
1338        t.add_mapping(r, 0x0..0x4000).await;
1339        t.add_mapping(r, 0x8000..0xC000).await;
1340
1341        // Register a DMA mapper — it should replay the two active mappings.
1342        let target = Arc::new(RecordingDmaTarget::default());
1343        let id = t.task.add_dma_mapper(target.clone(), false).await.unwrap();
1344
1345        assert_eq!(
1346            target.take_events(),
1347            vec![
1348                DmaEvent::Map(MemoryRange::new(0x0..0x4000)),
1349                DmaEvent::Map(MemoryRange::new(0x8000..0xC000)),
1350            ]
1351        );
1352
1353        // Clean up.
1354        t.task.remove_dma_mapper(id);
1355    }
1356
1357    #[async_test]
1358    async fn test_dma_live_map_unmap(spawn: impl Spawn) {
1359        let mut t = DmaTestTask::new(&spawn);
1360        let r = t.add_region(0x0..0x10000).await;
1361
1362        let target = Arc::new(RecordingDmaTarget::default());
1363        let _id = t.task.add_dma_mapper(target.clone(), false).await.unwrap();
1364        target.take_events(); // discard empty replay
1365
1366        // Adding a mapping to an active region should notify the DMA mapper.
1367        t.add_mapping(r, 0x0..0x4000).await;
1368        assert_eq!(
1369            target.take_events(),
1370            vec![DmaEvent::Map(MemoryRange::new(0x0..0x4000))]
1371        );
1372
1373        // Removing the mapping should unmap it.
1374        t.task
1375            .remove_mappings(r, MemoryRange::new(0x0..0x4000))
1376            .await;
1377        assert_eq!(
1378            target.take_events(),
1379            vec![DmaEvent::Unmap(MemoryRange::new(0x0..0x4000))]
1380        );
1381    }
1382
1383    /// Anonymous/private RAM has no backing fd, so its region mappings carry
1384    /// `mappable: None`. Such mappings must still drive DMA targets (by host
1385    /// VA) — otherwise an assigned device DMAing to private RAM would take
1386    /// IOMMU faults. This covers both the replay path (mapper registered after
1387    /// the mapping) and the live path (mapping added to an active region).
1388    #[async_test]
1389    async fn test_dma_private_mapping_maps_by_va(spawn: impl Spawn) {
1390        let mut t = DmaTestTask::new(&spawn);
1391        let r = t.add_region(0x0..0x10000).await;
1392        // Backing-less mapping present before the mapper registers (replay).
1393        t.add_private_mapping(r, 0x0..0x4000).await;
1394
1395        let target = Arc::new(RecordingDmaTarget::default());
1396        let id = t.task.add_dma_mapper(target.clone(), false).await.unwrap();
1397
1398        // Replay must emit a map for the private range, with no backing fd.
1399        assert_eq!(
1400            target.take_events(),
1401            vec![DmaEvent::Map(MemoryRange::new(0x0..0x4000))]
1402        );
1403        assert_eq!(
1404            target.take_backingless_maps(),
1405            vec![MemoryRange::new(0x0..0x4000)]
1406        );
1407
1408        // A private mapping added to an already-active region (live path)
1409        // must also notify the DMA target by VA.
1410        t.add_private_mapping(r, 0x8000..0xC000).await;
1411        assert_eq!(
1412            target.take_events(),
1413            vec![DmaEvent::Map(MemoryRange::new(0x8000..0xC000))]
1414        );
1415        t.task.remove_dma_mapper(id);
1416    }
1417
1418    /// A DMA target registered with `needs_fd = true` cannot coexist with
1419    /// backing-less (private/anonymous RAM) mappings: registration must fail
1420    /// when such a mapping already exists.
1421    #[async_test]
1422    async fn test_needs_fd_rejects_existing_private_mapping(spawn: impl Spawn) {
1423        let mut t = DmaTestTask::new(&spawn);
1424        let r = t.add_region(0x0..0x10000).await;
1425        t.add_private_mapping(r, 0x0..0x4000).await;
1426
1427        let target = Arc::new(RecordingDmaTarget::default());
1428        let result = t.task.add_dma_mapper(target.clone(), true).await;
1429        assert!(
1430            result.is_err(),
1431            "needs_fd mapper must fail to register when private RAM exists"
1432        );
1433    }
1434
1435    /// Once a `needs_fd` target is registered, creating a new backing-less
1436    /// mapping must fail (while a backed mapping continues to work).
1437    #[async_test]
1438    async fn test_needs_fd_rejects_new_private_mapping(spawn: impl Spawn) {
1439        let mut t = DmaTestTask::new(&spawn);
1440        let r = t.add_region(0x0..0x10000).await;
1441        // A backed mapping is present; needs_fd registration succeeds.
1442        t.add_mapping(r, 0x0..0x4000).await;
1443        let target = Arc::new(RecordingDmaTarget::default());
1444        let _id = t.task.add_dma_mapper(target.clone(), true).await.unwrap();
1445
1446        // Adding a backed mapping is still fine.
1447        t.add_mapping(r, 0x4000..0x8000).await;
1448
1449        // Adding a backing-less mapping must now fail.
1450        let result = t
1451            .task
1452            .add_mapping(
1453                r,
1454                RegionMappingParams {
1455                    range_in_region: MemoryRange::new(0x8000..0xC000),
1456                    backing: MappingBacking::Private,
1457                    writable: true,
1458                    policy: MemoryPolicy::none(),
1459                },
1460            )
1461            .await;
1462        assert!(
1463            result.is_err(),
1464            "creating a private mapping must fail while a needs_fd mapper is registered"
1465        );
1466    }
1467
1468    #[async_test]
1469    async fn test_dma_disable_region_unmaps(spawn: impl Spawn) {
1470        let mut t = DmaTestTask::new(&spawn);
1471        let r = t.add_region(0x0..0x10000).await;
1472        t.add_mapping(r, 0x0..0x4000).await;
1473        t.add_mapping(r, 0x8000..0xC000).await;
1474
1475        let target = Arc::new(RecordingDmaTarget::default());
1476        let _id = t.task.add_dma_mapper(target.clone(), false).await.unwrap();
1477        target.take_events(); // discard replay
1478
1479        // Disabling the region should unmap the entire region range.
1480        t.task.unmap_region(r, false).await;
1481        assert_eq!(
1482            target.take_events(),
1483            vec![DmaEvent::Unmap(MemoryRange::new(0x0..0x10000))]
1484        );
1485    }
1486
1487    #[async_test]
1488    async fn test_dma_remove_mapper_unmaps_all(spawn: impl Spawn) {
1489        let mut t = DmaTestTask::new(&spawn);
1490        let r = t.add_region(0x0..0x10000).await;
1491        t.add_mapping(r, 0x0..0x4000).await;
1492        t.add_mapping(r, 0x8000..0xC000).await;
1493
1494        let target = Arc::new(RecordingDmaTarget::default());
1495        let id = t.task.add_dma_mapper(target.clone(), false).await.unwrap();
1496        target.take_events(); // discard replay
1497
1498        // Removing the mapper should unmap each active sub-mapping.
1499        t.task.remove_dma_mapper(id);
1500        assert_eq!(
1501            target.take_events(),
1502            vec![
1503                DmaEvent::Unmap(MemoryRange::new(0x0..0x4000)),
1504                DmaEvent::Unmap(MemoryRange::new(0x8000..0xC000)),
1505            ]
1506        );
1507    }
1508
1509    #[async_test]
1510    async fn test_dma_inactive_region_no_notifications(spawn: impl Spawn) {
1511        let mut t = DmaTestTask::new(&spawn);
1512        let r = t.add_region(0x0..0x10000).await;
1513        t.add_mapping(r, 0x0..0x4000).await;
1514
1515        // Disable the region before registering the mapper.
1516        t.task.unmap_region(r, false).await;
1517
1518        let target = Arc::new(RecordingDmaTarget::default());
1519        let _id = t.task.add_dma_mapper(target.clone(), false).await.unwrap();
1520
1521        // No replay for inactive regions.
1522        assert_eq!(target.take_events(), vec![]);
1523
1524        // Adding a mapping while inactive should also not notify.
1525        t.add_mapping(r, 0x8000..0xC000).await;
1526        assert_eq!(target.take_events(), vec![]);
1527    }
1528
1529    /// A DMA target that fails map_dma after a configurable number of
1530    /// successful calls.
1531    struct FailAfterDmaTarget {
1532        /// Number of map_dma calls to succeed before failing.
1533        fail_after: usize,
1534        inner: RecordingDmaTarget,
1535        call_count: Mutex<usize>,
1536    }
1537
1538    impl FailAfterDmaTarget {
1539        fn new(fail_after: usize) -> Self {
1540            Self {
1541                fail_after,
1542                inner: RecordingDmaTarget::default(),
1543                call_count: Mutex::new(0),
1544            }
1545        }
1546
1547        fn take_events(&self) -> Vec<DmaEvent> {
1548            self.inner.take_events()
1549        }
1550    }
1551
1552    impl DmaTarget for FailAfterDmaTarget {
1553        unsafe fn map_dma(&self, request: DmaMapRequest<'_>) -> anyhow::Result<()> {
1554            let mut count = self.call_count.lock();
1555            if *count >= self.fail_after {
1556                anyhow::bail!("simulated DMA mapping failure at {}", request.range);
1557            }
1558            *count += 1;
1559            drop(count);
1560            // SAFETY: delegating to RecordingDmaTarget.
1561            unsafe { self.inner.map_dma(request) }
1562        }
1563
1564        fn unmap_dma(&self, range: MemoryRange) -> anyhow::Result<()> {
1565            self.inner.unmap_dma(range)
1566        }
1567    }
1568
1569    #[async_test]
1570    async fn test_add_mapping_dma_failure_propagates(spawn: impl Spawn) {
1571        let mut t = DmaTestTask::new(&spawn);
1572        let r = t.add_region(0x0..0x10000).await;
1573
1574        // Register a DMA mapper that fails immediately.
1575        let target = Arc::new(FailAfterDmaTarget::new(0));
1576        let _id = t.task.add_dma_mapper(target.clone(), false).await.unwrap();
1577        target.take_events();
1578
1579        // Adding a sub-mapping to the active region should fail because
1580        // the DMA mapper fails. The VA mapping should be rolled back.
1581        let result = t
1582            .task
1583            .add_mapping(
1584                r,
1585                RegionMappingParams {
1586                    range_in_region: MemoryRange::new(0x0..0x4000),
1587                    backing: MappingBacking::File {
1588                        mappable: t.mappable.clone(),
1589                        file_offset: 0,
1590                    },
1591                    writable: true,
1592                    policy: MemoryPolicy::none(),
1593                },
1594            )
1595            .await;
1596
1597        assert!(result.is_err(), "add_mapping should propagate DMA failure");
1598    }
1599
1600    #[async_test]
1601    async fn test_enable_region_rollback_unmaps_current_sub_mapping_from_earlier_dma_mappers(
1602        spawn: impl Spawn,
1603    ) {
1604        // Two DMA mappers: the first always succeeds, the second fails
1605        // immediately. With one sub-mapping, enable_region should:
1606        //   1. Map sub-mapping into mapper A (succeeds)
1607        //   2. Map sub-mapping into mapper B (fails)
1608        //   3. Roll back: unmap sub-mapping from mapper A
1609        //
1610        // The bug: rollback only unmaps `mappings[..mapped_count]` (previous
1611        // sub-mappings), but `mapped_count` is 0 for the first sub-mapping,
1612        // so mapper A's successful map is never rolled back.
1613        let mut t = DmaTestTask::new(&spawn);
1614
1615        let r = t.add_region(0x0..0x10000).await;
1616        t.add_mapping(r, 0x0..0x4000).await;
1617
1618        // Disable so we can re-enable with DMA mappers present.
1619        t.task.unmap_region(r, false).await;
1620
1621        let good_target = Arc::new(RecordingDmaTarget::default());
1622        let _good_id = t
1623            .task
1624            .add_dma_mapper(good_target.clone(), false)
1625            .await
1626            .unwrap();
1627
1628        let bad_target = Arc::new(FailAfterDmaTarget::new(0)); // fails immediately
1629        let _bad_id = t
1630            .task
1631            .add_dma_mapper(bad_target.clone(), false)
1632            .await
1633            .unwrap();
1634
1635        // Drain replay events (region is inactive, so there should be none).
1636        good_target.take_events();
1637        bad_target.take_events();
1638
1639        let result = t
1640            .task
1641            .map_region(
1642                r,
1643                MapParams {
1644                    writable: true,
1645                    executable: true,
1646                    prefetch: false,
1647                },
1648            )
1649            .await;
1650
1651        assert!(result.is_err(), "enable should fail");
1652
1653        // good_target should see: Map(0..0x4000) then Unmap(0..0x4000).
1654        assert_eq!(
1655            good_target.take_events(),
1656            vec![
1657                DmaEvent::Map(MemoryRange::new(0x0..0x4000)),
1658                DmaEvent::Unmap(MemoryRange::new(0x0..0x4000)),
1659            ],
1660            "the successful DMA mapper must have its mapping rolled back"
1661        );
1662    }
1663
1664    #[async_test]
1665    async fn test_enable_region_rollback_unmaps_dma_sub_mappings(spawn: impl Spawn) {
1666        let mut t = DmaTestTask::new(&spawn);
1667
1668        // Create a region with three sub-mappings.
1669        let r = t.add_region(0x0..0x18000).await;
1670        t.add_mapping(r, 0x0..0x4000).await;
1671        t.add_mapping(r, 0x4000..0x8000).await;
1672        t.add_mapping(r, 0x8000..0xC000).await;
1673
1674        // Disable the region so we can re-enable with a DMA mapper present.
1675        t.task.unmap_region(r, false).await;
1676
1677        // Register a DMA mapper that fails on the third map_dma call
1678        // (i.e., the third sub-mapping). The first two succeed.
1679        let target = Arc::new(FailAfterDmaTarget::new(2));
1680        let _id = t.task.add_dma_mapper(target.clone(), false).await.unwrap();
1681
1682        // Re-enable the region. Sub-mappings 0 and 1 succeed, sub-mapping 2
1683        // fails. Rollback should unmap sub-mappings 0 and 1.
1684        let result = t
1685            .task
1686            .map_region(
1687                r,
1688                MapParams {
1689                    writable: true,
1690                    executable: true,
1691                    prefetch: false,
1692                },
1693            )
1694            .await;
1695
1696        assert!(
1697            result.is_err(),
1698            "enable should fail on third DMA sub-mapping"
1699        );
1700
1701        let region = t.task.regions.iter().find(|reg| reg.id == r).unwrap();
1702        assert!(
1703            !region.is_active,
1704            "region should not be active after failed enable"
1705        );
1706
1707        assert_eq!(
1708            target.take_events(),
1709            vec![
1710                DmaEvent::Map(MemoryRange::new(0x0..0x4000)),
1711                DmaEvent::Map(MemoryRange::new(0x4000..0x8000)),
1712                DmaEvent::Unmap(MemoryRange::new(0x0..0x4000)),
1713                DmaEvent::Unmap(MemoryRange::new(0x4000..0x8000)),
1714            ],
1715            "successful DMA sub-mappings should be rolled back exactly once"
1716        );
1717    }
1718}