11// SPDX-License-Identifier: AGPL-3.0-or-later
22#![ allow(
33 unsafe_code,
4- reason = "memmap2 map_raw requires unsafe — containment zone"
4+ reason = "mmap/munmap require unsafe — containment zone"
55) ]
66
77//! RAII memory-mapped file region.
88//!
9- //! [`SafeMmapRegion`] wraps [`memmap2::MmapRaw`] for device BAR files, sysfs
10- //! resource files, and similar file-backed hardware mappings. The mapping
11- //! lifetime (munmap on drop) is handled by `memmap2` .
9+ //! [`SafeMmapRegion`] wraps `rustix::mm::mmap`/`munmap` for device BAR files,
10+ //! sysfs resource files, and similar file-backed hardware mappings. The mapping
11+ //! lifetime (munmap on drop) is managed by the RAII struct .
1212//!
1313//! This replaces the duplicate mmap patterns in:
1414//! - `akida-driver` `MmapRegion`
1515//! - `nvpmu` `Bar0Access`
1616//! - `display` V4L2 device mappings
1717
1818use std:: fs:: File ;
19+ use std:: os:: fd:: AsFd ;
1920use std:: path:: Path ;
2021use std:: ptr:: NonNull ;
2122
22- use memmap2:: { MmapOptions , MmapRaw } ;
23-
23+ use crate :: ExclusivePtr ;
2424use crate :: volatile_mmio:: VolatileMmio ;
2525
2626/// Error type for mmap operations.
@@ -48,20 +48,26 @@ pub enum MmapError {
4848 /// Underlying I/O error.
4949 source : std:: io:: Error ,
5050 } ,
51+ /// mmap returned a null pointer.
52+ #[ error( "mmap returned null for {path}" ) ]
53+ NullPointer {
54+ /// Path with null mmap result.
55+ path : String ,
56+ } ,
5157}
5258
5359/// RAII memory-mapped file region.
5460///
5561/// Maps a file (typically a PCI BAR resource, sysfs attribute, or device
56- /// node) into the process address space. Unmaps automatically on drop
57- /// (handled by [`memmap2`]).
62+ /// node) into the process address space. Unmaps automatically on drop.
5863///
5964/// ## Volatile MMIO
6065///
6166/// For hardware register access, use [`as_volatile`](SafeMmapRegion::as_volatile)
6267/// to get a [`VolatileMmio`] view with bounds-checked volatile reads and writes.
6368pub struct SafeMmapRegion {
64- mmap : MmapRaw ,
69+ ptr : ExclusivePtr ,
70+ size : usize ,
6571 _file : File ,
6672}
6773
@@ -74,15 +80,32 @@ impl SafeMmapRegion {
7480 /// the mmap syscall fails.
7581 pub fn map_shared_rw ( path : & Path ) -> Result < Self , MmapError > {
7682 let ( file, size) = Self :: open_validated ( path, true ) ?;
77- let mmap = MmapOptions :: new ( )
78- . len ( size)
79- . map_raw ( & file)
80- . map_err ( |source| MmapError :: MmapFailed {
81- path : path. display ( ) . to_string ( ) ,
82- source,
83- } ) ?;
83+ let path_str = path. display ( ) . to_string ( ) ;
84+
85+ // SAFETY: file is a valid open descriptor; size > 0 validated above;
86+ // PROT_READ|PROT_WRITE + MAP_SHARED are correct for device BAR files.
87+ let raw = unsafe {
88+ rustix:: mm:: mmap (
89+ std:: ptr:: null_mut ( ) ,
90+ size,
91+ rustix:: mm:: ProtFlags :: READ | rustix:: mm:: ProtFlags :: WRITE ,
92+ rustix:: mm:: MapFlags :: SHARED ,
93+ file. as_fd ( ) ,
94+ 0 ,
95+ )
96+ }
97+ . map_err ( |e| MmapError :: MmapFailed {
98+ path : path_str. clone ( ) ,
99+ source : e. into ( ) ,
100+ } ) ?;
101+
102+ let ptr = NonNull :: new ( raw. cast ( ) ) . ok_or ( MmapError :: NullPointer { path : path_str } ) ?;
84103 tracing:: debug!( path = %path. display( ) , size, "mmap region created (rw)" ) ;
85- Ok ( Self { mmap, _file : file } )
104+ Ok ( Self {
105+ ptr : ExclusivePtr :: new ( ptr) ,
106+ size,
107+ _file : file,
108+ } )
86109 }
87110
88111 /// Map a file as a shared read-only memory region.
@@ -93,15 +116,32 @@ impl SafeMmapRegion {
93116 /// the mmap syscall fails.
94117 pub fn map_shared_ro ( path : & Path ) -> Result < Self , MmapError > {
95118 let ( file, size) = Self :: open_validated ( path, false ) ?;
96- let mmap = MmapOptions :: new ( )
97- . len ( size)
98- . map_raw_read_only ( & file)
99- . map_err ( |source| MmapError :: MmapFailed {
100- path : path. display ( ) . to_string ( ) ,
101- source,
102- } ) ?;
119+ let path_str = path. display ( ) . to_string ( ) ;
120+
121+ // SAFETY: file is a valid open descriptor; size > 0 validated above;
122+ // PROT_READ + MAP_SHARED is correct for read-only device mappings.
123+ let raw = unsafe {
124+ rustix:: mm:: mmap (
125+ std:: ptr:: null_mut ( ) ,
126+ size,
127+ rustix:: mm:: ProtFlags :: READ ,
128+ rustix:: mm:: MapFlags :: SHARED ,
129+ file. as_fd ( ) ,
130+ 0 ,
131+ )
132+ }
133+ . map_err ( |e| MmapError :: MmapFailed {
134+ path : path_str. clone ( ) ,
135+ source : e. into ( ) ,
136+ } ) ?;
137+
138+ let ptr = NonNull :: new ( raw. cast ( ) ) . ok_or ( MmapError :: NullPointer { path : path_str } ) ?;
103139 tracing:: debug!( path = %path. display( ) , size, "mmap region created (ro)" ) ;
104- Ok ( Self { mmap, _file : file } )
140+ Ok ( Self {
141+ ptr : ExclusivePtr :: new ( ptr) ,
142+ size,
143+ _file : file,
144+ } )
105145 }
106146
107147 fn open_validated ( path : & Path , writable : bool ) -> Result < ( File , usize ) , MmapError > {
@@ -134,53 +174,41 @@ impl SafeMmapRegion {
134174 /// Size of the mapped region in bytes.
135175 #[ must_use]
136176 pub fn size ( & self ) -> usize {
137- self . mmap . len ( )
177+ self . size
138178 }
139179
140180 /// Get a [`VolatileMmio`] view for bounds-checked volatile register access.
141181 ///
142182 /// The returned view borrows this region — the mapping stays alive.
143- ///
144- /// # Panics
145- ///
146- /// Panics if `memmap2` returned a null pointer, which should never happen
147- /// after a successful `map_raw` call.
148183 #[ must_use]
149184 pub fn as_volatile ( & self ) -> VolatileMmio < ' _ > {
150- debug_assert ! (
151- self . mmap. len( ) > 0 ,
152- "SafeMmapRegion invariant: non-empty mapping (see open_validated)"
153- ) ;
154- // SAFETY: mmap is valid (from a successful map_raw call). as_mut_ptr
155- // returns a valid pointer for len() bytes. The VolatileMmio borrows
156- // self, preventing use-after-unmap.
157- unsafe {
158- VolatileMmio :: new (
159- NonNull :: new ( self . mmap . as_mut_ptr ( ) )
160- . expect ( "memmap2 returned null — mapping was successful" ) ,
161- self . mmap . len ( ) ,
162- )
163- }
185+ // SAFETY: ptr is valid for `size` bytes from the successful mmap.
186+ // The VolatileMmio borrows self, preventing use-after-unmap.
187+ unsafe { VolatileMmio :: new ( self . ptr . as_non_null ( ) , self . size ) }
164188 }
165189
166190 /// Raw pointer to the mapped region. Use [`as_volatile`](Self::as_volatile)
167191 /// for safe register access instead.
168- ///
169- /// # Panics
170- ///
171- /// Panics if `memmap2` returned a null pointer, which should never happen
172- /// after a successful `map_raw` call.
173192 #[ must_use]
174193 pub fn as_ptr ( & self ) -> NonNull < u8 > {
175- NonNull :: new ( self . mmap . as_mut_ptr ( ) )
176- . expect ( "memmap2 returned null — mapping was successful" )
194+ self . ptr . as_non_null ( )
195+ }
196+ }
197+
198+ impl Drop for SafeMmapRegion {
199+ fn drop ( & mut self ) {
200+ // SAFETY: ptr and size from a successful mmap in constructor;
201+ // Drop runs exactly once; no outstanding borrows possible.
202+ unsafe {
203+ let _ = rustix:: mm:: munmap ( self . ptr . as_ptr ( ) . cast ( ) , self . size ) ;
204+ }
177205 }
178206}
179207
180208impl std:: fmt:: Debug for SafeMmapRegion {
181209 fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
182210 f. debug_struct ( "SafeMmapRegion" )
183- . field ( "size" , & self . mmap . len ( ) )
211+ . field ( "size" , & self . size )
184212 . finish_non_exhaustive ( )
185213 }
186214}
@@ -238,7 +266,7 @@ mod tests {
238266
239267 assert ! ( mmio. read_u32( 0 ) . is_ok( ) ) ;
240268 assert ! ( mmio. read_u32( 4 ) . is_ok( ) ) ;
241- assert ! ( mmio. read_u32( 8 ) . is_err( ) ) ; // out of bounds
269+ assert ! ( mmio. read_u32( 8 ) . is_err( ) ) ;
242270 }
243271
244272 #[ test]
0 commit comments