libcamera/
framebuffer_map.rs

1use std::{collections::HashMap, mem::MaybeUninit};
2
3use thiserror::Error;
4
5use crate::framebuffer::AsFrameBuffer;
6
7#[derive(Debug, Error)]
8pub enum MemoryMappedFrameBufferError {
9    #[error("Plane {index} with offset {offset} and size {len} exceeds file descriptor size of {fd_len}")]
10    PlaneOutOfBounds {
11        index: usize,
12        offset: usize,
13        len: usize,
14        fd_len: usize,
15    },
16    #[error("Plane {index} has an invalid offset")]
17    InvalidOffset { index: usize },
18    #[error("mmap failed with {0:?}")]
19    MemoryMapError(std::io::Error),
20    #[error("mapping was created read-only; write access requested")]
21    NotWritable,
22}
23
24struct MappedPlane {
25    fd: i32,
26    offset: usize,
27    len: usize,
28}
29
30/// FrameBuffer wrapper, which exposes internal file descriptors as memory mapped [&[u8]] plane slices.
31pub struct MemoryMappedFrameBuffer<T: AsFrameBuffer> {
32    fb: T,
33    writable: bool,
34    /// fd -> (mapped_ptr, mapped_len, map_offset)
35    mmaps: HashMap<i32, (*mut core::ffi::c_void, usize, usize)>,
36    planes: Vec<MappedPlane>,
37}
38
39impl<T: AsFrameBuffer> MemoryMappedFrameBuffer<T> {
40    /// Memory map framebuffer, which implements [AsFrameBuffer].
41    ///
42    /// This might fail if framebuffer has invalid plane sizes/offsets or if [libc::mmap] fails itself.
43    pub fn new(fb: T) -> Result<Self, MemoryMappedFrameBufferError> {
44        Self::with_access(fb, false)
45    }
46
47    /// Memory map framebuffer for read/write access. Mapping will be `PROT_READ | PROT_WRITE`.
48    pub fn new_writable(fb: T) -> Result<Self, MemoryMappedFrameBufferError> {
49        Self::with_access(fb, true)
50    }
51
52    fn with_access(fb: T, writable: bool) -> Result<Self, MemoryMappedFrameBufferError> {
53        struct MapInfo {
54            /// Page-aligned start offset for mapping
55            start: usize,
56            /// Maximum offset used by data planes
57            end: usize,
58            /// Total file descriptor size
59            total_len: usize,
60        }
61
62        let mut planes = Vec::new();
63        let mut map_info: HashMap<i32, MapInfo> = HashMap::new();
64        let page_size = {
65            let ps = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
66            if ps > 0 {
67                ps as usize
68            } else {
69                4096
70            }
71        };
72
73        for (index, plane) in fb.planes().into_iter().enumerate() {
74            let fd = plane.fd();
75            let offset = plane
76                .offset()
77                .ok_or(MemoryMappedFrameBufferError::InvalidOffset { index })?;
78            let len = plane.len();
79
80            planes.push(MappedPlane { fd, offset, len });
81
82            // Find total FD length if not known yet
83            map_info.entry(fd).or_insert_with(|| {
84                let mut st = MaybeUninit::<libc::stat>::uninit();
85                let ret = unsafe { libc::fstat(fd, st.as_mut_ptr()) };
86                let total_len = if ret != 0 {
87                    0
88                } else {
89                    let st = unsafe { st.assume_init() };
90                    st.st_size as usize
91                };
92                MapInfo {
93                    start: offset,
94                    end: offset,
95                    total_len,
96                }
97            });
98
99            let info = map_info.get_mut(&fd).unwrap();
100
101            // If total_len is 0 (unknown for many DMA-BUFs), skip the bound check and let mmap fail if invalid.
102            if info.total_len > 0 && offset + len > info.total_len {
103                return Err(MemoryMappedFrameBufferError::PlaneOutOfBounds {
104                    index,
105                    offset,
106                    len,
107                    fd_len: info.total_len,
108                });
109            }
110
111            let aligned_start = offset - (offset % page_size);
112            info.start = info.start.min(aligned_start);
113            info.end = info.end.max(offset + len);
114        }
115
116        let mmaps = map_info
117            .iter()
118            .map(|(fd, info)| {
119                let map_len = info.end.saturating_sub(info.start);
120                let addr = unsafe {
121                    libc::mmap64(
122                        core::ptr::null_mut(),
123                        map_len,
124                        libc::PROT_READ | if writable { libc::PROT_WRITE } else { 0 },
125                        libc::MAP_SHARED,
126                        *fd,
127                        info.start as _,
128                    )
129                };
130
131                if addr == libc::MAP_FAILED {
132                    Err(MemoryMappedFrameBufferError::MemoryMapError(
133                        std::io::Error::last_os_error(),
134                    ))
135                } else {
136                    Ok((*fd, (addr, map_len, info.start)))
137                }
138            })
139            .collect::<Result<HashMap<i32, (*mut core::ffi::c_void, usize, usize)>, MemoryMappedFrameBufferError>>()
140            .unwrap();
141
142        Ok(Self {
143            fb,
144            writable,
145            mmaps,
146            planes,
147        })
148    }
149
150    /// Returns data slice for each plane within the framebuffer.
151    pub fn data(&self) -> Vec<&[u8]> {
152        self.planes
153            .iter()
154            .map(|plane| {
155                let (mmap_ptr, _, map_offset) = self.mmaps[&plane.fd];
156                let mmap_ptr: *const u8 = mmap_ptr.cast();
157                let offset = plane.offset - map_offset;
158                unsafe { core::slice::from_raw_parts(mmap_ptr.add(offset), plane.len) }
159            })
160            .collect()
161    }
162
163    /// Returns mutable data slices for each plane within the framebuffer. Mapping must be writable.
164    pub fn data_mut(&mut self) -> Result<Vec<&mut [u8]>, MemoryMappedFrameBufferError> {
165        if !self.writable {
166            return Err(MemoryMappedFrameBufferError::NotWritable);
167        }
168
169        Ok(self
170            .planes
171            .iter()
172            .map(|plane| {
173                let (mmap_ptr, _, map_offset) = self.mmaps[&plane.fd];
174                let mmap_ptr: *mut u8 = mmap_ptr.cast();
175                let offset = plane.offset - map_offset;
176                unsafe { core::slice::from_raw_parts_mut(mmap_ptr.add(offset), plane.len) }
177            })
178            .collect())
179    }
180
181    /// Returns true if this mapping was created with write access.
182    pub fn is_writable(&self) -> bool {
183        self.writable
184    }
185
186    /// Returns the mapped length for a given file descriptor, if present.
187    pub fn mapped_len(&self, fd: i32) -> Option<usize> {
188        self.mmaps.get(&fd).map(|(_, len, _)| *len)
189    }
190}
191
192impl<T: AsFrameBuffer> AsFrameBuffer for MemoryMappedFrameBuffer<T> {
193    unsafe fn ptr(&self) -> std::ptr::NonNull<libcamera_sys::libcamera_framebuffer_t> {
194        self.fb.ptr()
195    }
196}
197
198unsafe impl<T: AsFrameBuffer> Send for MemoryMappedFrameBuffer<T> {}
199
200impl<T: AsFrameBuffer> Drop for MemoryMappedFrameBuffer<T> {
201    fn drop(&mut self) {
202        // Unmap
203        for (_fd, (ptr, size, _map_offset)) in self.mmaps.drain() {
204            unsafe {
205                libc::munmap(ptr, size);
206            }
207        }
208    }
209}