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::MappingManagerClient;
12use crate::mapping_manager::MappingParams;
13use crate::mapping_manager::VaMapper;
14use crate::partition_mapper::PartitionMapper;
15use anyhow::Context as _;
16use futures::StreamExt;
17use inspect::Inspect;
18use inspect::InspectMut;
19use memory_range::MemoryRange;
20use mesh::MeshPayload;
21use mesh::rpc::Rpc;
22use mesh::rpc::RpcSend;
23use pal_async::task::Spawn;
24use std::cmp::Ordering;
25use std::sync::Arc;
26use thiserror::Error;
27use vmcore::local_only::LocalOnly;
28
29/// A consumer of IOMMU-granularity DMA mapping events.
30///
31/// Unlike [`PartitionMemoryMap`](virt::PartitionMemoryMap), which maps entire
32/// regions by VA pointer for lazy SLAT resolution, this trait receives
33/// individual sub-mapping events with the backing fd + offset, suitable for
34/// explicit IOMMU programming (VFIO type1, iommufd, etc.).
35///
36/// DMA targets receive notifications for **all** active sub-mappings,
37/// including device BAR memory (regions with `dma_target: false`). The
38/// `dma_target` flag controls only whether a region is exposed via
39/// `GuestMemorySharing` (for vhost-user); IOMMU consumers need the full
40/// GPA→backing map to program identity mappings for all guest-visible memory.
41///
42/// Implementations must be `Send + Sync` because they are stored behind `Arc`
43/// in the region manager task.
44pub trait DmaTarget: Send + Sync {
45    /// Program an IOMMU mapping for `range` to the backing described by
46    /// `mappable` at `file_offset`.
47    ///
48    /// `host_va` is the host virtual address of the mapping, provided when
49    /// the target was registered with `needs_va = true`. When `None`, the
50    /// implementation should use `mappable` and `file_offset` directly
51    /// (e.g., iommufd).
52    ///
53    /// # Safety
54    /// When `host_va` is `Some`, the pointed-to memory must be backed and
55    /// must not be unmapped for the duration of the resulting IOMMU mapping.
56    /// The caller (the crate-internal `DmaMapper`) guarantees this by holding an
57    /// [`Arc<VaMapper>`] and calling `ensure_mapped` before each invocation.
58    unsafe fn map_dma(
59        &self,
60        range: MemoryRange,
61        host_va: Option<*const u8>,
62        mappable: &Mappable,
63        file_offset: u64,
64    ) -> anyhow::Result<()>;
65
66    /// Remove IOMMU mappings within `range`.
67    ///
68    /// The region manager may call this with a range that covers multiple
69    /// prior `map_dma` calls (e.g., unmapping an entire region at once even
70    /// though individual sub-mappings were mapped separately). The range
71    /// will always be aligned to mapping boundaries — it will not bisect
72    /// any prior mapping. Gaps within the range (unmapped sub-ranges) are
73    /// expected and must not cause errors.
74    fn unmap_dma(&self, range: MemoryRange) -> anyhow::Result<()>;
75}
76
77/// Wraps a [`DmaTarget`] for use by the region manager.
78///
79/// Holds an optional [`VaMapper`] to ensure pages are faulted in before
80/// `map_dma` (needed for VFIO type1's `pin_user_pages`).
81struct DmaMapper {
82    id: DmaMapperId,
83    target: Arc<dyn DmaTarget>,
84    va_mapper: Option<Arc<VaMapper>>,
85}
86
87#[derive(Debug, Copy, Clone, PartialEq, Eq)]
88struct DmaMapperId(u64);
89
90impl DmaMapper {
91    /// Map a sub-mapping into the IOMMU.
92    async fn map_dma(
93        &self,
94        range: MemoryRange,
95        mappable: &Mappable,
96        file_offset: u64,
97    ) -> anyhow::Result<()> {
98        // Ensure the VaMapper has the backing pages faulted in and compute
99        // the host VA for type1 backends.
100        let host_va = if let Some(va_mapper) = &self.va_mapper {
101            va_mapper
102                .ensure_mapped(range)
103                .await
104                .context("VA range has no backing mapping")?;
105            // SAFETY: range.start() is within the VA reservation (ensured by
106            // ensure_mapped succeeding), so this produces a valid pointer
107            // within the mapping.
108            Some(unsafe { va_mapper.as_ptr().add(range.start() as usize).cast_const() })
109        } else {
110            None
111        };
112        // SAFETY: When host_va is Some, the VaMapper has been ensure_mapped
113        // for this range. The VaMapper is held alive by this DmaMapper (via
114        // Arc), so the VA reservation persists. The pages are backed because
115        // ensure_mapped succeeded. The IOMMU mapping will be torn down
116        // (via unmap_dma in disable_region or remove_dma_mapper) before the
117        // VaMapper releases the VA range.
118        unsafe { self.target.map_dma(range, host_va, mappable, file_offset) }
119    }
120
121    /// Unmap a range from the IOMMU.
122    fn unmap_dma(&self, range: MemoryRange) {
123        if let Err(e) = self.target.unmap_dma(range) {
124            tracing::warn!(
125                error = &*e as &dyn std::error::Error,
126                %range,
127                "DMA unmap failed"
128            );
129        }
130    }
131}
132
133/// The region manager.
134#[derive(Debug, Inspect)]
135pub struct RegionManager {
136    #[inspect(
137        flatten,
138        with = "|x| inspect::send(&x.req_send, RegionRequest::Inspect)"
139    )]
140    client: RegionManagerClient,
141}
142
143/// Provides access to the region manager.
144#[derive(Debug, MeshPayload, Clone)]
145pub struct RegionManagerClient {
146    req_send: mesh::Sender<RegionRequest>,
147}
148
149struct Region {
150    id: RegionId,
151    map_params: Option<MapParams>,
152    is_active: bool,
153    params: RegionParams,
154    mappings: Vec<RegionMapping>,
155}
156
157#[derive(Debug, MeshPayload)]
158struct RegionParams {
159    name: String,
160    range: MemoryRange,
161    priority: u8,
162    /// Whether mappings in this region are DMA targets (guest RAM or
163    /// similar shareable memory).
164    dma_target: bool,
165}
166
167#[derive(Copy, Clone, Debug, MeshPayload, PartialEq, Eq, Inspect)]
168pub struct MapParams {
169    pub writable: bool,
170    pub executable: bool,
171    pub prefetch: bool,
172}
173
174impl Region {
175    fn active_range(&self) -> Option<MemoryRange> {
176        if self.is_active {
177            Some(self.params.range)
178        } else {
179            None
180        }
181    }
182}
183
184/// The task object for the region manager.
185#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, MeshPayload)]
186pub struct RegionId(u64);
187
188#[derive(InspectMut)]
189struct RegionManagerTask {
190    #[inspect(with = "inspect_regions")]
191    regions: Vec<Region>,
192    #[inspect(skip)]
193    next_region_id: u64,
194    #[inspect(skip)]
195    inner: RegionManagerTaskInner,
196}
197
198fn inspect_regions(regions: &Vec<Region>) -> impl '_ + Inspect {
199    inspect::adhoc(move |req| {
200        let mut resp = req.respond();
201        for region in regions {
202            resp.field(
203                &format!("{}:{}", region.params.range, &region.params.name),
204                inspect::adhoc(|req| {
205                    req.respond()
206                        .field("map_params", region.map_params)
207                        .field("is_active", region.is_active)
208                        .field("priority", region.params.priority)
209                        .field(
210                            "mappings",
211                            inspect::adhoc(|req| {
212                                inspect_mappings(req, region.params.range.start(), &region.mappings)
213                            }),
214                        );
215                }),
216            );
217        }
218    })
219}
220
221fn inspect_mappings(req: inspect::Request<'_>, region_start: u64, mappings: &[RegionMapping]) {
222    let mut resp = req.respond();
223    for mapping in mappings {
224        let range = MemoryRange::new(
225            region_start + mapping.params.range_in_region.start()
226                ..region_start + mapping.params.range_in_region.end(),
227        )
228        .to_string();
229
230        resp.field(
231            &range,
232            inspect::adhoc(|req| {
233                req.respond()
234                    .field("writable", mapping.params.writable)
235                    .hex("file_offset", mapping.params.file_offset);
236            }),
237        );
238    }
239}
240
241struct RegionManagerTaskInner {
242    partitions: Vec<PartitionMapper>,
243    dma_mappers: Vec<DmaMapper>,
244    next_dma_mapper_id: u64,
245    mapping_manager: MappingManagerClient,
246}
247
248#[derive(MeshPayload)]
249enum RegionRequest {
250    AddRegion(Rpc<RegionParams, Result<RegionId, AddRegionError>>),
251    RemoveRegion(Rpc<RegionId, ()>),
252    MapRegion(Rpc<(RegionId, MapParams), ()>),
253    UnmapRegion(Rpc<RegionId, ()>),
254    AddMapping(Rpc<(RegionId, RegionMappingParams), ()>),
255    RemoveMappings(Rpc<(RegionId, MemoryRange), ()>),
256    AddPartition(
257        LocalOnly<Rpc<PartitionMapper, Result<(), crate::partition_mapper::PartitionMapperError>>>,
258    ),
259    AddDmaMapper(LocalOnly<Rpc<(Arc<dyn DmaTarget>, bool), anyhow::Result<DmaMapperId>>>),
260    RemoveDmaMapper(LocalOnly<DmaMapperId>),
261    Inspect(inspect::Deferred),
262}
263
264struct RegionMapping {
265    params: RegionMappingParams,
266}
267
268#[derive(MeshPayload)]
269struct RegionMappingParams {
270    range_in_region: MemoryRange,
271    mappable: Mappable,
272    file_offset: u64,
273    writable: bool,
274}
275
276fn range_within(outer: MemoryRange, inner: MemoryRange) -> MemoryRange {
277    assert!(inner.end() <= outer.len());
278    MemoryRange::new(outer.start() + inner.start()..outer.start() + inner.end())
279}
280
281#[derive(Debug, Error, MeshPayload)]
282pub enum AddRegionError {
283    #[error("memory region {new} overlaps with existing region {existing}")]
284    OverlapError { existing: String, new: String },
285}
286
287impl RegionManagerTask {
288    fn new(mapping_manager: MappingManagerClient) -> Self {
289        Self {
290            regions: Vec::new(),
291            next_region_id: 1,
292            inner: RegionManagerTaskInner {
293                mapping_manager,
294                partitions: Vec::new(),
295                dma_mappers: Vec::new(),
296                next_dma_mapper_id: 0,
297            },
298        }
299    }
300
301    async fn run(&mut self, req_recv: &mut mesh::Receiver<RegionRequest>) {
302        while let Some(req) = req_recv.next().await {
303            match req {
304                RegionRequest::AddMapping(rpc) => {
305                    rpc.handle(async |(id, params)| self.add_mapping(id, params).await)
306                        .await
307                }
308                RegionRequest::RemoveMappings(rpc) => {
309                    rpc.handle(async |(id, range)| self.remove_mappings(id, range).await)
310                        .await
311                }
312                RegionRequest::AddPartition(LocalOnly(rpc)) => {
313                    rpc.handle(async |partition| self.add_partition(partition).await)
314                        .await
315                }
316                RegionRequest::AddDmaMapper(LocalOnly(rpc)) => {
317                    let ((target, needs_va), rpc) = rpc.split();
318                    let result = self.add_dma_mapper(target, needs_va).await;
319                    rpc.complete(result);
320                }
321                RegionRequest::RemoveDmaMapper(LocalOnly(id)) => {
322                    self.remove_dma_mapper(id);
323                }
324                RegionRequest::AddRegion(rpc) => rpc.handle_sync(|params| self.add_region(params)),
325                RegionRequest::RemoveRegion(rpc) => {
326                    rpc.handle(async |id| self.unmap_region(id, true).await)
327                        .await
328                }
329                RegionRequest::MapRegion(rpc) => {
330                    rpc.handle(async |(id, params)| self.map_region(id, params).await)
331                        .await
332                }
333                RegionRequest::UnmapRegion(rpc) => {
334                    rpc.handle(async |id| self.unmap_region(id, false).await)
335                        .await
336                }
337                RegionRequest::Inspect(deferred) => {
338                    deferred.inspect(&mut *self);
339                }
340            }
341        }
342    }
343
344    async fn add_partition(
345        &mut self,
346        partition: PartitionMapper,
347    ) -> Result<(), crate::partition_mapper::PartitionMapperError> {
348        // Map existing regions. On failure, all regions will be unmapped by the
349        // region mapper's drop impl, so don't worry about that.
350        for region in &self.regions {
351            if region.is_active {
352                partition
353                    .map_region(region.params.range, region.map_params.unwrap())
354                    .await?;
355            }
356        }
357        self.inner.partitions.push(partition);
358        Ok(())
359    }
360
361    async fn add_dma_mapper(
362        &mut self,
363        target: Arc<dyn DmaTarget>,
364        needs_va: bool,
365    ) -> anyhow::Result<DmaMapperId> {
366        // Create a VaMapper if the target needs host VAs for IOMMU programming.
367        let va_mapper = if needs_va {
368            Some(
369                self.inner
370                    .mapping_manager
371                    .new_mapper()
372                    .await
373                    .map_err(|e| anyhow::anyhow!(e))?,
374            )
375        } else {
376            None
377        };
378
379        let id = DmaMapperId(self.inner.next_dma_mapper_id);
380        self.inner.next_dma_mapper_id += 1;
381
382        let mapper = DmaMapper {
383            id,
384            target,
385            va_mapper,
386        };
387
388        // Replay existing active sub-mappings so the new IOMMU consumer
389        // gets the current state.
390        for region in &self.regions {
391            if region.is_active {
392                for mapping in &region.mappings {
393                    let range = range_within(region.params.range, mapping.params.range_in_region);
394                    mapper
395                        .map_dma(range, &mapping.params.mappable, mapping.params.file_offset)
396                        .await?;
397                }
398            }
399        }
400
401        self.inner.dma_mappers.push(mapper);
402        Ok(id)
403    }
404
405    fn remove_dma_mapper(&mut self, id: DmaMapperId) {
406        if let Some(pos) = self.inner.dma_mappers.iter().position(|m| m.id == id) {
407            let mapper = &self.inner.dma_mappers[pos];
408            // Unmap all active sub-mappings from this mapper before removing it.
409            for region in &self.regions {
410                if region.is_active {
411                    for mapping in &region.mappings {
412                        let range =
413                            range_within(region.params.range, mapping.params.range_in_region);
414                        mapper.unmap_dma(range);
415                    }
416                }
417            }
418            self.inner.dma_mappers.swap_remove(pos);
419        }
420    }
421
422    fn region_index(&self, id: RegionId) -> usize {
423        self.regions.iter().position(|r| r.id == id).unwrap()
424    }
425
426    fn add_region(&mut self, params: RegionParams) -> Result<RegionId, AddRegionError> {
427        // Ensure that this fully overlaps everything at lower priority, and
428        // everything at higher priority fully overlaps this.
429        let range = params.range;
430        for other_region in &self.regions {
431            let other_range = other_region.params.range;
432            if !range.overlaps(&other_range) {
433                continue;
434            };
435            let ok = match params.priority.cmp(&other_region.params.priority) {
436                Ordering::Less => other_range.contains(&range),
437                Ordering::Equal => other_range == range,
438                Ordering::Greater => range.contains(&other_range),
439            };
440            if !ok {
441                return Err(AddRegionError::OverlapError {
442                    existing: other_region.params.name.clone(),
443                    new: params.name,
444                });
445            }
446        }
447
448        tracing::debug!(
449            range = %params.range,
450            name = params.name,
451            priority = params.priority,
452            "new region"
453        );
454
455        let id = RegionId(self.next_region_id);
456        self.next_region_id += 1;
457        self.regions.push(Region {
458            id,
459            map_params: None,
460            is_active: false,
461            params,
462            mappings: Vec::new(),
463        });
464        Ok(id)
465    }
466
467    /// Enables the highest priority region in `range`. Panics if any regions in
468    /// `range` are already enabled.
469    async fn enable_best_region(&mut self, mut range: MemoryRange) {
470        while !range.is_empty() {
471            // Pick the highest priority region with the lowest startest address
472            // in the range. Since lower priority ranges must be fully contained
473            // in higher priority ones, we can make the chosen region without
474            // overlapping with a higher priority region.
475            if let Some(region) = self
476                .regions
477                .iter_mut()
478                .filter_map(|region| {
479                    region.map_params?;
480                    if !range.contains(&region.params.range) {
481                        assert!(
482                            !range.overlaps(&region.params.range),
483                            "no overlap invariant violated"
484                        );
485                        return None;
486                    }
487                    assert!(!region.is_active);
488                    Some(region)
489                })
490                .min_by_key(|region| {
491                    (
492                        region.params.range.start(),
493                        u8::MAX - region.params.priority,
494                    )
495                })
496            {
497                self.inner.enable_region(region).await;
498                range = MemoryRange::new(region.params.range.end()..range.end());
499            } else {
500                range = MemoryRange::EMPTY;
501            }
502        }
503    }
504
505    async fn map_region(&mut self, id: RegionId, map_params: MapParams) {
506        let index = self.region_index(id);
507        let region = &mut self.regions[index];
508        let range = region.params.range;
509        let priority = region.params.priority;
510        if region.map_params == Some(map_params) {
511            return;
512        }
513
514        tracing::debug!(
515            name = region.params.name,
516            range = %region.params.range,
517            writable = map_params.writable,
518            "mapping region"
519        );
520
521        // Disable any overlapping active regions if they are lower priority. If
522        // they are higher priority, stop now since the active mappings won't change.
523        let mut enable = true;
524        for (other_index, other_region) in self.regions.iter_mut().enumerate() {
525            if !other_region.is_active || !other_region.params.range.overlaps(&range) {
526                continue;
527            }
528            if other_region.params.priority > priority
529                || (other_region.params.priority == priority && other_index < index)
530            {
531                enable = false;
532            } else {
533                assert!(enable);
534                self.inner.disable_region(other_region).await;
535            }
536        }
537
538        self.regions[index].map_params = Some(map_params);
539        if enable {
540            self.enable_best_region(range).await;
541        }
542    }
543
544    async fn unmap_region(&mut self, id: RegionId, remove: bool) {
545        let index = self.region_index(id);
546        let region = &mut self.regions[index];
547        tracing::debug!(
548            name = region.params.name,
549            range = %region.params.range,
550            remove,
551            "unmapping region"
552        );
553
554        let active_range = region.is_active.then_some(region.params.range);
555        if active_range.is_some() {
556            self.inner.disable_region(region).await;
557        }
558
559        if remove {
560            self.regions.remove(index);
561        } else {
562            region.map_params = None;
563        }
564        if let Some(range) = active_range {
565            self.enable_best_region(range).await;
566        }
567    }
568
569    async fn add_mapping(&mut self, id: RegionId, params: RegionMappingParams) {
570        let index = self.region_index(id);
571        let region = &mut self.regions[index];
572
573        // TODO: split and remove existing mappings, atomically. This is
574        // technically required by virtiofs DAX support.
575        assert!(
576            !region
577                .mappings
578                .iter()
579                .any(|m| m.params.range_in_region.overlaps(&params.range_in_region))
580        );
581
582        if let Some(region_range) = region.active_range() {
583            let range = range_within(region_range, params.range_in_region);
584            self.inner
585                .mapping_manager
586                .add_mapping(MappingParams {
587                    range,
588                    mappable: params.mappable.clone(),
589                    file_offset: params.file_offset,
590                    writable: params.writable,
591                    dma_target: region.params.dma_target,
592                })
593                .await;
594
595            for partition in &mut self.inner.partitions {
596                partition.notify_new_mapping(range).await;
597            }
598
599            for dma_mapper in &self.inner.dma_mappers {
600                if let Err(e) = dma_mapper
601                    .map_dma(range, &params.mappable, params.file_offset)
602                    .await
603                {
604                    tracing::warn!(
605                        error = &*e as &dyn std::error::Error,
606                        %range,
607                        "DMA mapper failed to map new sub-mapping"
608                    );
609                }
610            }
611        }
612
613        region.mappings.push(RegionMapping { params });
614    }
615
616    async fn remove_mappings(&mut self, id: RegionId, range_in_region: MemoryRange) {
617        let index = self.region_index(id);
618        let region = &mut self.regions[index];
619        let active_range = region.active_range();
620
621        // Collect absolute GPA ranges of mappings being removed (before
622        // mutating the vec) so we can notify DMA mappers.
623        let removed_ranges: Vec<MemoryRange> = if active_range.is_some() {
624            let region_range = region.params.range;
625            region
626                .mappings
627                .iter()
628                .filter(|m| range_in_region.contains(&m.params.range_in_region))
629                .map(|m| range_within(region_range, m.params.range_in_region))
630                .collect()
631        } else {
632            Vec::new()
633        };
634
635        region.mappings.retain_mut(|mapping| {
636            if !range_in_region.contains(&mapping.params.range_in_region) {
637                assert!(
638                    !range_in_region.overlaps(&mapping.params.range_in_region),
639                    "no partial unmappings allowed"
640                );
641                return true;
642            }
643            false
644        });
645        if let Some(region_range) = active_range {
646            // Unmap DMA mappers first — IOMMU entries must be removed before
647            // the VA mappings are torn down (same ordering as disable_region).
648            for &removed in &removed_ranges {
649                for dma_mapper in &self.inner.dma_mappers {
650                    dma_mapper.unmap_dma(removed);
651                }
652            }
653
654            self.inner
655                .mapping_manager
656                .remove_mappings(range_within(region_range, range_in_region))
657                .await;
658
659            // Currently there is no need to tell the partitions about the
660            // removed mappings; they will find out when the underlying VA is
661            // invalidated by the kernel.
662        }
663    }
664}
665
666impl RegionManagerTaskInner {
667    async fn enable_region(&mut self, region: &mut Region) {
668        assert!(!region.is_active);
669        let map_params = region.map_params.unwrap();
670
671        tracing::debug!(
672            name = region.params.name,
673            range = %region.params.range,
674            writable = map_params.writable,
675            "enabling region"
676        );
677
678        // Add the mappings for the region.
679        for mapping in &region.mappings {
680            self.mapping_manager
681                .add_mapping(MappingParams {
682                    range: range_within(region.params.range, mapping.params.range_in_region),
683                    mappable: mapping.params.mappable.clone(),
684                    file_offset: mapping.params.file_offset,
685                    writable: mapping.params.writable && map_params.writable,
686                    dma_target: region.params.dma_target,
687                })
688                .await;
689        }
690
691        // Map sub-mappings into DMA mappers (per-sub-mapping granularity).
692        for mapping in &region.mappings {
693            let range = range_within(region.params.range, mapping.params.range_in_region);
694            for dma_mapper in &self.dma_mappers {
695                if let Err(e) = dma_mapper
696                    .map_dma(range, &mapping.params.mappable, mapping.params.file_offset)
697                    .await
698                {
699                    tracing::warn!(
700                        error = &*e as &dyn std::error::Error,
701                        %range,
702                        "DMA mapper failed to map sub-mapping during region enable"
703                    );
704                }
705            }
706        }
707
708        // Map the region into the partitions.
709        for partition in &mut self.partitions {
710            partition
711                .map_region(region.params.range, map_params)
712                .await
713                .expect("cannot recover from failed mapping");
714        }
715
716        region.is_active = true;
717    }
718
719    async fn disable_region(&mut self, region: &mut Region) {
720        assert!(region.is_active);
721
722        tracing::debug!(
723            name = region.params.name,
724            range = %region.params.range,
725            "disabling region"
726        );
727
728        // Unmap DMA mappers first — IOMMU entries must be removed before
729        // the VA mappings are torn down (type1's pin_user_pages pins are
730        // released by unmap_dma, and the underlying pages must still be
731        // valid at that point).
732        let region_range = region.params.range;
733        for dma_mapper in &mut self.dma_mappers {
734            dma_mapper.unmap_dma(region_range);
735        }
736
737        for partition in &mut self.partitions {
738            partition.unmap_region(region_range);
739        }
740        self.mapping_manager.remove_mappings(region_range).await;
741        region.is_active = false;
742    }
743}
744
745impl RegionManager {
746    /// Returns a new region manager that sends mappings to `mapping_manager`.
747    pub fn new(spawn: impl Spawn, mapping_manager: MappingManagerClient) -> Self {
748        let (req_send, mut req_recv) = mesh::mpsc_channel();
749        spawn
750            .spawn("region_manager", {
751                let mut task = RegionManagerTask::new(mapping_manager);
752                async move {
753                    task.run(&mut req_recv).await;
754                }
755            })
756            .detach();
757        Self {
758            client: RegionManagerClient { req_send },
759        }
760    }
761
762    /// Gets access to the region manager.
763    pub fn client(&self) -> &RegionManagerClient {
764        &self.client
765    }
766}
767
768impl RegionManagerClient {
769    /// Adds a partition mapper.
770    ///
771    /// This may only be called in the same process as the region manager.
772    pub async fn add_partition(
773        &self,
774        partition: PartitionMapper,
775    ) -> Result<(), crate::partition_mapper::PartitionMapperError> {
776        self.req_send
777            .call(|x| RegionRequest::AddPartition(LocalOnly(x)), partition)
778            .await
779            .unwrap()
780    }
781
782    /// Creates a new, empty, unmapped region.
783    ///
784    /// Returns a handle that will remove the region on drop.
785    pub async fn new_region(
786        &self,
787        name: String,
788        range: MemoryRange,
789        priority: u8,
790        dma_target: bool,
791    ) -> Result<RegionHandle, AddRegionError> {
792        let params = RegionParams {
793            name,
794            range,
795            priority,
796            dma_target,
797        };
798
799        let id = self
800            .req_send
801            .call(RegionRequest::AddRegion, params)
802            .await
803            .unwrap()?;
804
805        Ok(RegionHandle {
806            id: Some(id),
807            req_send: self.req_send.clone(),
808        })
809    }
810}
811
812/// Client for registering DMA mappers with the region manager.
813///
814/// This is the public-facing handle for IOMMU consumers (VFIO, iommufd)
815/// to register themselves. It exposes only `add_dma_mapper`, hiding the
816/// rest of the region manager API.
817#[derive(Clone)]
818pub struct DmaMapperClient {
819    req_send: mesh::Sender<RegionRequest>,
820}
821
822impl DmaMapperClient {
823    pub(crate) fn new(region_manager: &RegionManagerClient) -> Self {
824        Self {
825            req_send: region_manager.req_send.clone(),
826        }
827    }
828
829    /// Register a DMA target to receive sub-mapping events.
830    ///
831    /// This may only be called in the same process as the region manager.
832    ///
833    /// If `needs_va` is `true`, the region manager will maintain a `VaMapper`
834    /// and pass a host VA to [`DmaTarget::map_dma`] for each sub-mapping.
835    /// Use this for backends that program the IOMMU via host VAs (VFIO type1).
836    ///
837    /// If `needs_va` is `false`, no `VaMapper` is created and `host_va` will
838    /// be `None`. Use this for backends that map from the fd directly (iommufd).
839    ///
840    /// The replay loop maps all existing active sub-mappings into the new
841    /// consumer. On failure, already-mapped entries are **not** rolled back;
842    /// the caller must clean up by dropping the [`DmaTarget`] (e.g., closing
843    /// the VFIO container fd).
844    ///
845    /// Returns a [`DmaMapperHandle`] that removes the mapper when dropped.
846    pub async fn add_dma_mapper(
847        &self,
848        target: Arc<dyn DmaTarget>,
849        needs_va: bool,
850    ) -> anyhow::Result<DmaMapperHandle> {
851        let id = self
852            .req_send
853            .call(
854                |x| RegionRequest::AddDmaMapper(LocalOnly(x)),
855                (target, needs_va),
856            )
857            .await
858            .unwrap()?;
859        Ok(DmaMapperHandle {
860            id: Some(id),
861            req_send: self.req_send.clone(),
862        })
863    }
864}
865
866/// Handle to a registered DMA mapper.
867///
868/// Removes the mapper from the region manager on drop, unmapping all
869/// active IOMMU entries.
870pub struct DmaMapperHandle {
871    id: Option<DmaMapperId>,
872    req_send: mesh::Sender<RegionRequest>,
873}
874
875impl Drop for DmaMapperHandle {
876    fn drop(&mut self) {
877        if let Some(id) = self.id {
878            self.req_send
879                .send(RegionRequest::RemoveDmaMapper(LocalOnly(id)));
880        }
881    }
882}
883
884/// A handle to a region.
885///
886/// Removes the region on drop.
887#[derive(Debug)]
888#[must_use]
889pub struct RegionHandle {
890    id: Option<RegionId>,
891    req_send: mesh::Sender<RegionRequest>,
892}
893
894impl RegionHandle {
895    /// Maps this region to a guest address.
896    pub async fn map(&self, params: MapParams) {
897        self.req_send
898            .call(RegionRequest::MapRegion, (self.id.unwrap(), params))
899            .await
900            .unwrap()
901    }
902
903    /// Unmaps this region.
904    pub async fn unmap(&self) {
905        let _ = self
906            .req_send
907            .call(RegionRequest::UnmapRegion, self.id.unwrap())
908            .await;
909    }
910
911    /// Adds a mapping to the region.
912    ///
913    /// TODO: allow this to split+overwrite existing mappings.
914    pub async fn add_mapping(
915        &self,
916        range_in_region: MemoryRange,
917        mappable: Mappable,
918        file_offset: u64,
919        writable: bool,
920    ) {
921        let _ = self
922            .req_send
923            .call(
924                RegionRequest::AddMapping,
925                (
926                    self.id.unwrap(),
927                    RegionMappingParams {
928                        range_in_region,
929                        mappable,
930                        file_offset,
931                        writable,
932                    },
933                ),
934            )
935            .await;
936    }
937
938    /// Removes the mappings in `range` within this region.
939    ///
940    /// TODO: allow this to split mappings.
941    pub async fn remove_mappings(&self, range: MemoryRange) {
942        let _ = self
943            .req_send
944            .call(RegionRequest::RemoveMappings, (self.id.unwrap(), range))
945            .await;
946    }
947
948    /// Tears the region down, waiting for all mappings to be unreferenced.
949    pub async fn teardown(mut self) {
950        let _ = self
951            .req_send
952            .call(RegionRequest::RemoveRegion, self.id.take().unwrap())
953            .await;
954    }
955}
956
957impl Drop for RegionHandle {
958    fn drop(&mut self) {
959        if let Some(id) = self.id {
960            let _recv = self.req_send.call(RegionRequest::RemoveRegion, id);
961            // Don't wait for the response.
962        }
963    }
964}
965
966#[cfg(test)]
967mod tests {
968    use super::MapParams;
969    use super::RegionManagerTask;
970    use crate::mapping_manager::Mappable;
971    use crate::mapping_manager::MappingManager;
972    use crate::region_manager::AddRegionError;
973    use crate::region_manager::DmaTarget;
974    use crate::region_manager::RegionId;
975    use crate::region_manager::RegionMappingParams;
976    use crate::region_manager::RegionParams;
977    use memory_range::MemoryRange;
978    use pal_async::async_test;
979    use pal_async::task::Spawn;
980    use parking_lot::Mutex;
981    use std::ops::Range;
982    use std::sync::Arc;
983
984    /// Records map/unmap calls for test assertions.
985    #[derive(Default)]
986    struct RecordingDmaTarget {
987        events: Mutex<Vec<DmaEvent>>,
988    }
989
990    #[derive(Debug, Clone, PartialEq, Eq)]
991    enum DmaEvent {
992        Map(MemoryRange),
993        Unmap(MemoryRange),
994    }
995
996    impl DmaTarget for RecordingDmaTarget {
997        unsafe fn map_dma(
998            &self,
999            range: MemoryRange,
1000            _host_va: Option<*const u8>,
1001            _mappable: &Mappable,
1002            _file_offset: u64,
1003        ) -> anyhow::Result<()> {
1004            self.events.lock().push(DmaEvent::Map(range));
1005            Ok(())
1006        }
1007
1008        fn unmap_dma(&self, range: MemoryRange) -> anyhow::Result<()> {
1009            self.events.lock().push(DmaEvent::Unmap(range));
1010            Ok(())
1011        }
1012    }
1013
1014    impl RecordingDmaTarget {
1015        fn take_events(&self) -> Vec<DmaEvent> {
1016            std::mem::take(&mut self.events.lock())
1017        }
1018    }
1019
1020    /// Create a dummy Mappable for tests (cross-platform).
1021    fn test_mappable() -> Mappable {
1022        sparse_mmap::alloc_shared_memory(0x10000, "test-dma")
1023            .unwrap()
1024            .into()
1025    }
1026
1027    #[async_test]
1028    async fn test_region_overlap(spawn: impl Spawn) {
1029        struct TestTask(RegionManagerTask);
1030        impl TestTask {
1031            async fn add(
1032                &mut self,
1033                priority: u8,
1034                range: Range<u64>,
1035            ) -> Result<RegionId, AddRegionError> {
1036                let id = self.0.add_region(RegionParams {
1037                    priority,
1038                    name: priority.to_string(),
1039                    range: MemoryRange::new(range),
1040                    dma_target: false,
1041                })?;
1042                self.0
1043                    .map_region(
1044                        id,
1045                        MapParams {
1046                            executable: true,
1047                            writable: true,
1048                            prefetch: false,
1049                        },
1050                    )
1051                    .await;
1052                Ok(id)
1053            }
1054
1055            async fn remove(&mut self, id: RegionId) {
1056                self.0.unmap_region(id, true).await;
1057            }
1058        }
1059
1060        let mm = MappingManager::new(spawn, 0x200000, false, None);
1061        let mut task = TestTask(RegionManagerTask::new(mm.client().clone()));
1062
1063        let high = task.add(1, 0x1000..0x3000).await.unwrap();
1064
1065        task.add(0, 0x2000..0x4000).await.unwrap_err();
1066
1067        let low = task.add(0, 0x1000..0x3000).await.unwrap();
1068
1069        task.remove(high).await;
1070
1071        task.add(1, 0x2000..0x4000).await.unwrap_err();
1072        task.add(1, 0x2000..0x3000).await.unwrap_err();
1073
1074        let _high = task.add(1, 0..0x10000).await.unwrap();
1075
1076        task.remove(low).await;
1077
1078        task.add(0, 0..0x20000).await.unwrap_err();
1079
1080        let _low = task.add(0, 0x1000..0x8000).await.unwrap();
1081    }
1082
1083    /// Helper that wraps RegionManagerTask for DMA tests.
1084    struct DmaTestTask {
1085        task: RegionManagerTask,
1086        mappable: Mappable,
1087    }
1088
1089    impl DmaTestTask {
1090        fn new(spawn: impl Spawn) -> Self {
1091            let mm = MappingManager::new(spawn, 0x200000, false, None);
1092            Self {
1093                task: RegionManagerTask::new(mm.client().clone()),
1094                mappable: test_mappable(),
1095            }
1096        }
1097
1098        async fn add_region(&mut self, range: Range<u64>) -> RegionId {
1099            let id = self
1100                .task
1101                .add_region(RegionParams {
1102                    priority: 0,
1103                    name: format!("{range:x?}"),
1104                    range: MemoryRange::new(range),
1105                    dma_target: false,
1106                })
1107                .unwrap();
1108            self.task
1109                .map_region(
1110                    id,
1111                    MapParams {
1112                        executable: true,
1113                        writable: true,
1114                        prefetch: false,
1115                    },
1116                )
1117                .await;
1118            id
1119        }
1120
1121        async fn add_mapping(&mut self, id: RegionId, range_in_region: Range<u64>) {
1122            self.task
1123                .add_mapping(
1124                    id,
1125                    RegionMappingParams {
1126                        range_in_region: MemoryRange::new(range_in_region),
1127                        mappable: self.mappable.clone(),
1128                        file_offset: 0,
1129                        writable: true,
1130                    },
1131                )
1132                .await;
1133        }
1134    }
1135
1136    #[async_test]
1137    async fn test_dma_replay_on_registration(spawn: impl Spawn) {
1138        let mut t = DmaTestTask::new(&spawn);
1139        let r = t.add_region(0x0..0x10000).await;
1140        t.add_mapping(r, 0x0..0x4000).await;
1141        t.add_mapping(r, 0x8000..0xC000).await;
1142
1143        // Register a DMA mapper — it should replay the two active mappings.
1144        let target = Arc::new(RecordingDmaTarget::default());
1145        let id = t.task.add_dma_mapper(target.clone(), false).await.unwrap();
1146
1147        assert_eq!(
1148            target.take_events(),
1149            vec![
1150                DmaEvent::Map(MemoryRange::new(0x0..0x4000)),
1151                DmaEvent::Map(MemoryRange::new(0x8000..0xC000)),
1152            ]
1153        );
1154
1155        // Clean up.
1156        t.task.remove_dma_mapper(id);
1157    }
1158
1159    #[async_test]
1160    async fn test_dma_live_map_unmap(spawn: impl Spawn) {
1161        let mut t = DmaTestTask::new(&spawn);
1162        let r = t.add_region(0x0..0x10000).await;
1163
1164        let target = Arc::new(RecordingDmaTarget::default());
1165        let _id = t.task.add_dma_mapper(target.clone(), false).await.unwrap();
1166        target.take_events(); // discard empty replay
1167
1168        // Adding a mapping to an active region should notify the DMA mapper.
1169        t.add_mapping(r, 0x0..0x4000).await;
1170        assert_eq!(
1171            target.take_events(),
1172            vec![DmaEvent::Map(MemoryRange::new(0x0..0x4000))]
1173        );
1174
1175        // Removing the mapping should unmap it.
1176        t.task
1177            .remove_mappings(r, MemoryRange::new(0x0..0x4000))
1178            .await;
1179        assert_eq!(
1180            target.take_events(),
1181            vec![DmaEvent::Unmap(MemoryRange::new(0x0..0x4000))]
1182        );
1183    }
1184
1185    #[async_test]
1186    async fn test_dma_disable_region_unmaps(spawn: impl Spawn) {
1187        let mut t = DmaTestTask::new(&spawn);
1188        let r = t.add_region(0x0..0x10000).await;
1189        t.add_mapping(r, 0x0..0x4000).await;
1190        t.add_mapping(r, 0x8000..0xC000).await;
1191
1192        let target = Arc::new(RecordingDmaTarget::default());
1193        let _id = t.task.add_dma_mapper(target.clone(), false).await.unwrap();
1194        target.take_events(); // discard replay
1195
1196        // Disabling the region should unmap the entire region range.
1197        t.task.unmap_region(r, false).await;
1198        assert_eq!(
1199            target.take_events(),
1200            vec![DmaEvent::Unmap(MemoryRange::new(0x0..0x10000))]
1201        );
1202    }
1203
1204    #[async_test]
1205    async fn test_dma_remove_mapper_unmaps_all(spawn: impl Spawn) {
1206        let mut t = DmaTestTask::new(&spawn);
1207        let r = t.add_region(0x0..0x10000).await;
1208        t.add_mapping(r, 0x0..0x4000).await;
1209        t.add_mapping(r, 0x8000..0xC000).await;
1210
1211        let target = Arc::new(RecordingDmaTarget::default());
1212        let id = t.task.add_dma_mapper(target.clone(), false).await.unwrap();
1213        target.take_events(); // discard replay
1214
1215        // Removing the mapper should unmap each active sub-mapping.
1216        t.task.remove_dma_mapper(id);
1217        assert_eq!(
1218            target.take_events(),
1219            vec![
1220                DmaEvent::Unmap(MemoryRange::new(0x0..0x4000)),
1221                DmaEvent::Unmap(MemoryRange::new(0x8000..0xC000)),
1222            ]
1223        );
1224    }
1225
1226    #[async_test]
1227    async fn test_dma_inactive_region_no_notifications(spawn: impl Spawn) {
1228        let mut t = DmaTestTask::new(&spawn);
1229        let r = t.add_region(0x0..0x10000).await;
1230        t.add_mapping(r, 0x0..0x4000).await;
1231
1232        // Disable the region before registering the mapper.
1233        t.task.unmap_region(r, false).await;
1234
1235        let target = Arc::new(RecordingDmaTarget::default());
1236        let _id = t.task.add_dma_mapper(target.clone(), false).await.unwrap();
1237
1238        // No replay for inactive regions.
1239        assert_eq!(target.take_events(), vec![]);
1240
1241        // Adding a mapping while inactive should also not notify.
1242        t.add_mapping(r, 0x8000..0xC000).await;
1243        assert_eq!(target.take_events(), vec![]);
1244    }
1245}