blob: 95a03df3944f366c245819e2194865f19699171b [file] [log] [blame]
// Copyright 2023 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use linux_uapi::{
ASHMEM_GET_NAME, ASHMEM_GET_PIN_STATUS, ASHMEM_GET_PROT_MASK, ASHMEM_GET_SIZE,
ASHMEM_IS_PINNED, ASHMEM_IS_UNPINNED, ASHMEM_NOT_PURGED, ASHMEM_PIN, ASHMEM_PURGE_ALL_CACHES,
ASHMEM_SET_NAME, ASHMEM_SET_PROT_MASK, ASHMEM_SET_SIZE, ASHMEM_UNPIN, ASHMEM_WAS_PURGED,
};
use once_cell::sync::OnceCell;
use range_map::RangeMap;
use starnix_core::device::DeviceOps;
use starnix_core::mm::memory::MemoryObject;
use starnix_core::mm::{
DesiredAddress, MappingName, MappingOptions, MemoryAccessor, MemoryAccessorExt, PAGE_SIZE,
ProtectionFlags,
};
use starnix_core::task::CurrentTask;
use starnix_core::vfs::{
FileObject, FileOps, FsString, InputBuffer, NamespaceNode, OutputBuffer, SeekTarget,
default_ioctl, default_seek, fileops_impl_noop_sync,
};
use starnix_lifecycle::AtomicU32Counter;
use starnix_sync::{FileOpsCore, Locked, Mutex, Unlocked};
use starnix_syscalls::{SUCCESS, SyscallArg, SyscallResult};
use starnix_uapi::errors::Errno;
use starnix_uapi::math::round_up_to_increment;
use starnix_uapi::open_flags::OpenFlags;
use starnix_uapi::user_address::{UserAddress, UserCString, UserRef};
use starnix_uapi::{ASHMEM_NAME_LEN, ashmem_pin, device_type, errno, error, off_t, uapi};
use std::sync::Arc;
/// Initializes the ashmem device.
pub fn ashmem_device_init(locked: &mut Locked<Unlocked>, system_task: &CurrentTask) {
let kernel = system_task.kernel();
let registry = &kernel.device_registry;
registry
.register_misc_device(locked, system_task, "ashmem".into(), AshmemDevice::new())
.expect("can register ashmem");
}
#[derive(Clone)]
pub struct AshmemDevice {
pub next_id: Arc<AtomicU32Counter>,
}
pub struct Ashmem {
memory: OnceCell<Arc<MemoryObject>>,
state: Mutex<AshmemState>,
}
struct AshmemState {
size: usize,
name: FsString,
prot_flags: ProtectionFlags,
unpinned: RangeMap<u32, bool>,
id: u32,
}
impl AshmemDevice {
pub fn new() -> AshmemDevice {
AshmemDevice { next_id: Arc::new(AtomicU32Counter::new(1)) }
}
}
impl DeviceOps for AshmemDevice {
fn open(
&self,
_locked: &mut Locked<FileOpsCore>,
_current_task: &CurrentTask,
_id: device_type::DeviceType,
_node: &NamespaceNode,
_flags: OpenFlags,
) -> Result<Box<dyn FileOps>, Errno> {
let ashmem = Ashmem::new(self.next_id.next());
Ok(Box::new(ashmem))
}
}
impl Ashmem {
fn new(id: u32) -> Ashmem {
let state = AshmemState {
size: 0,
name: b"dev/ashmem\0".into(),
prot_flags: ProtectionFlags::ACCESS_FLAGS,
unpinned: RangeMap::<u32, bool>::default(),
id: id,
};
Ashmem { memory: OnceCell::new(), state: Mutex::new(state) }
}
fn memory(&self) -> Result<&Arc<MemoryObject>, Errno> {
self.memory.get().ok_or_else(|| errno!(EINVAL))
}
fn is_mapped(&self) -> bool {
self.memory.get().is_some()
}
}
impl FileOps for Ashmem {
fileops_impl_noop_sync!();
fn is_seekable(&self) -> bool {
true
}
fn seek(
&self,
_locked: &mut Locked<FileOpsCore>,
_file: &FileObject,
_current_task: &CurrentTask,
current_offset: off_t,
target: SeekTarget,
) -> Result<off_t, Errno> {
if !self.is_mapped() {
return error!(EBADF);
}
let eof_offset = self.state.lock().size;
default_seek(current_offset, target, || Ok(eof_offset.try_into().unwrap()))
}
fn read(
&self,
_locked: &mut starnix_sync::Locked<FileOpsCore>,
_file: &FileObject,
_current_task: &CurrentTask,
offset: usize,
data: &mut dyn OutputBuffer,
) -> Result<usize, Errno> {
let memory = self.memory().map_err(|_| errno!(EBADF))?;
let file_length = self.state.lock().size;
let actual = {
let want_read = data.available();
if offset < file_length {
let to_read =
if file_length < offset + want_read { file_length - offset } else { want_read };
let buf =
memory.read_to_vec(offset as u64, to_read as u64).map_err(|_| errno!(EIO))?;
data.write_all(&buf[..])?;
to_read
} else {
0
}
};
Ok(actual)
}
fn write(
&self,
_locked: &mut Locked<FileOpsCore>,
_file: &FileObject,
_current_task: &CurrentTask,
_offset: usize,
_data: &mut dyn InputBuffer,
) -> Result<usize, Errno> {
error!(EINVAL)
}
fn mmap(
&self,
_locked: &mut Locked<FileOpsCore>,
file: &FileObject,
current_task: &CurrentTask,
addr: DesiredAddress,
memory_offset: u64,
length: usize,
prot_flags: ProtectionFlags,
mapping_options: MappingOptions,
_filename: NamespaceNode,
) -> Result<UserAddress, Errno> {
let state = self.state.lock();
let size_paged_aligned = round_up_to_increment(state.size, *PAGE_SIZE as usize)?;
// Filter protections
if !state.prot_flags.contains(prot_flags) {
return error!(EINVAL);
}
// Filter size
if size_paged_aligned < length {
return error!(EINVAL);
}
let memory = self
.memory
.get_or_try_init(|| {
if size_paged_aligned == 0 {
return error!(EINVAL);
}
// Round up to page boundary
let vmo = zx::Vmo::create(size_paged_aligned as u64).map_err(|_| errno!(ENOMEM))?;
let memory = MemoryObject::from(vmo).with_zx_name(b"starnix:ashmem");
Ok(Arc::new(memory))
})?
.clone();
let mapped_addr = current_task.mm()?.map_memory(
addr,
memory,
memory_offset,
length,
prot_flags,
file.max_access_for_memory_mapping(),
mapping_options,
MappingName::Ashmem(state.name.clone().into()),
)?;
Ok(mapped_addr)
}
fn ioctl(
&self,
locked: &mut Locked<Unlocked>,
file: &FileObject,
current_task: &CurrentTask,
request: u32,
arg: SyscallArg,
) -> Result<SyscallResult, Errno> {
match request {
#[allow(unreachable_patterns)]
ASHMEM_SET_SIZE | starnix_uapi::arch32::ASHMEM_SET_SIZE => {
let mut state = self.state.lock();
if self.is_mapped() {
return error!(EINVAL);
}
state.size = arg.into();
Ok(SUCCESS)
}
ASHMEM_GET_SIZE => Ok(self.state.lock().size.into()),
ASHMEM_SET_NAME => {
let mut state = self.state.lock();
if self.is_mapped() {
return error!(EINVAL);
}
let mut name = current_task.read_c_string_to_vec(
UserCString::new(current_task, arg),
ASHMEM_NAME_LEN as usize,
)?;
name.push(0); // Add a null terminator
state.name = name.into();
Ok(SUCCESS)
}
ASHMEM_GET_NAME => {
let state = self.state.lock();
let name = &state.name[..];
current_task.write_memory(arg.into(), name)?;
Ok(SUCCESS)
}
#[allow(unreachable_patterns)]
ASHMEM_SET_PROT_MASK | starnix_uapi::arch32::ASHMEM_SET_PROT_MASK => {
let mut state = self.state.lock();
let prot_flags =
ProtectionFlags::from_access_bits(arg.into()).ok_or_else(|| errno!(EINVAL))?;
// Do not allow protections to be increased
if !state.prot_flags.contains(prot_flags) {
return error!(EINVAL);
}
state.prot_flags = prot_flags;
Ok(SUCCESS)
}
ASHMEM_GET_PROT_MASK => Ok(self.state.lock().prot_flags.bits().into()),
ASHMEM_PIN | ASHMEM_UNPIN | ASHMEM_GET_PIN_STATUS => {
let mut state = self.state.lock();
if !self.is_mapped() {
return error!(EINVAL);
}
let user_ref = UserRef::<ashmem_pin>::new(arg.into());
let pin = current_task.read_object(user_ref)?;
let (lo, hi) =
(pin.offset, pin.offset.checked_add(pin.len).ok_or_else(|| errno!(EFAULT))?);
// Bounds check
if (lo as usize) >= state.size || (hi as usize) > state.size {
return error!(EINVAL);
}
// Aligned to page size
if (lo as u64) % *PAGE_SIZE != 0 || (hi as u64) % *PAGE_SIZE != 0 {
return error!(EINVAL);
}
match request {
ASHMEM_PIN => {
for is_purged in state.unpinned.remove(lo..hi).iter() {
if *is_purged {
return Ok(ASHMEM_WAS_PURGED.into());
}
}
return Ok(ASHMEM_NOT_PURGED.into());
}
ASHMEM_UNPIN => {
// This method has must_use but we don't actually need to do any explicit
// cleanup.
let _ = state.unpinned.insert(lo..hi, false);
return Ok(ASHMEM_IS_UNPINNED.into());
}
ASHMEM_GET_PIN_STATUS => {
let mut intervals = state.unpinned.range(lo..hi);
return match intervals.next() {
Some(_) => Ok(ASHMEM_IS_UNPINNED.into()),
None => Ok(ASHMEM_IS_PINNED.into()),
};
}
_ => unreachable!(),
}
}
ASHMEM_PURGE_ALL_CACHES => {
let mut state = self.state.lock();
let memory = self.memory.get().ok_or_else(|| errno!(EINVAL))?;
if state.unpinned.is_empty() {
return Ok(ASHMEM_IS_PINNED.into());
}
let unpinned: Vec<_> = state.unpinned.iter().map(|(k, _)| k.clone()).collect();
for range in unpinned.into_iter() {
let (lo, hi) = (range.start as u64, range.end as u64);
memory.op_range(zx::VmoOp::ZERO, lo, hi - lo).unwrap_or(());
// This method has must_use but we don't actually need to do any explicit
// cleanup.
let _ = state.unpinned.insert(range, true);
}
return Ok(ASHMEM_IS_UNPINNED.into());
}
#[allow(unreachable_patterns)]
uapi::ASHMEM_GET_FILE_ID | uapi::arch32::ASHMEM_GET_FILE_ID => {
let state = self.state.lock();
current_task.write_object(arg.into(), &(state.id))?;
Ok(SUCCESS)
}
_ => default_ioctl(file, locked, current_task, request, arg),
}
}
}