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: Sized { 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, _misc: &MiscDeviceRegistration<Self>) -> 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 raw_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, raw_file) }; 189 if ret != 0 { 190 return ret; 191 } 192 193 // SAFETY: The open call of a file can access the private data. 194 let misc_ptr = unsafe { (*raw_file).private_data }; 195 196 // SAFETY: This is a miscdevice, so `misc_open()` set the private data to a pointer to the 197 // associated `struct miscdevice` before calling into this method. Furthermore, `misc_open()` 198 // ensures that the miscdevice can't be unregistered and freed during this call to `fops_open`. 199 let misc = unsafe { &*misc_ptr.cast::<MiscDeviceRegistration<T>>() }; 200 201 // SAFETY: 202 // * This underlying file is valid for (much longer than) the duration of `T::open`. 203 // * There is no active fdget_pos region on the file on this thread. 204 let file = unsafe { File::from_raw_file(raw_file) }; 205 206 let ptr = match T::open(file, misc) { 207 Ok(ptr) => ptr, 208 Err(err) => return err.to_errno(), 209 }; 210 211 // This overwrites the private data with the value specified by the user, changing the type of 212 // this file's private data. All future accesses to the private data is performed by other 213 // fops_* methods in this file, which all correctly cast the private data to the new type. 214 // 215 // SAFETY: The open call of a file can access the private data. 216 unsafe { (*raw_file).private_data = ptr.into_foreign().cast_mut() }; 217 218 0 219 } 220 221 /// # Safety 222 /// 223 /// `file` and `inode` must be the file and inode for a file that is being released. The file must 224 /// be associated with a `MiscDeviceRegistration<T>`. 225 unsafe extern "C" fn fops_release<T: MiscDevice>( 226 _inode: *mut bindings::inode, 227 file: *mut bindings::file, 228 ) -> c_int { 229 // SAFETY: The release call of a file owns the private data. 230 let private = unsafe { (*file).private_data }; 231 // SAFETY: The release call of a file owns the private data. 232 let ptr = unsafe { <T::Ptr as ForeignOwnable>::from_foreign(private) }; 233 234 // SAFETY: 235 // * The file is valid for the duration of this call. 236 // * There is no active fdget_pos region on the file on this thread. 237 T::release(ptr, unsafe { File::from_raw_file(file) }); 238 239 0 240 } 241 242 /// # Safety 243 /// 244 /// `file` must be a valid file that is associated with a `MiscDeviceRegistration<T>`. 245 unsafe extern "C" fn fops_ioctl<T: MiscDevice>( 246 file: *mut bindings::file, 247 cmd: c_uint, 248 arg: c_ulong, 249 ) -> c_long { 250 // SAFETY: The ioctl call of a file can access the private data. 251 let private = unsafe { (*file).private_data }; 252 // SAFETY: Ioctl calls can borrow the private data of the file. 253 let device = unsafe { <T::Ptr as ForeignOwnable>::borrow(private) }; 254 255 // SAFETY: 256 // * The file is valid for the duration of this call. 257 // * There is no active fdget_pos region on the file on this thread. 258 let file = unsafe { File::from_raw_file(file) }; 259 260 match T::ioctl(device, file, cmd, arg as usize) { 261 Ok(ret) => ret as c_long, 262 Err(err) => err.to_errno() as c_long, 263 } 264 } 265 266 /// # Safety 267 /// 268 /// `file` must be a valid file that is associated with a `MiscDeviceRegistration<T>`. 269 #[cfg(CONFIG_COMPAT)] 270 unsafe extern "C" fn fops_compat_ioctl<T: MiscDevice>( 271 file: *mut bindings::file, 272 cmd: c_uint, 273 arg: c_ulong, 274 ) -> c_long { 275 // SAFETY: The compat ioctl call of a file can access the private data. 276 let private = unsafe { (*file).private_data }; 277 // SAFETY: Ioctl calls can borrow the private data of the file. 278 let device = unsafe { <T::Ptr as ForeignOwnable>::borrow(private) }; 279 280 // SAFETY: 281 // * The file is valid for the duration of this call. 282 // * There is no active fdget_pos region on the file on this thread. 283 let file = unsafe { File::from_raw_file(file) }; 284 285 match T::compat_ioctl(device, file, cmd, arg as usize) { 286 Ok(ret) => ret as c_long, 287 Err(err) => err.to_errno() as c_long, 288 } 289 } 290