Skip to main content

starnix_modules_ashmem/
lib.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use linux_uapi::{
6    ASHMEM_GET_NAME, ASHMEM_GET_PIN_STATUS, ASHMEM_GET_PROT_MASK, ASHMEM_GET_SIZE,
7    ASHMEM_IS_PINNED, ASHMEM_IS_UNPINNED, ASHMEM_NOT_PURGED, ASHMEM_PIN, ASHMEM_PURGE_ALL_CACHES,
8    ASHMEM_SET_NAME, ASHMEM_SET_PROT_MASK, ASHMEM_SET_SIZE, ASHMEM_UNPIN, ASHMEM_WAS_PURGED,
9};
10use once_cell::sync::OnceCell;
11use range_map::RangeMap;
12use starnix_core::device::DeviceOps;
13use starnix_core::mm::memory::MemoryObject;
14use starnix_core::mm::{
15    DesiredAddress, MappingName, MappingOptions, MemoryAccessor, MemoryAccessorExt, PAGE_SIZE,
16    ProtectionFlags,
17};
18use starnix_core::task::CurrentTask;
19use starnix_core::vfs::{
20    FileObject, FileOps, FsString, InputBuffer, NamespaceNode, OutputBuffer, SeekTarget,
21    default_ioctl, default_seek, fileops_impl_noop_sync,
22};
23use starnix_lifecycle::AtomicU32Counter;
24use starnix_sync::{FileOpsCore, Locked, Mutex, Unlocked};
25use starnix_syscalls::{SUCCESS, SyscallArg, SyscallResult};
26use starnix_uapi::errors::Errno;
27use starnix_uapi::math::round_up_to_increment;
28use starnix_uapi::open_flags::OpenFlags;
29use starnix_uapi::user_address::{UserAddress, UserCString, UserRef};
30use starnix_uapi::{ASHMEM_NAME_LEN, ashmem_pin, device_type, errno, error, off_t, uapi};
31use std::sync::Arc;
32
33/// Initializes the ashmem device.
34pub fn ashmem_device_init(locked: &mut Locked<Unlocked>, system_task: &CurrentTask) {
35    let kernel = system_task.kernel();
36    let registry = &kernel.device_registry;
37
38    registry
39        .register_misc_device(locked, system_task, "ashmem".into(), AshmemDevice::new())
40        .expect("can register ashmem");
41}
42
43#[derive(Clone)]
44pub struct AshmemDevice {
45    pub next_id: Arc<AtomicU32Counter>,
46}
47
48pub struct Ashmem {
49    memory: OnceCell<Arc<MemoryObject>>,
50    state: Mutex<AshmemState>,
51}
52
53struct AshmemState {
54    size: usize,
55    name: FsString,
56    prot_flags: ProtectionFlags,
57    unpinned: RangeMap<u32, bool>,
58    id: u32,
59}
60
61impl AshmemDevice {
62    pub fn new() -> AshmemDevice {
63        AshmemDevice { next_id: Arc::new(AtomicU32Counter::new(1)) }
64    }
65}
66
67impl DeviceOps for AshmemDevice {
68    fn open(
69        &self,
70        _locked: &mut Locked<FileOpsCore>,
71        _current_task: &CurrentTask,
72        _id: device_type::DeviceType,
73        _node: &NamespaceNode,
74        _flags: OpenFlags,
75    ) -> Result<Box<dyn FileOps>, Errno> {
76        let ashmem = Ashmem::new(self.next_id.next());
77        Ok(Box::new(ashmem))
78    }
79}
80
81impl Ashmem {
82    fn new(id: u32) -> Ashmem {
83        let state = AshmemState {
84            size: 0,
85            name: b"dev/ashmem\0".into(),
86            prot_flags: ProtectionFlags::ACCESS_FLAGS,
87            unpinned: RangeMap::<u32, bool>::default(),
88            id: id,
89        };
90
91        Ashmem { memory: OnceCell::new(), state: Mutex::new(state) }
92    }
93
94    fn memory(&self) -> Result<&Arc<MemoryObject>, Errno> {
95        self.memory.get().ok_or_else(|| errno!(EINVAL))
96    }
97
98    fn is_mapped(&self) -> bool {
99        self.memory.get().is_some()
100    }
101}
102
103impl FileOps for Ashmem {
104    fileops_impl_noop_sync!();
105
106    fn is_seekable(&self) -> bool {
107        true
108    }
109
110    fn seek(
111        &self,
112        _locked: &mut Locked<FileOpsCore>,
113        _file: &FileObject,
114        _current_task: &CurrentTask,
115        current_offset: off_t,
116        target: SeekTarget,
117    ) -> Result<off_t, Errno> {
118        if !self.is_mapped() {
119            return error!(EBADF);
120        }
121        let eof_offset = self.state.lock().size;
122        default_seek(current_offset, target, || Ok(eof_offset.try_into().unwrap()))
123    }
124
125    fn read(
126        &self,
127        _locked: &mut starnix_sync::Locked<FileOpsCore>,
128        _file: &FileObject,
129        _current_task: &CurrentTask,
130        offset: usize,
131        data: &mut dyn OutputBuffer,
132    ) -> Result<usize, Errno> {
133        let memory = self.memory().map_err(|_| errno!(EBADF))?;
134        let file_length = self.state.lock().size;
135        let actual = {
136            let want_read = data.available();
137            if offset < file_length {
138                let to_read =
139                    if file_length < offset + want_read { file_length - offset } else { want_read };
140                let buf =
141                    memory.read_to_vec(offset as u64, to_read as u64).map_err(|_| errno!(EIO))?;
142                data.write_all(&buf[..])?;
143                to_read
144            } else {
145                0
146            }
147        };
148        Ok(actual)
149    }
150
151    fn write(
152        &self,
153        _locked: &mut Locked<FileOpsCore>,
154        _file: &FileObject,
155        _current_task: &CurrentTask,
156        _offset: usize,
157        _data: &mut dyn InputBuffer,
158    ) -> Result<usize, Errno> {
159        error!(EINVAL)
160    }
161
162    fn mmap(
163        &self,
164        _locked: &mut Locked<FileOpsCore>,
165        file: &FileObject,
166        current_task: &CurrentTask,
167        addr: DesiredAddress,
168        memory_offset: u64,
169        length: usize,
170        prot_flags: ProtectionFlags,
171        mapping_options: MappingOptions,
172        _filename: NamespaceNode,
173    ) -> Result<UserAddress, Errno> {
174        let state = self.state.lock();
175        let size_paged_aligned = round_up_to_increment(state.size, *PAGE_SIZE as usize)?;
176
177        // Filter protections
178        if !state.prot_flags.contains(prot_flags) {
179            return error!(EINVAL);
180        }
181        // Filter size
182        if size_paged_aligned < length {
183            return error!(EINVAL);
184        }
185
186        let memory = self
187            .memory
188            .get_or_try_init(|| {
189                if size_paged_aligned == 0 {
190                    return error!(EINVAL);
191                }
192                // Round up to page boundary
193                let vmo = zx::Vmo::create(size_paged_aligned as u64).map_err(|_| errno!(ENOMEM))?;
194                let memory = MemoryObject::from(vmo).with_zx_name(b"starnix:ashmem");
195                Ok(Arc::new(memory))
196            })?
197            .clone();
198
199        let mapped_addr = current_task.mm()?.map_memory(
200            addr,
201            memory,
202            memory_offset,
203            length,
204            prot_flags,
205            file.max_access_for_memory_mapping(),
206            mapping_options,
207            MappingName::Ashmem(state.name.clone().into()),
208        )?;
209
210        Ok(mapped_addr)
211    }
212
213    fn ioctl(
214        &self,
215        locked: &mut Locked<Unlocked>,
216        file: &FileObject,
217        current_task: &CurrentTask,
218        request: u32,
219        arg: SyscallArg,
220    ) -> Result<SyscallResult, Errno> {
221        match request {
222            #[allow(unreachable_patterns)]
223            ASHMEM_SET_SIZE | starnix_uapi::arch32::ASHMEM_SET_SIZE => {
224                let mut state = self.state.lock();
225
226                if self.is_mapped() {
227                    return error!(EINVAL);
228                }
229                state.size = arg.into();
230                Ok(SUCCESS)
231            }
232            ASHMEM_GET_SIZE => Ok(self.state.lock().size.into()),
233            ASHMEM_SET_NAME => {
234                let mut state = self.state.lock();
235
236                if self.is_mapped() {
237                    return error!(EINVAL);
238                }
239                let mut name = current_task.read_c_string_to_vec(
240                    UserCString::new(current_task, arg),
241                    ASHMEM_NAME_LEN as usize,
242                )?;
243                name.push(0); // Add a null terminator
244
245                state.name = name.into();
246                Ok(SUCCESS)
247            }
248            ASHMEM_GET_NAME => {
249                let state = self.state.lock();
250                let name = &state.name[..];
251
252                current_task.write_memory(arg.into(), name)?;
253                Ok(SUCCESS)
254            }
255            #[allow(unreachable_patterns)]
256            ASHMEM_SET_PROT_MASK | starnix_uapi::arch32::ASHMEM_SET_PROT_MASK => {
257                let mut state = self.state.lock();
258                let prot_flags =
259                    ProtectionFlags::from_access_bits(arg.into()).ok_or_else(|| errno!(EINVAL))?;
260
261                // Do not allow protections to be increased
262                if !state.prot_flags.contains(prot_flags) {
263                    return error!(EINVAL);
264                }
265
266                state.prot_flags = prot_flags;
267                Ok(SUCCESS)
268            }
269            ASHMEM_GET_PROT_MASK => Ok(self.state.lock().prot_flags.bits().into()),
270            ASHMEM_PIN | ASHMEM_UNPIN | ASHMEM_GET_PIN_STATUS => {
271                let mut state = self.state.lock();
272
273                if !self.is_mapped() {
274                    return error!(EINVAL);
275                }
276
277                let user_ref = UserRef::<ashmem_pin>::new(arg.into());
278                let pin = current_task.read_object(user_ref)?;
279                let (lo, hi) =
280                    (pin.offset, pin.offset.checked_add(pin.len).ok_or_else(|| errno!(EFAULT))?);
281
282                // Bounds check
283                if (lo as usize) >= state.size || (hi as usize) > state.size {
284                    return error!(EINVAL);
285                }
286
287                // Aligned to page size
288                if (lo as u64) % *PAGE_SIZE != 0 || (hi as u64) % *PAGE_SIZE != 0 {
289                    return error!(EINVAL);
290                }
291
292                match request {
293                    ASHMEM_PIN => {
294                        for is_purged in state.unpinned.remove(lo..hi).iter() {
295                            if *is_purged {
296                                return Ok(ASHMEM_WAS_PURGED.into());
297                            }
298                        }
299
300                        return Ok(ASHMEM_NOT_PURGED.into());
301                    }
302                    ASHMEM_UNPIN => {
303                        // This method has must_use but we don't actually need to do any explicit
304                        // cleanup.
305                        let _ = state.unpinned.insert(lo..hi, false);
306                        return Ok(ASHMEM_IS_UNPINNED.into());
307                    }
308                    ASHMEM_GET_PIN_STATUS => {
309                        let mut intervals = state.unpinned.range(lo..hi);
310                        return match intervals.next() {
311                            Some(_) => Ok(ASHMEM_IS_UNPINNED.into()),
312                            None => Ok(ASHMEM_IS_PINNED.into()),
313                        };
314                    }
315                    _ => unreachable!(),
316                }
317            }
318            ASHMEM_PURGE_ALL_CACHES => {
319                let mut state = self.state.lock();
320                let memory = self.memory.get().ok_or_else(|| errno!(EINVAL))?;
321
322                if state.unpinned.is_empty() {
323                    return Ok(ASHMEM_IS_PINNED.into());
324                }
325                let unpinned: Vec<_> = state.unpinned.iter().map(|(k, _)| k.clone()).collect();
326                for range in unpinned.into_iter() {
327                    let (lo, hi) = (range.start as u64, range.end as u64);
328                    memory.op_range(zx::VmoOp::ZERO, lo, hi - lo).unwrap_or(());
329
330                    // This method has must_use but we don't actually need to do any explicit
331                    // cleanup.
332                    let _ = state.unpinned.insert(range, true);
333                }
334                return Ok(ASHMEM_IS_UNPINNED.into());
335            }
336            #[allow(unreachable_patterns)]
337            uapi::ASHMEM_GET_FILE_ID | uapi::arch32::ASHMEM_GET_FILE_ID => {
338                let state = self.state.lock();
339                current_task.write_object(arg.into(), &(state.id))?;
340                Ok(SUCCESS)
341            }
342            _ => default_ioctl(file, locked, current_task, request, arg),
343        }
344    }
345}