Skip to content

Commit f893691

Browse files
Darksonngregkh
authored andcommitted
rust: miscdevice: add base miscdevice abstraction
Provide a `MiscDevice` trait that lets you specify the file operations that you wish to provide for your misc device. For now, only three file operations are provided: open, close, ioctl. These abstractions only support MISC_DYNAMIC_MINOR. This enforces that new miscdevices should not hard-code a minor number. When implementing ioctl, the Result type is used. This means that you can choose to return either of: * An integer of type isize. * An errno using the kernel::error::Error type. When returning an isize, the integer is returned verbatim. It's mainly intended for returning positive integers to userspace. However, it is technically possible to return errors via the isize return value too. To avoid having a dependency on files, this patch does not provide the file operations callbacks a pointer to the file. This means that they cannot check file properties such as O_NONBLOCK (which Binder needs). Support for that can be added as a follow-up. To avoid having a dependency on vma, this patch does not provide any way to implement mmap (which Binder needs). Support for that can be added as a follow-up. Rust Binder will use these abstractions to create the /dev/binder file when binderfs is disabled. Signed-off-by: Alice Ryhl <aliceryhl@google.com> Link: https://lore.kernel.org/rust-for-linux/20240328195457.225001-1-wedsonaf@gmail.com/ Link: https://lore.kernel.org/r/20241001-b4-miscdevice-v2-2-330d760041fa@google.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1 parent a69dc41 commit f893691

File tree

3 files changed

+243
-0
lines changed

3 files changed

+243
-0
lines changed

rust/bindings/bindings_helper.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <linux/firmware.h>
1616
#include <linux/jiffies.h>
1717
#include <linux/mdio.h>
18+
#include <linux/miscdevice.h>
1819
#include <linux/phy.h>
1920
#include <linux/refcount.h>
2021
#include <linux/sched.h>

rust/kernel/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ pub mod ioctl;
3939
#[cfg(CONFIG_KUNIT)]
4040
pub mod kunit;
4141
pub mod list;
42+
pub mod miscdevice;
4243
#[cfg(CONFIG_NET)]
4344
pub mod net;
4445
pub mod page;

rust/kernel/miscdevice.rs

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
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+
}

0 commit comments

Comments
 (0)