1 // SPDX-License-Identifier: GPL-2.0 2 3 // Copyright (C) 2024 Google LLC. 4 5 //! Miscdevice support. 6 //! 7 //! C headers: [`include/linux/miscdevice.h`](srctree/include/linux/miscdevice.h). 8 //! 9 //! Reference: <https://www.kernel.org/doc/html/latest/driver-api/misc_devices.html> 10 11 use crate::{ 12 bindings, 13 error::{to_result, Error, Result, VTABLE_DEFAULT_ERROR}, 14 prelude::*, 15 str::CStr, 16 types::{ForeignOwnable, Opaque}, 17 }; 18 use core::{ 19 ffi::{c_int, c_long, c_uint, c_ulong}, 20 marker::PhantomData, 21 mem::MaybeUninit, 22 pin::Pin, 23 }; 24 25 /// Options for creating a misc device. 26 #[derive(Copy, Clone)] 27 pub struct MiscDeviceOptions { 28 /// The name of the miscdevice. 29 pub name: &'static CStr, 30 } 31 32 impl MiscDeviceOptions { 33 /// Create a raw `struct miscdev` ready for registration. 34 pub const fn into_raw<T: MiscDevice>(self) -> bindings::miscdevice { 35 // SAFETY: All zeros is valid for this C type. 36 let mut result: bindings::miscdevice = unsafe { MaybeUninit::zeroed().assume_init() }; 37 result.minor = bindings::MISC_DYNAMIC_MINOR as _; 38 result.name = self.name.as_char_ptr(); 39 result.fops = create_vtable::<T>(); 40 result 41 } 42 } 43 44 /// A registration of a miscdevice. 45 /// 46 /// # Invariants 47 /// 48 /// `inner` is a registered misc device. 49 #[repr(transparent)] 50 #[pin_data(PinnedDrop)] 51 pub struct MiscDeviceRegistration<T> { 52 #[pin] 53 inner: Opaque<bindings::miscdevice>, 54 _t: PhantomData<T>, 55 } 56 57 // SAFETY: It is allowed to call `misc_deregister` on a different thread from where you called 58 // `misc_register`. 59 unsafe impl<T> Send for MiscDeviceRegistration<T> {} 60 // SAFETY: All `&self` methods on this type are written to ensure that it is safe to call them in 61 // parallel. 62 unsafe impl<T> Sync for MiscDeviceRegistration<T> {} 63 64 impl<T: MiscDevice> MiscDeviceRegistration<T> { 65 /// Register a misc device. 66 pub fn register(opts: MiscDeviceOptions) -> impl PinInit<Self, Error> { 67 try_pin_init!(Self { 68 inner <- Opaque::try_ffi_init(move |slot: *mut bindings::miscdevice| { 69 // SAFETY: The initializer can write to the provided `slot`. 70 unsafe { slot.write(opts.into_raw::<T>()) }; 71 72 // SAFETY: We just wrote the misc device options to the slot. The miscdevice will 73 // get unregistered before `slot` is deallocated because the memory is pinned and 74 // the destructor of this type deallocates the memory. 75 // INVARIANT: If this returns `Ok(())`, then the `slot` will contain a registered 76 // misc device. 77 to_result(unsafe { bindings::misc_register(slot) }) 78 }), 79 _t: PhantomData, 80 }) 81 } 82 83 /// Returns a raw pointer to the misc device. 84 pub fn as_raw(&self) -> *mut bindings::miscdevice { 85 self.inner.get() 86 } 87 } 88 89 #[pinned_drop] 90 impl<T> PinnedDrop for MiscDeviceRegistration<T> { 91 fn drop(self: Pin<&mut Self>) { 92 // SAFETY: We know that the device is registered by the type invariants. 93 unsafe { bindings::misc_deregister(self.inner.get()) }; 94 } 95 } 96 97 /// Trait implemented by the private data of an open misc device. 98 #[vtable] 99 pub trait MiscDevice { 100 /// What kind of pointer should `Self` be wrapped in. 101 type Ptr: ForeignOwnable + Send + Sync; 102 103 /// Called when the misc device is opened. 104 /// 105 /// The returned pointer will be stored as the private data for the file. 106 fn open() -> Result<Self::Ptr>; 107 108 /// Called when the misc device is released. 109 fn release(device: Self::Ptr) { 110 drop(device); 111 } 112 113 /// Handler for ioctls. 114 /// 115 /// The `cmd` argument is usually manipulated using the utilties in [`kernel::ioctl`]. 116 /// 117 /// [`kernel::ioctl`]: mod@crate::ioctl 118 fn ioctl( 119 _device: <Self::Ptr as ForeignOwnable>::Borrowed<'_>, 120 _cmd: u32, 121 _arg: usize, 122 ) -> Result<isize> { 123 kernel::build_error(VTABLE_DEFAULT_ERROR) 124 } 125 126 /// Handler for ioctls. 127 /// 128 /// Used for 32-bit userspace on 64-bit platforms. 129 /// 130 /// This method is optional and only needs to be provided if the ioctl relies on structures 131 /// that have different layout on 32-bit and 64-bit userspace. If no implementation is 132 /// provided, then `compat_ptr_ioctl` will be used instead. 133 #[cfg(CONFIG_COMPAT)] 134 fn compat_ioctl( 135 _device: <Self::Ptr as ForeignOwnable>::Borrowed<'_>, 136 _cmd: u32, 137 _arg: usize, 138 ) -> Result<isize> { 139 kernel::build_error(VTABLE_DEFAULT_ERROR) 140 } 141 } 142 143 const fn create_vtable<T: MiscDevice>() -> &'static bindings::file_operations { 144 const fn maybe_fn<T: Copy>(check: bool, func: T) -> Option<T> { 145 if check { 146 Some(func) 147 } else { 148 None 149 } 150 } 151 152 struct VtableHelper<T: MiscDevice> { 153 _t: PhantomData<T>, 154 } 155 impl<T: MiscDevice> VtableHelper<T> { 156 const VTABLE: bindings::file_operations = bindings::file_operations { 157 open: Some(fops_open::<T>), 158 release: Some(fops_release::<T>), 159 unlocked_ioctl: maybe_fn(T::HAS_IOCTL, fops_ioctl::<T>), 160 #[cfg(CONFIG_COMPAT)] 161 compat_ioctl: if T::HAS_COMPAT_IOCTL { 162 Some(fops_compat_ioctl::<T>) 163 } else if T::HAS_IOCTL { 164 Some(bindings::compat_ptr_ioctl) 165 } else { 166 None 167 }, 168 ..unsafe { MaybeUninit::zeroed().assume_init() } 169 }; 170 } 171 172 &VtableHelper::<T>::VTABLE 173 } 174 175 unsafe extern "C" fn fops_open<T: MiscDevice>( 176 inode: *mut bindings::inode, 177 file: *mut bindings::file, 178 ) -> c_int { 179 // SAFETY: The pointers are valid and for a file being opened. 180 let ret = unsafe { bindings::generic_file_open(inode, file) }; 181 if ret != 0 { 182 return ret; 183 } 184 185 let ptr = match T::open() { 186 Ok(ptr) => ptr, 187 Err(err) => return err.to_errno(), 188 }; 189 190 // SAFETY: The open call of a file owns the private data. 191 unsafe { (*file).private_data = ptr.into_foreign().cast_mut() }; 192 193 0 194 } 195 196 unsafe extern "C" fn fops_release<T: MiscDevice>( 197 _inode: *mut bindings::inode, 198 file: *mut bindings::file, 199 ) -> c_int { 200 // SAFETY: The release call of a file owns the private data. 201 let private = unsafe { (*file).private_data }; 202 // SAFETY: The release call of a file owns the private data. 203 let ptr = unsafe { <T::Ptr as ForeignOwnable>::from_foreign(private) }; 204 205 T::release(ptr); 206 207 0 208 } 209 210 unsafe extern "C" fn fops_ioctl<T: MiscDevice>( 211 file: *mut bindings::file, 212 cmd: c_uint, 213 arg: c_ulong, 214 ) -> c_long { 215 // SAFETY: The ioctl call of a file can access the private data. 216 let private = unsafe { (*file).private_data }; 217 // SAFETY: Ioctl calls can borrow the private data of the file. 218 let device = unsafe { <T::Ptr as ForeignOwnable>::borrow(private) }; 219 220 match T::ioctl(device, cmd as u32, arg as usize) { 221 Ok(ret) => ret as c_long, 222 Err(err) => err.to_errno() as c_long, 223 } 224 } 225 226 #[cfg(CONFIG_COMPAT)] 227 unsafe extern "C" fn fops_compat_ioctl<T: MiscDevice>( 228 file: *mut bindings::file, 229 cmd: c_uint, 230 arg: c_ulong, 231 ) -> c_long { 232 // SAFETY: The compat ioctl call of a file can access the private data. 233 let private = unsafe { (*file).private_data }; 234 // SAFETY: Ioctl calls can borrow the private data of the file. 235 let device = unsafe { <T::Ptr as ForeignOwnable>::borrow(private) }; 236 237 match T::compat_ioctl(device, cmd as u32, arg as usize) { 238 Ok(ret) => ret as c_long, 239 Err(err) => err.to_errno() as c_long, 240 } 241 } 242