From f893691e742688ae21ad597c5bba13bef54706cd Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Tue, 1 Oct 2024 08:22:22 +0000 Subject: rust: miscdevice: add base miscdevice abstraction Provide a `MiscDevice` trait that lets you specify the file operations that you wish to provide for your misc device. For now, only three file operations are provided: open, close, ioctl. These abstractions only support MISC_DYNAMIC_MINOR. This enforces that new miscdevices should not hard-code a minor number. When implementing ioctl, the Result type is used. This means that you can choose to return either of: * An integer of type isize. * An errno using the kernel::error::Error type. When returning an isize, the integer is returned verbatim. It's mainly intended for returning positive integers to userspace. However, it is technically possible to return errors via the isize return value too. To avoid having a dependency on files, this patch does not provide the file operations callbacks a pointer to the file. This means that they cannot check file properties such as O_NONBLOCK (which Binder needs). Support for that can be added as a follow-up. To avoid having a dependency on vma, this patch does not provide any way to implement mmap (which Binder needs). Support for that can be added as a follow-up. Rust Binder will use these abstractions to create the /dev/binder file when binderfs is disabled. Signed-off-by: Alice Ryhl Link: https://lore.kernel.org/rust-for-linux/20240328195457.225001-1-wedsonaf@gmail.com/ Link: https://lore.kernel.org/r/20241001-b4-miscdevice-v2-2-330d760041fa@google.com Signed-off-by: Greg Kroah-Hartman --- rust/kernel/miscdevice.rs | 241 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 241 insertions(+) create mode 100644 rust/kernel/miscdevice.rs (limited to 'rust/kernel/miscdevice.rs') diff --git a/rust/kernel/miscdevice.rs b/rust/kernel/miscdevice.rs new file mode 100644 index 000000000000..cbd5249b5b45 --- /dev/null +++ b/rust/kernel/miscdevice.rs @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2024 Google LLC. + +//! Miscdevice support. +//! +//! C headers: [`include/linux/miscdevice.h`](srctree/include/linux/miscdevice.h). +//! +//! Reference: + +use crate::{ + bindings, + error::{to_result, Error, Result, VTABLE_DEFAULT_ERROR}, + prelude::*, + str::CStr, + types::{ForeignOwnable, Opaque}, +}; +use core::{ + ffi::{c_int, c_long, c_uint, c_ulong}, + marker::PhantomData, + mem::MaybeUninit, + pin::Pin, +}; + +/// Options for creating a misc device. +#[derive(Copy, Clone)] +pub struct MiscDeviceOptions { + /// The name of the miscdevice. + pub name: &'static CStr, +} + +impl MiscDeviceOptions { + /// Create a raw `struct miscdev` ready for registration. + pub const fn into_raw(self) -> bindings::miscdevice { + // SAFETY: All zeros is valid for this C type. + let mut result: bindings::miscdevice = unsafe { MaybeUninit::zeroed().assume_init() }; + result.minor = bindings::MISC_DYNAMIC_MINOR as _; + result.name = self.name.as_char_ptr(); + result.fops = create_vtable::(); + result + } +} + +/// A registration of a miscdevice. +/// +/// # Invariants +/// +/// `inner` is a registered misc device. +#[repr(transparent)] +#[pin_data(PinnedDrop)] +pub struct MiscDeviceRegistration { + #[pin] + inner: Opaque, + _t: PhantomData, +} + +// SAFETY: It is allowed to call `misc_deregister` on a different thread from where you called +// `misc_register`. +unsafe impl Send for MiscDeviceRegistration {} +// SAFETY: All `&self` methods on this type are written to ensure that it is safe to call them in +// parallel. +unsafe impl Sync for MiscDeviceRegistration {} + +impl MiscDeviceRegistration { + /// Register a misc device. + pub fn register(opts: MiscDeviceOptions) -> impl PinInit { + try_pin_init!(Self { + inner <- Opaque::try_ffi_init(move |slot: *mut bindings::miscdevice| { + // SAFETY: The initializer can write to the provided `slot`. + unsafe { slot.write(opts.into_raw::()) }; + + // SAFETY: We just wrote the misc device options to the slot. The miscdevice will + // get unregistered before `slot` is deallocated because the memory is pinned and + // the destructor of this type deallocates the memory. + // INVARIANT: If this returns `Ok(())`, then the `slot` will contain a registered + // misc device. + to_result(unsafe { bindings::misc_register(slot) }) + }), + _t: PhantomData, + }) + } + + /// Returns a raw pointer to the misc device. + pub fn as_raw(&self) -> *mut bindings::miscdevice { + self.inner.get() + } +} + +#[pinned_drop] +impl PinnedDrop for MiscDeviceRegistration { + fn drop(self: Pin<&mut Self>) { + // SAFETY: We know that the device is registered by the type invariants. + unsafe { bindings::misc_deregister(self.inner.get()) }; + } +} + +/// Trait implemented by the private data of an open misc device. +#[vtable] +pub trait MiscDevice { + /// What kind of pointer should `Self` be wrapped in. + type Ptr: ForeignOwnable + Send + Sync; + + /// Called when the misc device is opened. + /// + /// The returned pointer will be stored as the private data for the file. + fn open() -> Result; + + /// Called when the misc device is released. + fn release(device: Self::Ptr) { + drop(device); + } + + /// Handler for ioctls. + /// + /// The `cmd` argument is usually manipulated using the utilties in [`kernel::ioctl`]. + /// + /// [`kernel::ioctl`]: mod@crate::ioctl + fn ioctl( + _device: ::Borrowed<'_>, + _cmd: u32, + _arg: usize, + ) -> Result { + kernel::build_error(VTABLE_DEFAULT_ERROR) + } + + /// Handler for ioctls. + /// + /// Used for 32-bit userspace on 64-bit platforms. + /// + /// This method is optional and only needs to be provided if the ioctl relies on structures + /// that have different layout on 32-bit and 64-bit userspace. If no implementation is + /// provided, then `compat_ptr_ioctl` will be used instead. + #[cfg(CONFIG_COMPAT)] + fn compat_ioctl( + _device: ::Borrowed<'_>, + _cmd: u32, + _arg: usize, + ) -> Result { + kernel::build_error(VTABLE_DEFAULT_ERROR) + } +} + +const fn create_vtable() -> &'static bindings::file_operations { + const fn maybe_fn(check: bool, func: T) -> Option { + if check { + Some(func) + } else { + None + } + } + + struct VtableHelper { + _t: PhantomData, + } + impl VtableHelper { + const VTABLE: bindings::file_operations = bindings::file_operations { + open: Some(fops_open::), + release: Some(fops_release::), + unlocked_ioctl: maybe_fn(T::HAS_IOCTL, fops_ioctl::), + #[cfg(CONFIG_COMPAT)] + compat_ioctl: if T::HAS_COMPAT_IOCTL { + Some(fops_compat_ioctl::) + } else if T::HAS_IOCTL { + Some(bindings::compat_ptr_ioctl) + } else { + None + }, + ..unsafe { MaybeUninit::zeroed().assume_init() } + }; + } + + &VtableHelper::::VTABLE +} + +unsafe extern "C" fn fops_open( + inode: *mut bindings::inode, + file: *mut bindings::file, +) -> c_int { + // SAFETY: The pointers are valid and for a file being opened. + let ret = unsafe { bindings::generic_file_open(inode, file) }; + if ret != 0 { + return ret; + } + + let ptr = match T::open() { + Ok(ptr) => ptr, + Err(err) => return err.to_errno(), + }; + + // SAFETY: The open call of a file owns the private data. + unsafe { (*file).private_data = ptr.into_foreign().cast_mut() }; + + 0 +} + +unsafe extern "C" fn fops_release( + _inode: *mut bindings::inode, + file: *mut bindings::file, +) -> c_int { + // SAFETY: The release call of a file owns the private data. + let private = unsafe { (*file).private_data }; + // SAFETY: The release call of a file owns the private data. + let ptr = unsafe { ::from_foreign(private) }; + + T::release(ptr); + + 0 +} + +unsafe extern "C" fn fops_ioctl( + file: *mut bindings::file, + cmd: c_uint, + arg: c_ulong, +) -> c_long { + // SAFETY: The ioctl call of a file can access the private data. + let private = unsafe { (*file).private_data }; + // SAFETY: Ioctl calls can borrow the private data of the file. + let device = unsafe { ::borrow(private) }; + + match T::ioctl(device, cmd as u32, arg as usize) { + Ok(ret) => ret as c_long, + Err(err) => err.to_errno() as c_long, + } +} + +#[cfg(CONFIG_COMPAT)] +unsafe extern "C" fn fops_compat_ioctl( + file: *mut bindings::file, + cmd: c_uint, + arg: c_ulong, +) -> c_long { + // SAFETY: The compat ioctl call of a file can access the private data. + let private = unsafe { (*file).private_data }; + // SAFETY: Ioctl calls can borrow the private data of the file. + let device = unsafe { ::borrow(private) }; + + match T::compat_ioctl(device, cmd as u32, arg as usize) { + Ok(ret) => ret as c_long, + Err(err) => err.to_errno() as c_long, + } +} -- cgit v1.2.3 From ccb22ca2805226bbcacdff0fc4b047d947f7948e Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Tue, 15 Oct 2024 14:13:22 +0000 Subject: rust: miscdevice: fix warning on c_uint to u32 cast When building miscdevice with clippy warnings, the following warning is emitted: warning: casting to the same type is unnecessary (`u32` -> `u32`) --> /home/aliceryhl/rust-for-linux/rust/kernel/miscdevice.rs:220:28 | 220 | match T::ioctl(device, cmd as u32, arg as usize) { | ^^^^^^^^^^ help: try: `cmd` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast = note: `-W clippy::unnecessary-cast` implied by `-W clippy::all` = help: to override `-W clippy::all` add `#[allow(clippy::unnecessary_cast)]` Thus, fix it. Signed-off-by: Alice Ryhl Link: https://lore.kernel.org/r/20241015-miscdevice-cint-cast-v1-1-fcf4b75700ac@google.com Signed-off-by: Greg Kroah-Hartman --- rust/kernel/miscdevice.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'rust/kernel/miscdevice.rs') diff --git a/rust/kernel/miscdevice.rs b/rust/kernel/miscdevice.rs index cbd5249b5b45..50885fb511bf 100644 --- a/rust/kernel/miscdevice.rs +++ b/rust/kernel/miscdevice.rs @@ -217,7 +217,7 @@ unsafe extern "C" fn fops_ioctl( // SAFETY: Ioctl calls can borrow the private data of the file. let device = unsafe { ::borrow(private) }; - match T::ioctl(device, cmd as u32, arg as usize) { + match T::ioctl(device, cmd, arg as usize) { Ok(ret) => ret as c_long, Err(err) => err.to_errno() as c_long, } @@ -234,7 +234,7 @@ unsafe extern "C" fn fops_compat_ioctl( // SAFETY: Ioctl calls can borrow the private data of the file. let device = unsafe { ::borrow(private) }; - match T::compat_ioctl(device, cmd as u32, arg as usize) { + match T::compat_ioctl(device, cmd, arg as usize) { Ok(ret) => ret as c_long, Err(err) => err.to_errno() as c_long, } -- cgit v1.2.3 From bd5ee6bcc51b617d28e54069fef818751763962b Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Tue, 22 Oct 2024 13:14:13 +0000 Subject: rust: miscdevice: add missing safety comments This fixes the following four warnings: warning: unsafe block missing a safety comment --> /home/aliceryhl/rust-for-linux/rust/kernel/miscdevice.rs:168:15 | 168 | ..unsafe { MaybeUninit::zeroed().assume_init() } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: consider adding a safety comment on the preceding line = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#undocumented_unsafe_blocks = note: requested on the command line with `-W clippy::undocumented-unsafe-blocks` warning: unsafe function's docs are missing a `# Safety` section --> /home/aliceryhl/rust-for-linux/rust/kernel/miscdevice.rs:175:1 | 175 | / unsafe extern "C" fn fops_open( 176 | | inode: *mut bindings::inode, 177 | | file: *mut bindings::file, 178 | | ) -> c_int { | |__________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#missing_safety_doc = note: `-W clippy::missing-safety-doc` implied by `-W clippy::all` = help: to override `-W clippy::all` add `#[allow(clippy::missing_safety_doc)]` warning: unsafe function's docs are missing a `# Safety` section --> /home/aliceryhl/rust-for-linux/rust/kernel/miscdevice.rs:196:1 | 196 | / unsafe extern "C" fn fops_release( 197 | | _inode: *mut bindings::inode, 198 | | file: *mut bindings::file, 199 | | ) -> c_int { | |__________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#missing_safety_doc warning: unsafe function's docs are missing a `# Safety` section --> /home/aliceryhl/rust-for-linux/rust/kernel/miscdevice.rs:210:1 | 210 | / unsafe extern "C" fn fops_ioctl( 211 | | file: *mut bindings::file, 212 | | cmd: c_uint, 213 | | arg: c_ulong, 214 | | ) -> c_long { | |___________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#missing_safety_doc Note that these warnings are currently not enabled in the build, but rust-next contains a commit that will enable them, so we should fix them. Reported-by: Miguel Ojeda Signed-off-by: Alice Ryhl Closes: https://lore.kernel.org/rust-for-linux/CANiq72kOs6vPDUzZttQNqePFHphCQ30iVmZ5MO7eCJfPG==Vzg@mail.gmail.com/ Acked-by: Miguel Ojeda Link: https://lore.kernel.org/r/20241022-miscdevice-unsafe-warn-fix-v1-1-a78fde1740d6@google.com Signed-off-by: Greg Kroah-Hartman --- rust/kernel/miscdevice.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'rust/kernel/miscdevice.rs') diff --git a/rust/kernel/miscdevice.rs b/rust/kernel/miscdevice.rs index 50885fb511bf..7e2a79b3ae26 100644 --- a/rust/kernel/miscdevice.rs +++ b/rust/kernel/miscdevice.rs @@ -165,6 +165,7 @@ const fn create_vtable() -> &'static bindings::file_operations { } else { None }, + // SAFETY: All zeros is a valid value for `bindings::file_operations`. ..unsafe { MaybeUninit::zeroed().assume_init() } }; } @@ -172,6 +173,10 @@ const fn create_vtable() -> &'static bindings::file_operations { &VtableHelper::::VTABLE } +/// # Safety +/// +/// `file` and `inode` must be the file and inode for a file that is undergoing initialization. +/// The file must be associated with a `MiscDeviceRegistration`. unsafe extern "C" fn fops_open( inode: *mut bindings::inode, file: *mut bindings::file, @@ -193,6 +198,10 @@ unsafe extern "C" fn fops_open( 0 } +/// # Safety +/// +/// `file` and `inode` must be the file and inode for a file that is being released. The file must +/// be associated with a `MiscDeviceRegistration`. unsafe extern "C" fn fops_release( _inode: *mut bindings::inode, file: *mut bindings::file, @@ -207,6 +216,9 @@ unsafe extern "C" fn fops_release( 0 } +/// # Safety +/// +/// `file` must be a valid file that is associated with a `MiscDeviceRegistration`. unsafe extern "C" fn fops_ioctl( file: *mut bindings::file, cmd: c_uint, @@ -223,6 +235,9 @@ unsafe extern "C" fn fops_ioctl( } } +/// # Safety +/// +/// `file` must be a valid file that is associated with a `MiscDeviceRegistration`. #[cfg(CONFIG_COMPAT)] unsafe extern "C" fn fops_compat_ioctl( file: *mut bindings::file, -- cgit v1.2.3