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