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