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