Skip to main content

membacking/mapping_manager/
manager.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Implements the mapping manager, which keeps track of the VA mappers and
5//! their currently active mappings. It is responsible for invalidating mappings
6//! in each VA range when they are torn down by the region manager.
7
8use super::mappable::Mappable;
9use super::object_cache::ObjectCache;
10use super::object_cache::ObjectId;
11use super::va_mapper::MapperRole;
12use super::va_mapper::VaMapper;
13use super::va_mapper::VaMapperError;
14use crate::RemoteProcess;
15use crate::region_manager::MappingType;
16use futures::StreamExt;
17use futures::future::join_all;
18use guestmem::ProvideShareableRegions;
19use guestmem::ShareableRegion;
20use inspect::Inspect;
21use inspect::InspectMut;
22use memory_range::MemoryRange;
23use mesh::MeshPayload;
24use mesh::error::RemoteError;
25use mesh::rpc::FailableRpc;
26use mesh::rpc::Rpc;
27use mesh::rpc::RpcSend;
28use pal_async::task::Spawn;
29use slab::Slab;
30use std::sync::Arc;
31use thiserror::Error;
32
33/// The mapping manager.
34#[derive(Debug, Inspect)]
35pub struct MappingManager {
36    #[inspect(
37        flatten,
38        with = "|x| inspect::send(&x.req_send, MappingRequest::Inspect)"
39    )]
40    client: MappingManagerClient,
41}
42
43impl MappingManager {
44    /// Returns a new mapping manager (mapping addresses up to `max_addr`) and
45    /// its primary VA mapper.
46    ///
47    /// The primary mapper is created here, as part of construction, so it is
48    /// necessarily the first mapper in the owning process and is captured by the
49    /// shared cache: every later
50    /// [`new_mapper`](MappingManagerClient::new_mapper) in this process returns
51    /// it. It is the loader's write target and the partition's fault resolver,
52    /// and the only mapper eligible for soft large pages. It is always eager,
53    /// and starts empty — it receives mappings as regions are added.
54    ///
55    /// Private memory imposes a "single local eager mapper" restriction, but
56    /// that is enforced dynamically by the manager task as mappings and mappers
57    /// come and go (private RAM may be added later, e.g. via hotplug) rather
58    /// than captured here at construction time.
59    pub async fn new(
60        spawn: impl Spawn,
61        max_addr: u64,
62        minimum_va_alignment: Option<usize>,
63        supports_memory_fault_resolution: bool,
64    ) -> Result<(Self, Arc<VaMapper>), VaMapperError> {
65        let this = Self::new_bare(spawn, max_addr, minimum_va_alignment);
66        // Create the primary mapper as part of construction. Being first, it is
67        // the instance the shared cache hands to every later `new_mapper` in
68        // this process.
69        let primary = this
70            .client()
71            .get_or_create_mapper(
72                true,
73                MapperRole::Primary {
74                    supports_memory_fault_resolution,
75                },
76            )
77            .await?;
78        Ok((this, primary))
79    }
80
81    /// Spawns the manager task and builds the client, without creating a primary
82    /// mapper.
83    fn new_bare(spawn: impl Spawn, max_addr: u64, minimum_va_alignment: Option<usize>) -> Self {
84        let (req_send, mut req_recv) = mesh::mpsc_channel();
85        spawn
86            .spawn("mapping_manager", {
87                let mut task = MappingManagerTask::new();
88                async move {
89                    task.run(&mut req_recv).await;
90                }
91            })
92            .detach();
93        Self {
94            client: MappingManagerClient {
95                id: ObjectId::new(),
96                req_send,
97                max_addr,
98                minimum_va_alignment,
99            },
100        }
101    }
102
103    /// Test-only constructor that builds a manager with no primary mapper (an
104    /// empty mapper cache), so unit tests can exercise secondary/lazy mapper
105    /// mechanics directly. Production code must use [`new`](Self::new).
106    #[cfg(test)]
107    pub(crate) fn new_without_primary(
108        spawn: impl Spawn,
109        max_addr: u64,
110        minimum_va_alignment: Option<usize>,
111    ) -> Self {
112        Self::new_bare(spawn, max_addr, minimum_va_alignment)
113    }
114
115    /// Returns an object used to access the mapping manager, potentially from a
116    /// remote process.
117    pub fn client(&self) -> &MappingManagerClient {
118        &self.client
119    }
120}
121
122/// Provides access to the mapping manager.
123#[derive(Debug, MeshPayload, Clone)]
124pub struct MappingManagerClient {
125    req_send: mesh::Sender<MappingRequest>,
126    id: ObjectId,
127    max_addr: u64,
128    minimum_va_alignment: Option<usize>,
129}
130
131static MAPPER_CACHE: ObjectCache<VaMapper> = ObjectCache::new();
132
133impl MappingManagerClient {
134    /// Returns a secondary VA mapper for this guest memory.
135    ///
136    /// A *secondary* mapper is any host-side view of guest memory other than the
137    /// primary one that the loader writes through and the partition resolves
138    /// faults against (created by [`MappingManager::new`](MappingManager::new)).
139    /// Secondary mappers are additional, remote views: for example a mapper in
140    /// the VP process for WHP VTL2 emulation, an out-of-process device that needs
141    /// to touch guest memory, or a DMA target. They never own the memory, and
142    /// soft large pages are not applied to them: in soft-large-page mode a
143    /// secondary mapper only ever gets 4 KB pages.
144    /// Returns a VA mapper for this guest memory.
145    ///
146    /// When `eager` is true, the mapper receives all existing mappings
147    /// immediately and gets new ones pushed synchronously. File-backed
148    /// page faults will fail (since mappings should already be
149    /// established). This is the right choice for the VP process, where
150    /// the hypervisor does not forward page faults back to the VMM.
151    ///
152    /// When `eager` is false, the mapper is lazy: mappings are populated
153    /// on demand via page faults. This avoids the cost of pushing every
154    /// mapping change to processes that rarely access the mapped regions
155    /// (e.g., device-emulation processes with virtio-fs DAX).
156    ///
157    /// The mapper is single-instanced per process via a cache. If a lazy
158    /// mapper was previously created and an eager one is now requested,
159    /// it is upgraded in place. In the process that owns the
160    /// [`GuestMemoryManager`](crate::MemoryManager), that cached instance is the
161    /// primary mapper created by [`MappingManager::new`](MappingManager::new);
162    /// in other processes (device/DMA workers) this creates a fresh secondary
163    /// mapper.
164    pub async fn new_mapper(&self, eager: bool) -> Result<Arc<VaMapper>, VaMapperError> {
165        self.get_or_create_mapper(eager, MapperRole::Secondary)
166            .await
167    }
168
169    async fn get_or_create_mapper(
170        &self,
171        eager: bool,
172        role: MapperRole,
173    ) -> Result<Arc<VaMapper>, VaMapperError> {
174        let mapper = MAPPER_CACHE
175            .get_or_insert_with(&self.id, async {
176                VaMapper::new(
177                    self.req_send.clone(),
178                    self.max_addr,
179                    None,
180                    self.minimum_va_alignment,
181                    eager,
182                    role,
183                )
184                .await
185            })
186            .await?;
187
188        // If we need eager but the cached mapper is lazy (created by an
189        // earlier lazy call), upgrade it.
190        if eager && !mapper.is_eager() {
191            self.req_send
192                .call(MappingRequest::UpgradeToEager, mapper.mapper_id())
193                .await
194                .map_err(VaMapperError::MemoryManagerGone)?
195                .map_err(VaMapperError::Registration)?;
196        }
197
198        Ok(mapper)
199    }
200
201    /// Returns a VA mapper for this guest memory, but map everything into the
202    /// address space of `process`.
203    ///
204    /// Each call will allocate a new unique mapper.
205    ///
206    /// If private memory is present, this fails at registration time because it
207    /// would be a second mapper (private RAM requires a single mapper); a remote
208    /// mapper is only rejected on that count, not for being remote.
209    pub async fn new_remote_mapper(
210        &self,
211        process: RemoteProcess,
212    ) -> Result<Arc<VaMapper>, VaMapperError> {
213        Ok(Arc::new(
214            VaMapper::new(
215                self.req_send.clone(),
216                self.max_addr,
217                Some(process),
218                self.minimum_va_alignment,
219                true, // eager — remote mappers used for partition mappings
220                // Secondary: this backs a partition from a remote process, but
221                // the soft-large-page work (deferred protect, first-write 2 MB
222                // populate, fault resolution) runs on the single primary mapper
223                // in the owning process. This mapper shares the same section
224                // pages, so it just maps them plain read-write 4 KB.
225                MapperRole::Secondary,
226            )
227            .await?,
228        ))
229    }
230
231    /// Adds an active mapping.
232    ///
233    /// The mapping is pushed eagerly to all existing VA mappers. Returns an
234    /// error if any mapper fails to establish the mapping.
235    ///
236    /// TODO: currently this will panic if the mapping overlaps an existing
237    /// mapping. This needs to be fixed to allow this to overlap existing
238    /// mappings, in which case the old ones will be split and replaced.
239    pub async fn add_mapping(&self, params: MappingParams) -> anyhow::Result<()> {
240        self.req_send
241            .call_failable(MappingRequest::AddMapping, params)
242            .await?;
243        Ok(())
244    }
245
246    /// Removes all mappings in `range`.
247    ///
248    /// TODO: allow this to split existing mappings.
249    pub async fn remove_mappings(&self, range: MemoryRange) {
250        self.req_send
251            .call(MappingRequest::RemoveMappings, range)
252            .await
253            .unwrap();
254    }
255}
256
257/// Parameters for registering a new VA mapper.
258#[derive(MeshPayload)]
259pub struct AddMapperParams {
260    /// Channel for sending mapping requests to the mapper task.
261    pub send: mesh::Sender<MapperRequest>,
262    /// Whether the mapper is eager (mappings pushed immediately and replayed
263    /// on creation) or lazy (mappings populated on demand via page faults).
264    pub eager: bool,
265}
266
267/// A mapping request message.
268#[derive(MeshPayload)]
269pub enum MappingRequest {
270    /// Register a new VA mapper.
271    AddMapper(FailableRpc<AddMapperParams, MapperId>),
272    RemoveMapper(MapperId),
273    /// Request that mappings covering the given range be sent to the specified
274    /// mapper via fire-and-forget `MapLazy` messages. Used by lazy mappers
275    /// to populate on demand.
276    SendMappings(MapperId, MemoryRange),
277    /// Upgrade a lazy mapper to eager: replay all existing mappings and
278    /// mark it for future pushes.
279    UpgradeToEager(FailableRpc<MapperId, ()>),
280    AddMapping(FailableRpc<MappingParams, ()>),
281    RemoveMappings(Rpc<MemoryRange, ()>),
282    /// Returns all mappings that have [`MappingType::Ram`] type.
283    GetDmaTargetMappings(Rpc<(), Vec<MappingParams>>),
284    Inspect(inspect::Deferred),
285}
286
287#[derive(InspectMut)]
288struct MappingManagerTask {
289    #[inspect(with = "inspect_mappings")]
290    mappings: Vec<Mapping>,
291    #[inspect(skip)]
292    mappers: Mappers,
293}
294
295fn inspect_mappings(mappings: &Vec<Mapping>) -> impl '_ + Inspect {
296    inspect::adhoc(move |req| {
297        let mut resp = req.respond();
298        for mapping in mappings {
299            resp.field(
300                &mapping.params.range.to_string(),
301                inspect::adhoc(|req| {
302                    req.respond()
303                        .field("writable", mapping.params.writable)
304                        .field("mapping_type", mapping.params.mapping_type)
305                        .field("backed_by_fd", mapping.params.backing.mappable().is_some())
306                        .hex("file_offset", mapping.params.backing.file_offset());
307                }),
308            );
309        }
310    })
311}
312
313struct Mapping {
314    params: MappingParams,
315    active_mappers: Vec<MapperId>,
316}
317
318/// How a guest memory mapping is backed by host memory.
319#[derive(Debug, MeshPayload, Clone)]
320pub enum MappingBacking {
321    /// Backed by a mappable OS object (shared memory or file). The mapping
322    /// manager mmaps `mappable` at `file_offset` into each VA mapper, so the
323    /// same physical pages are shared across all mappers (and shareable with
324    /// other processes via `GuestMemorySharing`).
325    File {
326        /// The OS object to map.
327        mappable: Mappable,
328        /// The file offset into `mappable`.
329        file_offset: u64,
330    },
331    /// Backed by private anonymous memory committed directly by the mapping
332    /// manager. There is no backing fd, so the memory cannot be shared with
333    /// other processes or programmed into DMA targets that require an fd; it is
334    /// exposed to DMA targets purely by host VA.
335    ///
336    /// Private memory is only valid with a single mapper: its anonymous storage
337    /// lives in one mapper's address space, with no backing fd, so it cannot be
338    /// shared to a second mapper. The mapping manager rejects a second mapper
339    /// while private memory is present, and rejects adding private RAM while
340    /// more than one mapper exists (see the `AddMapper` handler and
341    /// [`MappingManagerTask::add_mapping`]). A lone remote mapper is fine — the
342    /// restriction is on mapper *count*, not on locality.
343    ///
344    /// Unlike file-backed memory, the storage *is* the VA mapping: there is no
345    /// fd holding the pages. Tearing the mapping down (via the region manager's
346    /// `remove_mappings`) decommits and zeroes the pages, so a private region
347    /// must not be transiently disabled and re-enabled — doing so would lose
348    /// guest memory. The region manager asserts against this.
349    Private,
350}
351
352impl MappingBacking {
353    /// Returns the backing object, if this mapping is file-backed.
354    pub fn mappable(&self) -> Option<&Mappable> {
355        match self {
356            MappingBacking::File { mappable, .. } => Some(mappable),
357            MappingBacking::Private => None,
358        }
359    }
360
361    /// Returns the offset within the backing object, or 0 if there is none.
362    pub fn file_offset(&self) -> u64 {
363        match self {
364            MappingBacking::File { file_offset, .. } => *file_offset,
365            MappingBacking::Private => 0,
366        }
367    }
368}
369
370/// Host-memory policy for a mapping, applied to the mapped VA range after the
371/// backing is established. Independent of how the mapping is backed, so it
372/// lives here rather than on [`MappingBacking`].
373///
374/// There is deliberately no `Default` impl: the correct policy is
375/// context-sensitive (guest RAM wants THP enabled, device memory does not), so
376/// an implicit fallback would silently do the wrong thing. Use [`MemoryPolicy::none`]
377/// when no special policy is desired, or construct the struct explicitly.
378#[derive(Debug, Copy, Clone, MeshPayload)]
379pub struct MemoryPolicy {
380    /// Host NUMA node to strictly bind the mapping to (Linux `mbind(MPOL_BIND)`,
381    /// Windows `MemExtendedParameterNumaNode`). `None` means OS default.
382    pub numa_node: Option<u32>,
383    /// Whether the range is advised as Transparent Huge Page eligible (Linux
384    /// `madvise(MADV_HUGEPAGE)`).
385    pub transparent_hugepages: bool,
386    /// Whether this range is populated eagerly at build time (prefetch). On
387    /// Windows this selects the *eager* soft-large-page path: the range is
388    /// mapped read-write and its large pages are built up front by the
389    /// build-time populate, with its first-fault windows pre-marked attempted
390    /// (rather than the lazy deferred-protect, fault-time path).
391    pub prefetch: bool,
392}
393
394impl MemoryPolicy {
395    /// Returns a policy that requests no special host-memory placement: no NUMA
396    /// binding (OS default placement) and no Transparent Huge Page advice.
397    ///
398    /// This may be the right choice for mappings that are not guest RAM, such
399    /// as device memory. Guest RAM should instead construct the policy
400    /// explicitly so that THP eligibility is a deliberate decision.
401    pub fn none() -> Self {
402        Self {
403            numa_node: None,
404            transparent_hugepages: false,
405            prefetch: false,
406        }
407    }
408}
409
410/// The mapping parameters.
411#[derive(Debug, MeshPayload, Clone)]
412pub struct MappingParams {
413    /// The memory range for the mapping.
414    pub range: MemoryRange,
415    /// How the mapping is backed by host memory.
416    pub backing: MappingBacking,
417    /// Whether to map the memory as writable.
418    pub writable: bool,
419    /// The type of memory being mapped.
420    ///
421    /// [`MappingType::Ram`] mappings are exposed via
422    /// [`GuestMemorySharing`](guestmem::GuestMemorySharing) so that external
423    /// consumers (vhost-user backends, etc.) can share the backing memory.
424    pub mapping_type: MappingType,
425    /// Host-memory policy (NUMA node binding, THP eligibility).
426    pub policy: MemoryPolicy,
427}
428
429/// Error from a failed VA mapping operation.
430#[derive(Debug, Error)]
431#[error("failed to map {range}")]
432pub struct MappingError {
433    /// The GPA range that failed to map.
434    pub range: MemoryRange,
435    /// The underlying OS error.
436    #[source]
437    pub error: std::io::Error,
438}
439
440impl MappingError {
441    pub(crate) fn new(range: MemoryRange, error: std::io::Error) -> Self {
442        Self { range, error }
443    }
444}
445
446struct Mappers {
447    mappers: Slab<MapperComm>,
448}
449
450struct MapperComm {
451    req_send: mesh::Sender<MapperRequest>,
452    eager: bool,
453}
454
455#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, MeshPayload)]
456pub struct MapperId(pub(crate) usize);
457
458/// A request to a VA mapper.
459#[derive(Debug, MeshPayload)]
460pub enum MapperRequest {
461    /// Map the specified mapping and respond with success/failure.
462    /// Used by the eager path (`add_mapping`).
463    MapEager(Rpc<MappingParams, Result<(), RemoteError>>),
464    /// Map the specified mapping (fire-and-forget).
465    /// Used by the lazy path (`send_mappings`). The mapper task wakes
466    /// any pending waiters after processing.
467    MapLazy(MappingParams),
468    /// There is no mapping for the specified range. The mapper task
469    /// wakes waiters with a failure.
470    NoMapping(MemoryRange),
471    /// Unmap the specified range and send a response when it's done.
472    Unmap(Rpc<MemoryRange, ()>),
473    /// Mark this mapper as eager and respond when done. Sent after all
474    /// replay `MapEager` Rpcs have completed, so the mapper task
475    /// processes it after all replays.
476    SetEager(Rpc<(), ()>),
477}
478
479impl MappingManagerTask {
480    fn new() -> Self {
481        Self {
482            mappers: Mappers {
483                mappers: Slab::new(),
484            },
485            mappings: Vec::new(),
486        }
487    }
488
489    async fn run(&mut self, req_recv: &mut mesh::Receiver<MappingRequest>) {
490        while let Some(req) = req_recv.next().await {
491            match req {
492                MappingRequest::AddMapper(rpc) => {
493                    rpc.handle_failable(async |params: AddMapperParams| {
494                        // Private memory is anonymous: its storage *is* a single
495                        // mapper's address space, with no backing fd to share, so
496                        // a second mapper would get its own incoherent copy.
497                        // Private RAM therefore requires at most one mapper.
498                        // Reject a new mapper if private memory is already
499                        // present; the reverse direction (private RAM added
500                        // later) is enforced in `add_mapping`.
501                        if !self.mappers.mappers.is_empty() && self.has_private_mapping() {
502                            return Err(MappingError::new(
503                                MemoryRange::EMPTY,
504                                std::io::Error::other(
505                                    "cannot add a second mapper while private memory is present",
506                                ),
507                            ));
508                        }
509                        self.add_mapper(params.send, params.eager).await
510                    })
511                    .await
512                }
513                MappingRequest::RemoveMapper(id) => {
514                    self.remove_mapper(id);
515                }
516                MappingRequest::SendMappings(id, range) => {
517                    self.send_mappings(id, range);
518                }
519                MappingRequest::UpgradeToEager(rpc) => {
520                    rpc.handle_failable(async |id| self.upgrade_to_eager(id).await)
521                        .await
522                }
523                MappingRequest::AddMapping(rpc) => {
524                    rpc.handle_failable(async |params| self.add_mapping(params).await)
525                        .await
526                }
527                MappingRequest::RemoveMappings(rpc) => {
528                    rpc.handle(async |range| self.remove_mappings(range).await)
529                        .await
530                }
531                MappingRequest::GetDmaTargetMappings(rpc) => {
532                    rpc.handle_sync(|()| self.get_dma_target_mappings())
533                }
534                MappingRequest::Inspect(deferred) => deferred.inspect(&mut *self),
535            }
536        }
537    }
538
539    async fn add_mapper(
540        &mut self,
541        req_send: mesh::Sender<MapperRequest>,
542        eager: bool,
543    ) -> Result<MapperId, MappingError> {
544        let id = self.mappers.mappers.insert(MapperComm { req_send, eager });
545        let mapper_id = MapperId(id);
546        tracing::debug!(?id, eager, "adding mapper");
547
548        if eager {
549            // Replay all existing mappings to the new eager mapper.
550            let mut failed = None;
551            for mapping in &mut self.mappings {
552                match self.mappers.mappers[id]
553                    .req_send
554                    .call(MapperRequest::MapEager, mapping.params.clone())
555                    .await
556                {
557                    Ok(Ok(())) => {
558                        mapping.active_mappers.push(mapper_id);
559                    }
560                    Ok(Err(e)) => {
561                        failed = Some(MappingError::new(
562                            mapping.params.range,
563                            std::io::Error::other(e),
564                        ));
565                        break;
566                    }
567                    Err(_) => {
568                        failed = Some(MappingError::new(
569                            MemoryRange::EMPTY,
570                            std::io::Error::other("mapper gone during replay"),
571                        ));
572                        break;
573                    }
574                }
575            }
576            if let Some(err) = failed {
577                self.remove_mapper(mapper_id);
578                return Err(err);
579            }
580        }
581
582        Ok(mapper_id)
583    }
584
585    fn remove_mapper(&mut self, id: MapperId) {
586        tracing::debug!(?id, "removing mapper");
587        self.mappers.mappers.remove(id.0);
588        for mapping in &mut self.mappings {
589            mapping.active_mappers.retain(|m| m != &id);
590        }
591    }
592
593    /// Upgrade a mapper from lazy to eager: mark it eager and replay all
594    /// existing mappings. On failure, the mapper stays lazy.
595    async fn upgrade_to_eager(&mut self, id: MapperId) -> Result<(), MappingError> {
596        let mapper = &mut self.mappers.mappers[id.0];
597        if mapper.eager {
598            return Ok(()); // already eager
599        }
600        // Mark eager on the manager side first so new add_mapping calls
601        // will push to this mapper. The mapper task itself isn't marked
602        // eager until SetEager is processed (after all replays succeed).
603        //
604        // This is safe because the manager task processes requests
605        // sequentially — no concurrent add_mapping can run while this
606        // method is executing. If the upgrade fails, we roll back
607        // `mapper.eager` to `false` before returning.
608        mapper.eager = true;
609        tracing::debug!(?id, "upgrading mapper to eager");
610
611        let mut failed = None;
612        for mapping in &mut self.mappings {
613            // Skip mappings already established by lazy resolution.
614            if mapping.active_mappers.contains(&id) {
615                continue;
616            }
617            match self.mappers.mappers[id.0]
618                .req_send
619                .call(MapperRequest::MapEager, mapping.params.clone())
620                .await
621            {
622                Ok(Ok(())) => {
623                    mapping.active_mappers.push(id);
624                }
625                Ok(Err(e)) => {
626                    failed = Some(MappingError::new(
627                        mapping.params.range,
628                        std::io::Error::other(e),
629                    ));
630                    break;
631                }
632                Err(_) => {
633                    failed = Some(MappingError::new(
634                        MemoryRange::EMPTY,
635                        std::io::Error::other("mapper gone during eager upgrade"),
636                    ));
637                    break;
638                }
639            }
640        }
641
642        if let Some(err) = failed {
643            // Roll back: unmark eager so future add_mapping calls don't
644            // push to this mapper. Keep successfully-replayed entries in
645            // active_mappers so the mapper gets Unmap when those mappings
646            // are removed (the VA space already has them mapped).
647            self.mappers.mappers[id.0].eager = false;
648            return Err(err);
649        }
650
651        // Tell the mapper task to mark itself eager. Since the mapper task
652        // processes messages sequentially, this runs after all the MapEager
653        // replays above.
654        self.mappers.mappers[id.0]
655            .req_send
656            .call(MapperRequest::SetEager, ())
657            .await
658            .ok();
659
660        Ok(())
661    }
662
663    /// Handle a lazy mapper's on-demand request for mappings covering `range`.
664    ///
665    /// Finds all mappings that overlap the requested range and sends them
666    /// to the mapper via fire-and-forget `MapLazy` messages. Gaps send
667    /// `NoMapping` so the mapper task can wake waiters with failure.
668    fn send_mappings(&mut self, id: MapperId, mut range: MemoryRange) {
669        while !range.is_empty() {
670            // Find the next mapping that overlaps range.
671            let (this_end, params) = if let Some(mapping) = self
672                .mappings
673                .iter_mut()
674                .filter(|mapping| mapping.params.range.overlaps(&range))
675                .min_by_key(|mapping| mapping.params.range.start())
676            {
677                if mapping.params.range.start() <= range.start() {
678                    if !mapping.active_mappers.contains(&id) {
679                        mapping.active_mappers.push(id);
680                    }
681                    // The next mapping overlaps with the start of our range.
682                    (
683                        mapping.params.range.end().min(range.end()),
684                        Some(mapping.params.clone()),
685                    )
686                } else {
687                    // There's a gap before the next mapping.
688                    (mapping.params.range.start(), None)
689                }
690            } else {
691                // No matching mappings, consume the rest of the range.
692                (range.end(), None)
693            };
694            let this_range = MemoryRange::new(range.start()..this_end);
695            let req = if let Some(params) = params {
696                tracing::debug!(range = %this_range, full_range = %params.range, "sending lazy mapping");
697                MapperRequest::MapLazy(params)
698            } else {
699                tracing::debug!(range = %this_range, "no mapping for range");
700                MapperRequest::NoMapping(this_range)
701            };
702            self.mappers.mappers[id.0].req_send.send(req);
703            range = MemoryRange::new(this_end..range.end());
704        }
705    }
706
707    async fn add_mapping(&mut self, params: MappingParams) -> anyhow::Result<()> {
708        tracing::debug!(range = %params.range, "adding mapping");
709
710        // Private memory is anonymous and lives in a single mapper's address
711        // space, so it requires at most one mapper. Reject adding private RAM
712        // (e.g. via hotplug) while more than one mapper is present.
713        if matches!(params.backing, MappingBacking::Private) && self.mappers.mappers.len() > 1 {
714            anyhow::bail!("cannot add private memory while multiple mappers are present");
715        }
716
717        assert!(!self.mappings.iter().any(|m| m.params.range == params.range));
718
719        // Push to eager mappers only. Lazy mappers will request on demand.
720        let mut active_mappers = Vec::new();
721        for (i, mapper) in self.mappers.mappers.iter() {
722            if !mapper.eager {
723                continue;
724            }
725            let id = MapperId(i);
726            match mapper
727                .req_send
728                .call(MapperRequest::MapEager, params.clone())
729                .await
730            {
731                Ok(Ok(())) => {
732                    active_mappers.push(id);
733                }
734                Ok(Err(e)) => {
735                    // Unmap from mappers that already succeeded before returning
736                    // the error.
737                    for &rollback_id in &active_mappers {
738                        if let Err(err) = self.mappers.mappers[rollback_id.0]
739                            .req_send
740                            .call(MapperRequest::Unmap, params.range)
741                            .await
742                        {
743                            tracing::warn!(
744                                error = &err as &dyn std::error::Error,
745                                "mapper dropped unmap during rollback"
746                            );
747                        }
748                    }
749                    return Err(e.into());
750                }
751                Err(_) => {
752                    // Mapper gone, skip. VaMapper::drop sends RemoveMapper
753                    // which cleans up the stale entry.
754                    tracing::debug!(?id, "mapper gone during add_mapping");
755                }
756            }
757        }
758
759        self.mappings.push(Mapping {
760            params,
761            active_mappers,
762        });
763        Ok(())
764    }
765
766    /// Returns true if any current mapping is backed by private memory.
767    fn has_private_mapping(&self) -> bool {
768        self.mappings
769            .iter()
770            .any(|m| matches!(m.params.backing, MappingBacking::Private))
771    }
772
773    fn get_dma_target_mappings(&self) -> Vec<MappingParams> {
774        self.mappings
775            .iter()
776            // Only file-backed RAM can be shared with other processes;
777            // private/anonymous RAM has no fd to hand out.
778            .filter(|m| {
779                m.params.mapping_type == MappingType::Ram && m.params.backing.mappable().is_some()
780            })
781            .map(|m| m.params.clone())
782            .collect()
783    }
784
785    async fn remove_mappings(&mut self, range: MemoryRange) {
786        let mut mappers = Vec::new();
787        self.mappings.retain_mut(|mapping| {
788            if !range.contains(&mapping.params.range) {
789                assert!(
790                    !range.overlaps(&mapping.params.range),
791                    "no partial unmappings allowed"
792                );
793                return true;
794            }
795            tracing::debug!(range = %mapping.params.range, "removing mapping");
796            mappers.append(&mut mapping.active_mappers);
797            false
798        });
799        mappers.sort();
800        mappers.dedup();
801        self.mappers.invalidate(&mappers, range).await;
802    }
803}
804
805impl Mappers {
806    async fn invalidate(&self, ids: &[MapperId], range: MemoryRange) {
807        tracing::debug!(mapper_count = ids.len(), %range, "sending invalidations");
808        join_all(ids.iter().map(async |&MapperId(i)| {
809            if let Err(err) = self.mappers[i]
810                .req_send
811                .call(MapperRequest::Unmap, range)
812                .await
813            {
814                tracing::warn!(
815                    error = &err as &dyn std::error::Error,
816                    "mapper dropped invalidate request"
817                );
818            }
819        }))
820        .await;
821    }
822}
823
824/// Implements [`ProvideShareableRegions`] by querying the
825/// [`MappingManager`] for DMA-target mappings. Used by `VaMapper`'s
826/// `sharing()` implementation.
827pub(crate) struct DmaRegionProvider {
828    pub req_send: mesh::Sender<MappingRequest>,
829}
830
831impl ProvideShareableRegions for DmaRegionProvider {
832    async fn get_regions(&self) -> Result<Vec<ShareableRegion>, guestmem::ShareableRegionError> {
833        let mappings = self
834            .req_send
835            .call(MappingRequest::GetDmaTargetMappings, ())
836            .await?;
837
838        Ok(mappings
839            .into_iter()
840            .filter_map(|m| {
841                let mappable = m.backing.mappable()?;
842                Some(ShareableRegion {
843                    guest_address: m.range.start(),
844                    size: m.range.len(),
845                    file: mappable.inner_arc(),
846                    file_offset: m.backing.file_offset(),
847                })
848            })
849            .collect())
850    }
851}
852
853#[cfg(test)]
854mod tests {
855    use super::*;
856    use crate::region_manager::MappingType;
857    use guestmem::GuestMemoryAccess;
858    use guestmem::ProvideShareableRegions;
859    use memory_range::MemoryRange;
860
861    #[pal_async::async_test]
862    async fn test_dma_target_regions_returned(spawn: impl Spawn) {
863        let mm = MappingManager::new_without_primary(&spawn, 0x200000, None);
864        let client = mm.client().clone();
865
866        let ram: Mappable = sparse_mmap::alloc_shared_memory(0x100000, "test-ram")
867            .unwrap()
868            .into();
869        let device: Mappable = sparse_mmap::alloc_shared_memory(0x1000, "test-dev")
870            .unwrap()
871            .into();
872
873        client
874            .add_mapping(MappingParams {
875                range: MemoryRange::new(0..0x100000),
876                backing: MappingBacking::File {
877                    mappable: ram,
878                    file_offset: 0,
879                },
880                writable: true,
881                mapping_type: MappingType::Ram,
882                policy: MemoryPolicy::none(),
883            })
884            .await
885            .unwrap();
886
887        client
888            .add_mapping(MappingParams {
889                range: MemoryRange::new(0x100000..0x101000),
890                backing: MappingBacking::File {
891                    mappable: device,
892                    file_offset: 0,
893                },
894                writable: true,
895                mapping_type: MappingType::Device,
896                policy: MemoryPolicy::none(),
897            })
898            .await
899            .unwrap();
900
901        let provider = DmaRegionProvider {
902            req_send: client.req_send.clone(),
903        };
904        let regions = provider.get_regions().await.unwrap();
905
906        // Only the DMA-target mapping should appear.
907        assert_eq!(regions.len(), 1);
908        assert_eq!(regions[0].guest_address, 0);
909        assert_eq!(regions[0].size, 0x100000);
910        assert_eq!(regions[0].file_offset, 0);
911    }
912
913    #[pal_async::async_test]
914    async fn test_no_dma_targets_returns_empty(spawn: impl Spawn) {
915        let mm = MappingManager::new_without_primary(&spawn, 0x100000, None);
916        let client = mm.client().clone();
917
918        let mappable: Mappable = sparse_mmap::alloc_shared_memory(0x1000, "test")
919            .unwrap()
920            .into();
921
922        client
923            .add_mapping(MappingParams {
924                range: MemoryRange::new(0..0x1000),
925                backing: MappingBacking::File {
926                    mappable,
927                    file_offset: 0,
928                },
929                writable: true,
930                mapping_type: MappingType::Device,
931                policy: MemoryPolicy::none(),
932            })
933            .await
934            .unwrap();
935
936        let provider = DmaRegionProvider {
937            req_send: client.req_send.clone(),
938        };
939        let regions = provider.get_regions().await.unwrap();
940        assert!(regions.is_empty());
941    }
942
943    /// Helper: create a MappingManagerTask and add a mapping.
944    async fn task_with_mapping() -> (MappingManagerTask, MappingParams) {
945        let mut task = MappingManagerTask::new();
946        let mappable: Mappable = sparse_mmap::alloc_shared_memory(0x10000, "test")
947            .unwrap()
948            .into();
949        let params = MappingParams {
950            range: MemoryRange::new(0..0x10000),
951            backing: MappingBacking::File {
952                mappable,
953                file_offset: 0,
954            },
955            writable: true,
956            mapping_type: MappingType::Ram,
957            policy: MemoryPolicy::none(),
958        };
959        task.add_mapping(params.clone()).await.unwrap();
960        (task, params)
961    }
962
963    /// Helper: add a mapper to a task and collect the MapEager/MapLazy messages
964    /// it receives.
965    async fn add_mapper_and_drain(
966        task: &mut MappingManagerTask,
967        eager: bool,
968    ) -> (MapperId, Vec<MapperRequest>) {
969        let (send, mut recv) = mesh::channel();
970        let id = task.add_mapper(send, eager).await.unwrap();
971        // Drain all pending messages.
972        let mut msgs = Vec::new();
973        while let Ok(msg) = recv.try_recv() {
974            msgs.push(msg);
975        }
976        (id, msgs)
977    }
978
979    #[pal_async::async_test]
980    async fn test_eager_mapper_gets_replay(_spawn: impl Spawn) {
981        let (mut task, _params) = task_with_mapping().await;
982
983        let (send, mut recv) = mesh::channel();
984        // add_mapper for eager blocks on the MapEager Rpc, so drive both
985        // concurrently.
986        let (id, _) = futures::join!(task.add_mapper(send, true), async {
987            let msg = recv.recv().await.unwrap();
988            match msg {
989                MapperRequest::MapEager(rpc) => {
990                    let (params, rpc) = rpc.split();
991                    assert_eq!(params.range, MemoryRange::new(0..0x10000));
992                    rpc.complete(Ok(()));
993                }
994                other => panic!("expected MapEager, got {other:?}"),
995            }
996        });
997        let _ = id;
998    }
999
1000    #[pal_async::async_test]
1001    async fn test_lazy_mapper_no_replay(_spawn: impl Spawn) {
1002        let (mut task, _params) = task_with_mapping().await;
1003
1004        let (_id, msgs) = add_mapper_and_drain(&mut task, false).await;
1005
1006        // Lazy mapper should receive no messages on creation.
1007        assert!(msgs.is_empty(), "lazy mapper should not get replay");
1008    }
1009
1010    #[pal_async::async_test]
1011    async fn test_add_mapping_pushes_only_to_eager(_spawn: impl Spawn) {
1012        let mut task = MappingManagerTask::new();
1013
1014        // Add one eager and one lazy mapper.
1015        let (eager_send, mut eager_recv) = mesh::channel();
1016        let _eager_id = task.add_mapper(eager_send, true).await.unwrap();
1017
1018        let (lazy_send, mut lazy_recv) = mesh::channel();
1019        let _lazy_id = task.add_mapper(lazy_send, false).await.unwrap();
1020
1021        // Add a mapping.
1022        let mappable: Mappable = sparse_mmap::alloc_shared_memory(0x1000, "test")
1023            .unwrap()
1024            .into();
1025        let params = MappingParams {
1026            range: MemoryRange::new(0..0x1000),
1027            backing: MappingBacking::File {
1028                mappable,
1029                file_offset: 0,
1030            },
1031            writable: true,
1032            mapping_type: MappingType::Device,
1033            policy: MemoryPolicy::none(),
1034        };
1035
1036        // The eager mapper needs to respond to the MapEager Rpc.
1037        let add_future = task.add_mapping(params);
1038        // Drive the add_mapping by responding to the eager mapper's Rpc.
1039        let (add_result, _) = futures::join!(add_future, async {
1040            let msg = eager_recv.recv().await.unwrap();
1041            match msg {
1042                MapperRequest::MapEager(rpc) => rpc.complete(Ok(())),
1043                other => panic!("expected MapEager, got {other:?}"),
1044            }
1045        });
1046        add_result.unwrap();
1047
1048        // Lazy mapper should have received nothing.
1049        assert!(
1050            lazy_recv.try_recv().is_err(),
1051            "lazy mapper should not be notified on add_mapping"
1052        );
1053    }
1054
1055    #[pal_async::async_test]
1056    async fn test_upgrade_to_eager_replays(_spawn: impl Spawn) {
1057        let (mut task, _params) = task_with_mapping().await;
1058
1059        // Add a lazy mapper — no replay.
1060        let (send, mut recv) = mesh::channel();
1061        let id = task.add_mapper(send, false).await.unwrap();
1062        assert!(
1063            recv.try_recv().is_err(),
1064            "lazy mapper should not get replay"
1065        );
1066
1067        // Upgrade to eager — should replay existing mappings.
1068        let upgrade_future = task.upgrade_to_eager(id);
1069        let (result, _) = futures::join!(upgrade_future, async {
1070            let msg = recv.recv().await.unwrap();
1071            match msg {
1072                MapperRequest::MapEager(rpc) => {
1073                    let (params, rpc) = rpc.split();
1074                    assert_eq!(params.range, MemoryRange::new(0..0x10000));
1075                    rpc.complete(Ok(()));
1076                }
1077                other => panic!("expected MapEager during upgrade, got {other:?}"),
1078            }
1079            // Respond to the SetEager RPC.
1080            let msg = recv.recv().await.unwrap();
1081            match msg {
1082                MapperRequest::SetEager(rpc) => rpc.complete(()),
1083                other => panic!("expected SetEager, got {other:?}"),
1084            }
1085        });
1086        result.unwrap();
1087
1088        // Verify the mapper is now marked eager.
1089        assert!(task.mappers.mappers[id.0].eager);
1090    }
1091
1092    #[pal_async::async_test]
1093    async fn test_upgrade_already_eager_is_noop(_spawn: impl Spawn) {
1094        let (mut task, _params) = task_with_mapping().await;
1095
1096        // Add an eager mapper (responds to replay).
1097        let (send, mut recv) = mesh::channel();
1098        let upgrade_future = task.add_mapper(send, true);
1099        let (id, _) = futures::join!(upgrade_future, async {
1100            let msg = recv.recv().await.unwrap();
1101            match msg {
1102                MapperRequest::MapEager(rpc) => rpc.complete(Ok(())),
1103                other => panic!("expected MapEager, got {other:?}"),
1104            }
1105        });
1106        let id = id.unwrap();
1107
1108        // Upgrade again — should be a no-op, no messages sent.
1109        task.upgrade_to_eager(id).await.unwrap();
1110        assert!(
1111            recv.try_recv().is_err(),
1112            "upgrade of already-eager mapper should send nothing"
1113        );
1114    }
1115
1116    #[pal_async::async_test]
1117    async fn test_after_upgrade_new_mappings_are_pushed(_spawn: impl Spawn) {
1118        let mut task = MappingManagerTask::new();
1119
1120        // Add a lazy mapper.
1121        let (send, mut recv) = mesh::channel();
1122        let id = task.add_mapper(send, false).await.unwrap();
1123
1124        // Upgrade to eager (no existing mappings, so no replay, but SetEager
1125        // is still sent as an RPC).
1126        let upgrade_future = task.upgrade_to_eager(id);
1127        let (result, _) = futures::join!(upgrade_future, async {
1128            let msg = recv.recv().await.unwrap();
1129            match msg {
1130                MapperRequest::SetEager(rpc) => rpc.complete(()),
1131                other => panic!("expected SetEager, got {other:?}"),
1132            }
1133        });
1134        result.unwrap();
1135
1136        // Now add a mapping — should be pushed to the upgraded mapper.
1137        let mappable: Mappable = sparse_mmap::alloc_shared_memory(0x1000, "test")
1138            .unwrap()
1139            .into();
1140        let params = MappingParams {
1141            range: MemoryRange::new(0..0x1000),
1142            backing: MappingBacking::File {
1143                mappable,
1144                file_offset: 0,
1145            },
1146            writable: true,
1147            mapping_type: MappingType::Device,
1148            policy: MemoryPolicy::none(),
1149        };
1150
1151        let add_future = task.add_mapping(params);
1152        let (result, _) = futures::join!(add_future, async {
1153            let msg = recv.recv().await.unwrap();
1154            match msg {
1155                MapperRequest::MapEager(rpc) => rpc.complete(Ok(())),
1156                other => panic!("expected MapEager after upgrade, got {other:?}"),
1157            }
1158        });
1159        result.unwrap();
1160    }
1161
1162    #[pal_async::async_test]
1163    async fn test_send_mappings_for_lazy(_spawn: impl Spawn) {
1164        let (mut task, _params) = task_with_mapping().await;
1165
1166        // Add a lazy mapper.
1167        let (send, mut recv) = mesh::channel();
1168        let id = task.add_mapper(send, false).await.unwrap();
1169
1170        // Request mappings for the range (simulates page fault path).
1171        task.send_mappings(id, MemoryRange::new(0..0x10000));
1172
1173        // Should receive a MapLazy.
1174        let msg = recv.recv().await.unwrap();
1175        match msg {
1176            MapperRequest::MapLazy(params) => {
1177                assert_eq!(params.range, MemoryRange::new(0..0x10000));
1178            }
1179            other => panic!("expected MapLazy, got {other:?}"),
1180        }
1181    }
1182
1183    #[pal_async::async_test]
1184    async fn test_send_mappings_gap_sends_no_mapping(_spawn: impl Spawn) {
1185        let (mut task, _params) = task_with_mapping().await;
1186
1187        let (send, mut recv) = mesh::channel();
1188        let id = task.add_mapper(send, false).await.unwrap();
1189
1190        // Request a range that is partially unmapped (0x10000..0x20000 has no mapping).
1191        task.send_mappings(id, MemoryRange::new(0x10000..0x20000));
1192
1193        let msg = recv.recv().await.unwrap();
1194        match msg {
1195            MapperRequest::NoMapping(range) => {
1196                assert_eq!(range, MemoryRange::new(0x10000..0x20000));
1197            }
1198            other => panic!("expected NoMapping, got {other:?}"),
1199        }
1200    }
1201
1202    #[pal_async::async_test]
1203    async fn test_remove_mapping_invalidates_both_eager_and_lazy(_spawn: impl Spawn) {
1204        let (mut task, _params) = task_with_mapping().await;
1205
1206        // Add eager mapper (responds to replay).
1207        let (eager_send, mut eager_recv) = mesh::channel();
1208        let add_future = task.add_mapper(eager_send, true);
1209        let (_eager_id, _) = futures::join!(add_future, async {
1210            let msg = eager_recv.recv().await.unwrap();
1211            match msg {
1212                MapperRequest::MapEager(rpc) => rpc.complete(Ok(())),
1213                other => panic!("expected MapEager, got {other:?}"),
1214            }
1215        });
1216
1217        // Add lazy mapper and fault in the mapping.
1218        let (lazy_send, mut lazy_recv) = mesh::channel();
1219        let lazy_id = task.add_mapper(lazy_send, false).await.unwrap();
1220        task.send_mappings(lazy_id, MemoryRange::new(0..0x10000));
1221        // Consume the MapLazy.
1222        let _ = lazy_recv.recv().await.unwrap();
1223
1224        // Remove the mapping — both should get Unmap.
1225        let remove_future = task.remove_mappings(MemoryRange::new(0..0x10000));
1226        let ((), _, _) = futures::join!(
1227            remove_future,
1228            async {
1229                let msg = eager_recv.recv().await.unwrap();
1230                match msg {
1231                    MapperRequest::Unmap(rpc) => {
1232                        let (range, rpc) = rpc.split();
1233                        assert_eq!(range, MemoryRange::new(0..0x10000));
1234                        rpc.complete(());
1235                    }
1236                    other => panic!("expected Unmap for eager, got {other:?}"),
1237                }
1238            },
1239            async {
1240                let msg = lazy_recv.recv().await.unwrap();
1241                match msg {
1242                    MapperRequest::Unmap(rpc) => {
1243                        let (range, rpc) = rpc.split();
1244                        assert_eq!(range, MemoryRange::new(0..0x10000));
1245                        rpc.complete(());
1246                    }
1247                    other => panic!("expected Unmap for lazy, got {other:?}"),
1248                }
1249            }
1250        );
1251    }
1252
1253    /// Helper: create a task with two mappings for rollback tests.
1254    async fn task_with_two_mappings() -> MappingManagerTask {
1255        let mut task = MappingManagerTask::new();
1256        for (start, end) in [(0u64, 0x10000u64), (0x10000, 0x20000)] {
1257            let mappable: Mappable = sparse_mmap::alloc_shared_memory(0x10000, "test")
1258                .unwrap()
1259                .into();
1260            task.add_mapping(MappingParams {
1261                range: MemoryRange::new(start..end),
1262                backing: MappingBacking::File {
1263                    mappable,
1264                    file_offset: 0,
1265                },
1266                writable: true,
1267                mapping_type: MappingType::Ram,
1268                policy: MemoryPolicy::none(),
1269            })
1270            .await
1271            .unwrap();
1272        }
1273        task
1274    }
1275
1276    #[pal_async::async_test]
1277    async fn test_add_eager_mapper_rollback_on_replay_failure(_spawn: impl Spawn) {
1278        let mut task = task_with_two_mappings().await;
1279
1280        // Create a mapper that succeeds on the first mapping but fails
1281        // on the second.
1282        let (send, mut recv) = mesh::channel();
1283        let add_future = task.add_mapper(send, true);
1284        let (result, _) = futures::join!(add_future, async {
1285            // First MapEager: succeed.
1286            let msg = recv.recv().await.unwrap();
1287            match msg {
1288                MapperRequest::MapEager(rpc) => rpc.complete(Ok(())),
1289                other => panic!("expected MapEager #1, got {other:?}"),
1290            }
1291            // Second MapEager: fail.
1292            let msg = recv.recv().await.unwrap();
1293            match msg {
1294                MapperRequest::MapEager(rpc) => {
1295                    rpc.complete(Err(RemoteError::new(std::io::Error::other(
1296                        "simulated failure",
1297                    ))));
1298                }
1299                other => panic!("expected MapEager #2, got {other:?}"),
1300            }
1301        });
1302
1303        // add_mapper should return an error.
1304        assert!(result.is_err());
1305
1306        // The mapper should have been removed from the slab.
1307        assert_eq!(task.mappers.mappers.len(), 0);
1308
1309        // The first mapping's active_mappers should have been cleaned up.
1310        for mapping in &task.mappings {
1311            assert!(
1312                mapping.active_mappers.is_empty(),
1313                "active_mappers should be empty after rollback, got {:?} for {}",
1314                mapping.active_mappers,
1315                mapping.params.range
1316            );
1317        }
1318
1319        // Behavioral check: adding another eager mapper should succeed and
1320        // replay both mappings cleanly (no stale state from failed mapper).
1321        let (send2, mut recv2) = mesh::channel();
1322        let add_future2 = task.add_mapper(send2, true);
1323        let (result2, _) = futures::join!(add_future2, async {
1324            for _ in 0..2 {
1325                let msg = recv2.recv().await.unwrap();
1326                match msg {
1327                    MapperRequest::MapEager(rpc) => rpc.complete(Ok(())),
1328                    other => panic!("expected MapEager during second add, got {other:?}"),
1329                }
1330            }
1331        });
1332        assert!(result2.is_ok());
1333    }
1334
1335    #[pal_async::async_test]
1336    async fn test_add_mapping_rollback_on_eager_failure(_spawn: impl Spawn) {
1337        let mut task = MappingManagerTask::new();
1338
1339        // Add two eager mappers.
1340        let (send1, mut recv1) = mesh::channel();
1341        let _id1 = task.add_mapper(send1, true).await.unwrap();
1342
1343        let (send2, mut recv2) = mesh::channel();
1344        let _id2 = task.add_mapper(send2, true).await.unwrap();
1345
1346        // Add a mapping. Mapper 1 succeeds, mapper 2 fails.
1347        let mappable: Mappable = sparse_mmap::alloc_shared_memory(0x1000, "test")
1348            .unwrap()
1349            .into();
1350        let params = MappingParams {
1351            range: MemoryRange::new(0..0x1000),
1352            backing: MappingBacking::File {
1353                mappable,
1354                file_offset: 0,
1355            },
1356            writable: true,
1357            mapping_type: MappingType::Device,
1358            policy: MemoryPolicy::none(),
1359        };
1360
1361        let add_future = task.add_mapping(params);
1362        let (result, _, _) = futures::join!(
1363            add_future,
1364            async {
1365                // Mapper 1: succeed.
1366                let msg = recv1.recv().await.unwrap();
1367                match msg {
1368                    MapperRequest::MapEager(rpc) => rpc.complete(Ok(())),
1369                    other => panic!("expected MapEager, got {other:?}"),
1370                }
1371                // Mapper 1 should then receive an Unmap (rollback).
1372                let msg = recv1.recv().await.unwrap();
1373                match msg {
1374                    MapperRequest::Unmap(rpc) => {
1375                        let (range, rpc) = rpc.split();
1376                        assert_eq!(range, MemoryRange::new(0..0x1000));
1377                        rpc.complete(());
1378                    }
1379                    other => panic!("expected Unmap rollback, got {other:?}"),
1380                }
1381            },
1382            async {
1383                // Mapper 2: fail.
1384                let msg = recv2.recv().await.unwrap();
1385                match msg {
1386                    MapperRequest::MapEager(rpc) => {
1387                        rpc.complete(Err(RemoteError::new(std::io::Error::other(
1388                            "simulated failure",
1389                        ))));
1390                    }
1391                    other => panic!("expected MapEager, got {other:?}"),
1392                }
1393            }
1394        );
1395
1396        // add_mapping should return an error.
1397        assert!(result.is_err());
1398
1399        // The mapping should not have been added.
1400        assert!(task.mappings.is_empty());
1401    }
1402
1403    #[pal_async::async_test]
1404    async fn test_upgrade_to_eager_rollback_on_failure(_spawn: impl Spawn) {
1405        let mut task = task_with_two_mappings().await;
1406
1407        // Add a lazy mapper.
1408        let (send, mut recv) = mesh::channel();
1409        let id = task.add_mapper(send, false).await.unwrap();
1410        assert!(!task.mappers.mappers[id.0].eager);
1411
1412        // Upgrade: succeed on first mapping, fail on second.
1413        let upgrade_future = task.upgrade_to_eager(id);
1414        let (result, _) = futures::join!(upgrade_future, async {
1415            // First MapEager: succeed.
1416            let msg = recv.recv().await.unwrap();
1417            match msg {
1418                MapperRequest::MapEager(rpc) => rpc.complete(Ok(())),
1419                other => panic!("expected MapEager #1, got {other:?}"),
1420            }
1421            // Second MapEager: fail.
1422            let msg = recv.recv().await.unwrap();
1423            match msg {
1424                MapperRequest::MapEager(rpc) => {
1425                    rpc.complete(Err(RemoteError::new(std::io::Error::other(
1426                        "simulated failure",
1427                    ))));
1428                }
1429                other => panic!("expected MapEager #2, got {other:?}"),
1430            }
1431        });
1432
1433        // upgrade_to_eager should return an error.
1434        assert!(result.is_err());
1435
1436        // The mapper should still be lazy (rolled back).
1437        assert!(!task.mappers.mappers[id.0].eager);
1438
1439        // The first mapping should still have this mapper in active_mappers
1440        // (it was successfully replayed), so it will get Unmap when that
1441        // mapping is removed.
1442        assert!(
1443            task.mappings[0].active_mappers.contains(&id),
1444            "first mapping should retain mapper in active_mappers"
1445        );
1446        assert!(
1447            !task.mappings[1].active_mappers.contains(&id),
1448            "second mapping should not have mapper (replay failed)"
1449        );
1450
1451        // The mapper should still be in the slab (not removed, just stayed lazy).
1452        assert!(task.mappers.mappers.contains(id.0));
1453
1454        // Behavioral check: a subsequent add_mapping should NOT push to
1455        // this mapper (it's still lazy).
1456        let mappable: Mappable = sparse_mmap::alloc_shared_memory(0x1000, "test")
1457            .unwrap()
1458            .into();
1459        task.add_mapping(MappingParams {
1460            range: MemoryRange::new(0x20000..0x21000),
1461            backing: MappingBacking::File {
1462                mappable,
1463                file_offset: 0,
1464            },
1465            writable: true,
1466            mapping_type: MappingType::Device,
1467            policy: MemoryPolicy::none(),
1468        })
1469        .await
1470        .unwrap();
1471
1472        // The lazy mapper should have received nothing.
1473        assert!(
1474            recv.try_recv().is_err(),
1475            "lazy mapper should not receive add_mapping push after failed upgrade"
1476        );
1477    }
1478
1479    #[pal_async::async_test]
1480    async fn test_eager_page_fault_fails_immediately(_spawn: impl Spawn) {
1481        use super::super::va_mapper::VaMapper;
1482
1483        // Create a VaMapper directly, manually driving the AddMapper RPC.
1484        let (req_send, mut req_recv) = mesh::channel::<MappingRequest>();
1485        let mapper_future = VaMapper::new(
1486            req_send,
1487            0x10000,
1488            None,
1489            None,
1490            true, // eager
1491            MapperRole::Primary {
1492                supports_memory_fault_resolution: false,
1493            },
1494        );
1495        let (mapper, _) = futures::join!(mapper_future, async {
1496            let msg = req_recv.recv().await.unwrap();
1497            match msg {
1498                MappingRequest::AddMapper(rpc) => {
1499                    rpc.handle_failable_sync(|params| {
1500                        assert!(params.eager);
1501                        Ok::<_, MappingError>(MapperId(0))
1502                    });
1503                }
1504                _ => panic!("expected AddMapper"),
1505            }
1506        });
1507        let mapper = mapper.unwrap();
1508        assert!(mapper.is_eager());
1509
1510        // No mappings have been established, so a page fault on a
1511        // file-backed address should fail immediately rather than
1512        // trying to request the mapping lazily.
1513        let action = mapper.page_fault(0x1000, 0x1000, false, false);
1514        assert!(
1515            matches!(action, guestmem::PageFaultAction::Fail(_)),
1516            "eager mapper should fail page faults on unmapped file-backed ranges"
1517        );
1518    }
1519
1520    #[pal_async::async_test]
1521    async fn test_va_mapper_drop_removes_mapper(_spawn: impl Spawn) {
1522        use super::super::va_mapper::VaMapper;
1523
1524        let (req_send, mut req_recv) = mesh::channel::<MappingRequest>();
1525        let mapper_future = VaMapper::new(
1526            req_send,
1527            0x10000,
1528            None,
1529            None,
1530            true, // eager
1531            MapperRole::Primary {
1532                supports_memory_fault_resolution: false,
1533            },
1534        );
1535        let (mapper, mapper_req_send) = futures::join!(mapper_future, async {
1536            let msg = req_recv.recv().await.unwrap();
1537            match msg {
1538                MappingRequest::AddMapper(rpc) => {
1539                    let (params, rpc) = rpc.split();
1540                    assert!(params.eager);
1541                    rpc.complete(Ok(MapperId(7)));
1542                    params.send
1543                }
1544                _ => panic!("expected AddMapper"),
1545            }
1546        });
1547        drop(mapper.unwrap());
1548
1549        match req_recv.recv().await.unwrap() {
1550            MappingRequest::RemoveMapper(id) => assert_eq!(id, MapperId(7)),
1551            _ => panic!("expected RemoveMapper"),
1552        }
1553
1554        // In production, MappingManagerTask::remove_mapper drops this sender.
1555        // Do the same here so the mapper thread can exit.
1556        drop(mapper_req_send);
1557    }
1558
1559    #[pal_async::async_test]
1560    async fn test_lazy_page_fault_requests_mapping(spawn: impl Spawn) {
1561        let _ = spawn;
1562        let (manager_thread, manager_driver) =
1563            pal_async::DefaultPool::spawn_on_thread("mapping-manager-test");
1564        let mm = MappingManager::new_without_primary(&manager_driver, 0x10000, None);
1565        let client = mm.client().clone();
1566
1567        // Add a mapping so the lazy mapper can find it.
1568        let mappable: Mappable = sparse_mmap::alloc_shared_memory(0x10000, "test")
1569            .unwrap()
1570            .into();
1571        client
1572            .add_mapping(MappingParams {
1573                range: MemoryRange::new(0..0x10000),
1574                backing: MappingBacking::File {
1575                    mappable,
1576                    file_offset: 0,
1577                },
1578                writable: true,
1579                mapping_type: MappingType::Device,
1580                policy: MemoryPolicy::none(),
1581            })
1582            .await
1583            .unwrap();
1584
1585        // Create a lazy mapper.
1586        let mapper = client.new_mapper(false).await.unwrap();
1587        assert!(!mapper.is_eager());
1588
1589        // page_fault calls block_on(request_mapping(...)), which blocks the
1590        // calling thread. The MappingManager task is running on its own pool
1591        // thread, so it can still serve SendMappings while this test thread is
1592        // blocked here.
1593        let action = mapper.page_fault(0x1000, 0x1000, false, false);
1594        assert!(
1595            matches!(action, guestmem::PageFaultAction::Retry),
1596            "lazy mapper should request mapping on page fault and succeed"
1597        );
1598
1599        drop(mapper);
1600        drop(client);
1601        drop(mm);
1602        drop(manager_driver);
1603        manager_thread.join().unwrap();
1604    }
1605
1606    /// Tests that creating an eager mapper succeeds even when mappings
1607    /// already exist (the mapper thread must be running to service the
1608    /// replay RPCs during AddMapper).
1609    #[pal_async::async_test]
1610    async fn test_eager_mapper_with_existing_mappings(spawn: impl Spawn) {
1611        let _ = spawn;
1612        let (manager_thread, manager_driver) =
1613            pal_async::DefaultPool::spawn_on_thread("mapping-manager-test");
1614        let mm = MappingManager::new_without_primary(&manager_driver, 0x10000, None);
1615        let client = mm.client().clone();
1616
1617        // Add a mapping while no mappers exist — it is stored for replay.
1618        let mappable: Mappable = sparse_mmap::alloc_shared_memory(0x10000, "test")
1619            .unwrap()
1620            .into();
1621        client
1622            .add_mapping(MappingParams {
1623                range: MemoryRange::new(0..0x10000),
1624                backing: MappingBacking::File {
1625                    mappable,
1626                    file_offset: 0,
1627                },
1628                writable: true,
1629                mapping_type: MappingType::Ram,
1630                policy: MemoryPolicy::none(),
1631            })
1632            .await
1633            .unwrap();
1634
1635        // Create an eager mapper. The mapper thread must be spawned before
1636        // the AddMapper RPC so it can respond to replay MapEager RPCs.
1637        let mapper = client.new_mapper(true).await.unwrap();
1638        assert!(mapper.is_eager());
1639
1640        drop(mapper);
1641        drop(client);
1642        drop(mm);
1643        drop(manager_driver);
1644        manager_thread.join().unwrap();
1645    }
1646
1647    #[pal_async::async_test]
1648    async fn test_new_mapper_upgrades_cached_lazy_to_eager(spawn: impl Spawn) {
1649        let _ = spawn;
1650        let (manager_thread, manager_driver) =
1651            pal_async::DefaultPool::spawn_on_thread("mapping-manager-test");
1652        let mm = MappingManager::new_without_primary(&manager_driver, 0x20000, None);
1653        let client = mm.client().clone();
1654
1655        // Add a mapping first so replay has something to push.
1656        let mappable: Mappable = sparse_mmap::alloc_shared_memory(0x10000, "test")
1657            .unwrap()
1658            .into();
1659        client
1660            .add_mapping(MappingParams {
1661                range: MemoryRange::new(0..0x10000),
1662                backing: MappingBacking::File {
1663                    mappable,
1664                    file_offset: 0,
1665                },
1666                writable: true,
1667                mapping_type: MappingType::Device,
1668                policy: MemoryPolicy::none(),
1669            })
1670            .await
1671            .unwrap();
1672
1673        // Create a lazy mapper first — this gets cached.
1674        let lazy = client.new_mapper(false).await.unwrap();
1675        assert!(!lazy.is_eager());
1676
1677        // Now request an eager mapper — should upgrade the cached lazy one.
1678        let eager = client.new_mapper(true).await.unwrap();
1679
1680        // They should be the same Arc (same underlying mapper).
1681        assert!(Arc::ptr_eq(&lazy, &eager));
1682
1683        // Queue one more eager mapping. The manager sends SetEager before
1684        // returning from the upgrade RPC, and this MapEager is queued after
1685        // that, so a successful add_mapping means the mapper thread has
1686        // processed SetEager.
1687        let mappable: Mappable = sparse_mmap::alloc_shared_memory(0x1000, "test")
1688            .unwrap()
1689            .into();
1690        client
1691            .add_mapping(MappingParams {
1692                range: MemoryRange::new(0x10000..0x11000),
1693                backing: MappingBacking::File {
1694                    mappable,
1695                    file_offset: 0,
1696                },
1697                writable: true,
1698                mapping_type: MappingType::Device,
1699                policy: MemoryPolicy::none(),
1700            })
1701            .await
1702            .unwrap();
1703
1704        // The mapper is now observably eager.
1705        assert!(eager.is_eager());
1706
1707        drop(eager);
1708        drop(lazy);
1709        drop(client);
1710        drop(mm);
1711        drop(manager_driver);
1712        manager_thread.join().unwrap();
1713    }
1714}