Skip to content

Commit 9fd1981

Browse files
committed
Use Pin<T> in the chrdev API
This makes the safety more a part of the type-system. Also changes the chrdev API to a) require compile-time declaration of how many items it can have, b) allows gradual registration of chrdevs. Fixes #53
1 parent 03777f6 commit 9fd1981

File tree

1 file changed

+75
-69
lines changed

1 file changed

+75
-69
lines changed

rust/kernel/chrdev.rs

+75-69
Original file line numberDiff line numberDiff line change
@@ -1,100 +1,106 @@
11
// SPDX-License-Identifier: GPL-2.0
22

33
use core::convert::TryInto;
4-
use core::mem;
5-
use core::ops::Range;
6-
7-
use alloc::boxed::Box;
8-
use alloc::vec;
9-
use alloc::vec::Vec;
4+
use core::marker::PhantomPinned;
5+
use core::mem::MaybeUninit;
6+
use core::pin::Pin;
107

118
use crate::bindings;
129
use crate::c_types;
1310
use crate::error::{Error, KernelResult};
1411
use crate::file_operations;
1512
use crate::types::CStr;
1613

17-
pub fn builder(name: CStr<'static>, minors: Range<u16>) -> KernelResult<Builder> {
18-
Ok(Builder {
19-
name,
20-
minors,
21-
file_ops: vec![],
22-
})
14+
struct RegistrationInner<const N: usize> {
15+
dev: bindings::dev_t,
16+
used: usize,
17+
cdevs: [MaybeUninit<bindings::cdev>; N],
18+
_pin: PhantomPinned,
2319
}
2420

25-
pub struct Builder {
21+
/// chrdev registration. May contain up to a fixed number (N) of devices. Must
22+
/// be pinned.
23+
pub struct Registration<const N: usize> {
2624
name: CStr<'static>,
27-
minors: Range<u16>,
28-
file_ops: Vec<&'static bindings::file_operations>,
25+
minors_start: u16,
26+
this_module: &'static crate::ThisModule,
27+
inner: Option<RegistrationInner<N>>,
2928
}
3029

31-
impl Builder {
32-
pub fn register_device<T: file_operations::FileOperations>(mut self) -> Builder {
33-
if self.file_ops.len() >= self.minors.len() {
34-
panic!("More devices registered than minor numbers allocated.")
30+
impl<const N: usize> Registration<{ N }> {
31+
pub fn new(
32+
name: CStr<'static>,
33+
minors_start: u16,
34+
this_module: &'static crate::ThisModule,
35+
) -> Self {
36+
Registration {
37+
name,
38+
minors_start,
39+
this_module,
40+
inner: None,
3541
}
36-
self.file_ops
37-
.push(&file_operations::FileOperationsVtable::<T>::VTABLE);
38-
self
3942
}
4043

41-
pub fn build(self, this_module: &'static crate::ThisModule) -> KernelResult<Registration> {
42-
let mut dev: bindings::dev_t = 0;
43-
let res = unsafe {
44-
bindings::alloc_chrdev_region(
45-
&mut dev,
46-
self.minors.start.into(),
47-
self.minors.len().try_into()?,
48-
self.name.as_ptr() as *const c_types::c_char,
49-
)
50-
};
51-
if res != 0 {
52-
return Err(Error::from_kernel_errno(res));
44+
pub fn register<T: file_operations::FileOperations>(self: Pin<&mut Self>) -> KernelResult<()> {
45+
// SAFETY: we must ensure that we never move out of `this`.
46+
let this = unsafe { self.get_unchecked_mut() };
47+
if this.inner.is_none() {
48+
let mut dev: bindings::dev_t = 0;
49+
// SAFETY: Calling unsafe function. `this.name` has 'static
50+
// lifetime
51+
let res = unsafe {
52+
bindings::alloc_chrdev_region(
53+
&mut dev,
54+
this.minors_start.into(),
55+
N.try_into()?,
56+
this.name.as_ptr() as *const c_types::c_char,
57+
)
58+
};
59+
if res != 0 {
60+
return Err(Error::from_kernel_errno(res));
61+
}
62+
this.inner = Some(RegistrationInner {
63+
dev: dev,
64+
used: 0,
65+
cdevs: [MaybeUninit::<bindings::cdev>::uninit(); N],
66+
_pin: PhantomPinned,
67+
});
5368
}
5469

55-
// Turn this into a boxed slice immediately because the kernel stores pointers into it, and
56-
// so that data should never be moved.
57-
let mut cdevs = vec![unsafe { mem::zeroed() }; self.file_ops.len()].into_boxed_slice();
58-
for (i, file_op) in self.file_ops.iter().enumerate() {
59-
unsafe {
60-
bindings::cdev_init(&mut cdevs[i], *file_op);
61-
cdevs[i].owner = this_module.0;
62-
let rc = bindings::cdev_add(&mut cdevs[i], dev + i as bindings::dev_t, 1);
63-
if rc != 0 {
64-
// Clean up the ones that were allocated.
65-
for j in 0..=i {
66-
bindings::cdev_del(&mut cdevs[j]);
67-
}
68-
bindings::unregister_chrdev_region(dev, self.minors.len() as _);
69-
return Err(Error::from_kernel_errno(rc));
70-
}
70+
let mut inner = this.inner.as_mut().unwrap();
71+
if inner.used == N {
72+
panic!("More devices registered than minor numbers allocated.")
73+
}
74+
let cdev = inner.cdevs[inner.used].as_mut_ptr();
75+
// SAFETY: calling unsafe functions and manipulating MaybeUninit ptr.
76+
unsafe {
77+
bindings::cdev_init(cdev, &file_operations::FileOperationsVtable::<T>::VTABLE);
78+
(*cdev).owner = this.this_module.0;
79+
let rc = bindings::cdev_add(cdev, inner.dev + inner.used as bindings::dev_t, 1);
80+
if rc != 0 {
81+
return Err(Error::from_kernel_errno(rc));
7182
}
7283
}
73-
74-
Ok(Registration {
75-
dev,
76-
count: self.minors.len(),
77-
cdevs,
78-
})
84+
inner.used += 1;
85+
Ok(())
7986
}
8087
}
8188

82-
pub struct Registration {
83-
dev: bindings::dev_t,
84-
count: usize,
85-
cdevs: Box<[bindings::cdev]>,
86-
}
87-
88-
// This is safe because Registration doesn't actually expose any methods.
89-
unsafe impl Sync for Registration {}
89+
// SAFETY: `Registration` doesn't expose any of its state across threads (it's
90+
// fine for multiple threads to have a shared reference to it).
91+
unsafe impl<const N: usize> Sync for Registration<{ N }> {}
9092

91-
impl Drop for Registration {
93+
impl<const N: usize> Drop for Registration<{ N }> {
9294
fn drop(&mut self) {
93-
unsafe {
94-
for dev in self.cdevs.iter_mut() {
95-
bindings::cdev_del(dev);
95+
if let Some(inner) = self.inner.as_mut() {
96+
// SAFETY: calling unsafe functions, `0..inner.used` of
97+
// `inner.cdevs` are initialized in `Registration::register`.
98+
unsafe {
99+
for i in 0..inner.used {
100+
bindings::cdev_del(inner.cdevs[i].as_mut_ptr());
101+
}
102+
bindings::unregister_chrdev_region(inner.dev, N.try_into().unwrap());
96103
}
97-
bindings::unregister_chrdev_region(self.dev, self.count as _);
98104
}
99105
}
100106
}

0 commit comments

Comments
 (0)