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