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 // SAFETY: All zeros is a valid value for `bindings::file_operations`. 169 ..unsafe { MaybeUninit::zeroed().assume_init() } 170 }; 171 } 172 173 &VtableHelper::<T>::VTABLE 174 } 175 176 /// # Safety 177 /// 178 /// `file` and `inode` must be the file and inode for a file that is undergoing initialization. 179 /// The file must be associated with a `MiscDeviceRegistration<T>`. 180 unsafe extern "C" fn fops_open<T: MiscDevice>( 181 inode: *mut bindings::inode, 182 file: *mut bindings::file, 183 ) -> c_int { 184 // SAFETY: The pointers are valid and for a file being opened. 185 let ret = unsafe { bindings::generic_file_open(inode, file) }; 186 if ret != 0 { 187 return ret; 188 } 189 190 let ptr = match T::open() { 191 Ok(ptr) => ptr, 192 Err(err) => return err.to_errno(), 193 }; 194 195 // SAFETY: The open call of a file owns the private data. 196 unsafe { (*file).private_data = ptr.into_foreign().cast_mut() }; 197 198 0 199 } 200 201 /// # Safety 202 /// 203 /// `file` and `inode` must be the file and inode for a file that is being released. The file must 204 /// be associated with a `MiscDeviceRegistration<T>`. 205 unsafe extern "C" fn fops_release<T: MiscDevice>( 206 _inode: *mut bindings::inode, 207 file: *mut bindings::file, 208 ) -> c_int { 209 // SAFETY: The release call of a file owns the private data. 210 let private = unsafe { (*file).private_data }; 211 // SAFETY: The release call of a file owns the private data. 212 let ptr = unsafe { <T::Ptr as ForeignOwnable>::from_foreign(private) }; 213 214 T::release(ptr); 215 216 0 217 } 218 219 /// # Safety 220 /// 221 /// `file` must be a valid file that is associated with a `MiscDeviceRegistration<T>`. 222 unsafe extern "C" fn fops_ioctl<T: MiscDevice>( 223 file: *mut bindings::file, 224 cmd: c_uint, 225 arg: c_ulong, 226 ) -> c_long { 227 // SAFETY: The ioctl call of a file can access the private data. 228 let private = unsafe { (*file).private_data }; 229 // SAFETY: Ioctl calls can borrow the private data of the file. 230 let device = unsafe { <T::Ptr as ForeignOwnable>::borrow(private) }; 231 232 match T::ioctl(device, cmd, arg as usize) { 233 Ok(ret) => ret as c_long, 234 Err(err) => err.to_errno() as c_long, 235 } 236 } 237 238 /// # Safety 239 /// 240 /// `file` must be a valid file that is associated with a `MiscDeviceRegistration<T>`. 241 #[cfg(CONFIG_COMPAT)] 242 unsafe extern "C" fn fops_compat_ioctl<T: MiscDevice>( 243 file: *mut bindings::file, 244 cmd: c_uint, 245 arg: c_ulong, 246 ) -> c_long { 247 // SAFETY: The compat ioctl call of a file can access the private data. 248 let private = unsafe { (*file).private_data }; 249 // SAFETY: Ioctl calls can borrow the private data of the file. 250 let device = unsafe { <T::Ptr as ForeignOwnable>::borrow(private) }; 251 252 match T::compat_ioctl(device, cmd, arg as usize) { 253 Ok(ret) => ret as c_long, 254 Err(err) => err.to_errno() as c_long, 255 } 256 } 257