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