blob: 3549c69d137bc93e62ad7c134fdec551179b1281 [file] [edit]
// Copyright 2025 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::{
UFFDIO_CONTINUE, UFFDIO_COPY, UFFDIO_WAKE, UFFDIO_WRITEPROTECT, UFFDIO_ZEROPAGE, uffdio_copy,
uffdio_zeropage,
};
use starnix_core::mm::{
FaultCopyMode, FaultRegisterMode, FaultZeroMode, MemoryAccessorExt, UserFault,
UserFaultFeatures,
};
use starnix_core::task::{CurrentTask, EventHandler, WaitCanceler, Waiter};
use starnix_core::vfs::{
Anon, FileHandle, FileObject, FileObjectState, FileOps, InputBuffer, OutputBuffer,
fileops_impl_nonseekable, fileops_impl_noop_sync,
};
use starnix_logging::track_stub;
use starnix_sync::{FileOpsCore, LockBefore, LockEqualOrBefore, Locked, Unlocked, UserFaultInner};
use starnix_uapi::errors::Errno;
use starnix_uapi::open_flags::OpenFlags;
use starnix_uapi::user_address::UserRef;
use starnix_uapi::vfs::FdEvents;
use starnix_uapi::{
_UFFDIO_API, _UFFDIO_REGISTER, _UFFDIO_UNREGISTER, UFFDIO, UFFDIO_API, UFFDIO_MOVE,
UFFDIO_POISON, UFFDIO_REGISTER, UFFDIO_UNREGISTER, errno, error, uapi, uffdio_api,
uffdio_range, uffdio_register,
};
use static_assertions::const_assert_eq;
use std::sync::Arc;
uapi::check_arch_independent_layout! {
uffdio_api {
api,
features,
ioctls,
}
uffdio_range {
start,
len,
}
uffdio_register {
range,
mode,
ioctls,
}
uffdio_copy {
dst,
src,
len,
mode,
copy,
}
uffdio_zeropage {
range,
mode,
zeropage,
}
uffdio_writeprotect {
range,
mode,
}
uffdio_continue {
range,
mode,
mapped,
}
uffdio_poison {
range,
mode,
updated,
}
uffdio_move {
dst,
src,
len,
mode,
move_,
}
}
pub struct UserFaultFile {
inner: Arc<UserFault>,
}
// API version hasn't changed
const_assert_eq!(UFFDIO, 0xAA);
impl UserFaultFile {
pub fn new<L>(
locked: &mut Locked<L>,
current_task: &CurrentTask,
open_flags: OpenFlags,
_user_mode_only: bool,
) -> Result<FileHandle, Errno>
where
L: LockEqualOrBefore<FileOpsCore>,
{
let mm = current_task.mm()?;
let inner = Arc::new(UserFault::new(Arc::downgrade(&mm)));
mm.register_uffd(&inner);
Anon::new_file(locked, current_task, Box::new(Self { inner }), open_flags, "[userfaultfd]")
}
fn api_handshake<L>(
&self,
locked: &mut Locked<L>,
_current_task: &CurrentTask,
request: uffdio_api,
) -> Result<uffdio_api, Errno>
where
L: LockBefore<UserFaultInner>,
{
if self.inner.is_initialized(locked) {
return error!(EPERM, "userfault object already initialized");
}
if request.api != UFFDIO as u64 {
return error!(EINVAL, format!("unsupported API version {}", request.api));
}
let requested_features =
UserFaultFeatures::from_bits(request.features.try_into().map_err(|_| errno!(EINVAL))?)
.ok_or_else(|| errno!(EINVAL))?;
let requested_unsupported = requested_features.difference(UserFaultFeatures::ALL_SUPPORTED);
if !requested_unsupported.is_empty() {
return error!(EINVAL);
}
// We can support the client, initialize the object.
self.inner.initialize(locked, requested_features);
Ok(uffdio_api {
api: request.api,
features: UserFaultFeatures::ALL_SUPPORTED.bits() as u64,
ioctls: (1 << _UFFDIO_API) | (1 << _UFFDIO_REGISTER) | (1 << _UFFDIO_UNREGISTER),
})
}
}
impl FileOps for UserFaultFile {
fileops_impl_nonseekable!();
fileops_impl_noop_sync!();
fn read(
&self,
_locked: &mut Locked<FileOpsCore>,
_file: &FileObject,
_current_task: &CurrentTask,
_offset: usize,
_data: &mut dyn OutputBuffer,
) -> Result<usize, Errno> {
track_stub!(TODO("https://fxbug.dev/391599171"), "event-based uffd operations");
error!(ENOTSUP)
}
fn write(
&self,
_locked: &mut Locked<FileOpsCore>,
_file: &FileObject,
_current_task: &CurrentTask,
_offset: usize,
_data: &mut dyn InputBuffer,
) -> Result<usize, Errno> {
error!(EINVAL)
}
fn query_events(
&self,
_locked: &mut Locked<FileOpsCore>,
_file: &FileObject,
_current_task: &CurrentTask,
) -> Result<FdEvents, Errno> {
track_stub!(TODO("https://fxbug.dev/391599171"), "event-based uffd operations");
error!(ENOTSUP)
}
fn wait_async(
&self,
_locked: &mut Locked<FileOpsCore>,
_file: &FileObject,
_current_task: &CurrentTask,
_waiter: &Waiter,
_events: FdEvents,
_handler: EventHandler,
) -> Option<WaitCanceler> {
track_stub!(TODO("https://fxbug.dev/391599171"), "event-based uffd operations");
None
}
fn ioctl(
&self,
locked: &mut Locked<Unlocked>,
_file: &FileObject,
current_task: &CurrentTask,
request: u32,
arg: starnix_syscalls::SyscallArg,
) -> Result<starnix_syscalls::SyscallResult, Errno> {
match request {
UFFDIO_API => {
let arg: UserRef<uffdio_api> = arg.into();
let request = current_task.read_object(arg)?;
match self.api_handshake(locked, current_task, request) {
Ok(reply) => {
current_task.write_object(arg, &reply)?;
Ok(0.into())
}
Err(e) => {
current_task.write_object(arg, &uffdio_api::default())?;
Err(e)
}
}
}
UFFDIO_REGISTER => {
let arg: UserRef<uffdio_register> = arg.into();
let mut request = current_task.read_object(arg)?;
request.ioctls = self
.inner
.op_register(
locked,
request.range.start.into(),
request.range.len,
FaultRegisterMode::from_bits_truncate(
request.mode.try_into().map_err(|_| errno!(EINVAL))?,
),
)?
.bits();
current_task.write_object(arg, &request)?;
Ok(0.into())
}
UFFDIO_UNREGISTER => {
let arg: UserRef<uffdio_range> = arg.into();
let request = current_task.read_object(arg)?;
self.inner.op_unregister(locked, request.start.into(), request.len)?;
Ok(0.into())
}
UFFDIO_ZEROPAGE => {
let arg: UserRef<uffdio_zeropage> = arg.into();
let mut request = current_task.read_object(arg)?;
let ioctl_res = self.inner.op_zero(
locked,
request.range.start.into(),
request.range.len,
FaultZeroMode::from_bits_truncate(
request.mode.try_into().map_err(|_| errno!(EINVAL))?,
),
);
request.zeropage = match ioctl_res {
Ok(bytes) => bytes as i64,
Err(ref e) => -1 * (e.code.error_code() as i64),
};
current_task.write_object(arg, &request)?;
// EAGAIN is returned if the number of bytes zeroed is not equal to the requested
// length
match ioctl_res {
Ok(bytes) if bytes == request.range.len as usize => Ok(0.into()),
Err(e) => Err(e),
_ => error!(EAGAIN),
}
}
UFFDIO_COPY => {
let arg: UserRef<uffdio_copy> = arg.into();
let mut request = current_task.read_object(arg)?;
let mm = current_task.mm()?;
let ioctl_res = self.inner.op_copy(
locked,
&mm,
request.src.into(),
request.dst.into(),
request.len,
FaultCopyMode::from_bits_truncate(
request.mode.try_into().map_err(|_| errno!(EINVAL))?,
),
);
request.copy = match ioctl_res {
Ok(bytes) => bytes as i64,
Err(ref e) => -1 * (e.code.error_code() as i64),
};
current_task.write_object(arg, &request)?;
// EAGAIN is returned if the number of bytes copied is not equal to the requested
// length
match ioctl_res {
Ok(bytes) if bytes == request.len as usize => Ok(0.into()),
Err(e) => Err(e),
_ => error!(EAGAIN),
}
}
UFFDIO_MOVE => {
track_stub!(TODO("https://fxbug.dev/297375964"), "basic uffd ioctls", request);
error!(ENOSYS)
}
UFFDIO_WAKE | UFFDIO_WRITEPROTECT | UFFDIO_CONTINUE | UFFDIO_POISON => {
track_stub!(
TODO("https://fxbug.dev/322893681"),
"full set of uffd ioctls",
request
);
error!(ENOSYS)
}
unknown => error!(EINVAL, format!("unknown ioctl request {unknown}")),
}
}
// On closing, clear all the registrations pointing to this userfault object.
fn close(
self: Box<Self>,
locked: &mut Locked<FileOpsCore>,
_file: &FileObjectState,
_current_task: &CurrentTask,
) {
self.inner.cleanup(locked);
}
}