Skip to content

Commit 8d2489b

Browse files
authored
Merge pull request #145 from wedsonaf/file-open
Allow files to share state.
2 parents 1d9b44b + 69ff160 commit 8d2489b

File tree

5 files changed

+193
-27
lines changed

5 files changed

+193
-27
lines changed

drivers/char/rust_example.rs

+109-8
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,16 @@
66
#![feature(allocator_api, global_asm)]
77
#![feature(test)]
88

9-
use alloc::boxed::Box;
9+
use alloc::{boxed::Box, sync::Arc};
1010
use core::pin::Pin;
1111
use kernel::prelude::*;
1212
use kernel::{
1313
chrdev, condvar_init, cstr,
14-
file_operations::FileOperations,
14+
file_operations::{File, FileOpener, FileOperations},
1515
miscdev, mutex_init, spinlock_init,
1616
sync::{CondVar, Mutex, SpinLock},
17+
user_ptr::{UserSlicePtrReader, UserSlicePtrWriter},
18+
Error,
1719
};
1820

1921
module! {
@@ -51,23 +53,120 @@ module! {
5153
},
5254
}
5355

54-
struct RustFile;
56+
const MAX_TOKENS: usize = 3;
5557

56-
impl FileOperations for RustFile {
58+
struct SharedStateInner {
59+
token_count: usize,
60+
}
61+
62+
struct SharedState {
63+
state_changed: CondVar,
64+
inner: Mutex<SharedStateInner>,
65+
}
66+
67+
impl SharedState {
68+
fn try_new() -> KernelResult<Arc<Self>> {
69+
let state = Arc::try_new(Self {
70+
// SAFETY: `condvar_init!` is called below.
71+
state_changed: unsafe { CondVar::new() },
72+
// SAFETY: `mutex_init!` is called below.
73+
inner: unsafe { Mutex::new(SharedStateInner { token_count: 0 }) },
74+
})?;
75+
// SAFETY: `state_changed` is pinned behind `Arc`.
76+
let state_changed = unsafe { Pin::new_unchecked(&state.state_changed) };
77+
kernel::condvar_init!(state_changed, "SharedState::state_changed");
78+
// SAFETY: `inner` is pinned behind `Arc`.
79+
let inner = unsafe { Pin::new_unchecked(&state.inner) };
80+
kernel::mutex_init!(inner, "SharedState::inner");
81+
Ok(state)
82+
}
83+
}
84+
85+
struct Token {
86+
shared: Arc<SharedState>,
87+
}
88+
89+
impl FileOpener<Arc<SharedState>> for Token {
90+
fn open(shared: &Arc<SharedState>) -> KernelResult<Self::Wrapper> {
91+
Ok(Box::try_new(Self {
92+
shared: shared.clone(),
93+
})?)
94+
}
95+
}
96+
97+
impl FileOperations for Token {
5798
type Wrapper = Box<Self>;
5899

59-
kernel::declare_file_operations!();
100+
kernel::declare_file_operations!(read, write);
101+
102+
fn read(&self, _: &File, data: &mut UserSlicePtrWriter, offset: u64) -> KernelResult<usize> {
103+
// Succeed if the caller doesn't provide a buffer or if not at the start.
104+
if data.is_empty() || offset != 0 {
105+
return Ok(0);
106+
}
107+
108+
{
109+
let mut inner = self.shared.inner.lock();
110+
111+
// Wait until we are allowed to decrement the token count or a signal arrives.
112+
while inner.token_count == 0 {
113+
if self.shared.state_changed.wait(&mut inner) {
114+
return Err(Error::EINTR);
115+
}
116+
}
117+
118+
// Consume a token.
119+
inner.token_count -= 1;
120+
}
121+
122+
// Notify a possible writer waiting.
123+
self.shared.state_changed.notify_all();
124+
125+
// Write a one-byte 1 to the reader.
126+
data.write_slice(&[1u8; 1])?;
127+
Ok(1)
128+
}
129+
130+
fn write(&self, data: &mut UserSlicePtrReader, _offset: u64) -> KernelResult<usize> {
131+
{
132+
let mut inner = self.shared.inner.lock();
60133

61-
fn open() -> KernelResult<Self::Wrapper> {
134+
// Wait until we are allowed to increment the token count or a signal arrives.
135+
while inner.token_count == MAX_TOKENS {
136+
if self.shared.state_changed.wait(&mut inner) {
137+
return Err(Error::EINTR);
138+
}
139+
}
140+
141+
// Increment the number of token so that a reader can be released.
142+
inner.token_count += 1;
143+
}
144+
145+
// Notify a possible reader waiting.
146+
self.shared.state_changed.notify_all();
147+
Ok(data.len())
148+
}
149+
}
150+
151+
struct RustFile;
152+
153+
impl FileOpener<()> for RustFile {
154+
fn open(_ctx: &()) -> KernelResult<Self::Wrapper> {
62155
println!("rust file was opened!");
63156
Ok(Box::try_new(Self)?)
64157
}
65158
}
66159

160+
impl FileOperations for RustFile {
161+
type Wrapper = Box<Self>;
162+
163+
kernel::declare_file_operations!();
164+
}
165+
67166
struct RustExample {
68167
message: String,
69168
_chrdev: Pin<Box<chrdev::Registration<2>>>,
70-
_dev: Pin<Box<miscdev::Registration>>,
169+
_dev: Pin<Box<miscdev::Registration<Arc<SharedState>>>>,
71170
}
72171

73172
impl KernelModule for RustExample {
@@ -146,9 +245,11 @@ impl KernelModule for RustExample {
146245
chrdev_reg.as_mut().register::<RustFile>()?;
147246
chrdev_reg.as_mut().register::<RustFile>()?;
148247

248+
let state = SharedState::try_new()?;
249+
149250
Ok(RustExample {
150251
message: "on the heap!".to_owned(),
151-
_dev: miscdev::Registration::new_pinned::<RustFile>(cstr!("rust_miscdev"), None, ())?,
252+
_dev: miscdev::Registration::new_pinned::<Token>(cstr!("rust_miscdev"), None, state)?,
152253
_chrdev: chrdev_reg,
153254
})
154255
}

rust/kernel/chrdev.rs

+20-2
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ impl<const N: usize> Registration<{ N }> {
7878
/// Registers a character device.
7979
///
8080
/// You may call this once per device type, up to `N` times.
81-
pub fn register<T: file_operations::FileOperations>(self: Pin<&mut Self>) -> KernelResult {
81+
pub fn register<T: file_operations::FileOpener<()>>(self: Pin<&mut Self>) -> KernelResult {
8282
// SAFETY: We must ensure that we never move out of `this`.
8383
let this = unsafe { self.get_unchecked_mut() };
8484
if this.inner.is_none() {
@@ -112,7 +112,12 @@ impl<const N: usize> Registration<{ N }> {
112112
// SAFETY: Calling unsafe functions and manipulating `MaybeUninit`
113113
// pointer.
114114
unsafe {
115-
bindings::cdev_init(cdev, file_operations::FileOperationsVtable::<T>::build());
115+
bindings::cdev_init(
116+
cdev,
117+
// SAFETY: The adapter doesn't retrieve any state yet, so it's compatible with any
118+
// registration.
119+
file_operations::FileOperationsVtable::<Self, T>::build(),
120+
);
116121
(*cdev).owner = this.this_module.0;
117122
let rc = bindings::cdev_add(cdev, inner.dev + inner.used as bindings::dev_t, 1);
118123
if rc != 0 {
@@ -124,6 +129,19 @@ impl<const N: usize> Registration<{ N }> {
124129
}
125130
}
126131

132+
impl<const N: usize> file_operations::FileOpenAdapter for Registration<{ N }> {
133+
type Arg = ();
134+
135+
unsafe fn convert(
136+
_inode: *mut bindings::inode,
137+
_file: *mut bindings::file,
138+
) -> *const Self::Arg {
139+
// TODO: Update the SAFETY comment on the call to `FileOperationsVTable::build` above once
140+
// this is updated to retrieve state.
141+
&()
142+
}
143+
}
144+
127145
// SAFETY: `Registration` does not expose any of its state across threads
128146
// (it is fine for multiple threads to have a shared reference to it).
129147
unsafe impl<const N: usize> Sync for Registration<{ N }> {}

rust/kernel/error.rs

+3
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ impl Error {
4545
/// No such file or directory.
4646
pub const ENOENT: Self = Error(-(bindings::ENOENT as i32));
4747

48+
/// Interrupted system call.
49+
pub const EINTR: Self = Error(-(bindings::EINTR as i32));
50+
4851
/// Creates an [`Error`] from a kernel error code.
4952
pub fn from_kernel_errno(errno: c_types::c_int) -> Error {
5053
Error(errno)

rust/kernel/file_operations.rs

+45-12
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,13 @@ macro_rules! from_kernel_result {
8282
}};
8383
}
8484

85-
unsafe extern "C" fn open_callback<T: FileOperations>(
86-
_inode: *mut bindings::inode,
85+
unsafe extern "C" fn open_callback<A: FileOpenAdapter, T: FileOpener<A::Arg>>(
86+
inode: *mut bindings::inode,
8787
file: *mut bindings::file,
8888
) -> c_types::c_int {
8989
from_kernel_result! {
90-
let ptr = T::open()?.into_pointer();
90+
let arg = A::convert(inode, file);
91+
let ptr = T::open(&*arg)?.into_pointer();
9192
(*file).private_data = ptr as *mut c_types::c_void;
9293
Ok(0)
9394
}
@@ -198,11 +199,11 @@ unsafe extern "C" fn fsync_callback<T: FileOperations>(
198199
}
199200
}
200201

201-
pub(crate) struct FileOperationsVtable<T>(marker::PhantomData<T>);
202+
pub(crate) struct FileOperationsVtable<A, T>(marker::PhantomData<A>, marker::PhantomData<T>);
202203

203-
impl<T: FileOperations> FileOperationsVtable<T> {
204+
impl<A: FileOpenAdapter, T: FileOpener<A::Arg>> FileOperationsVtable<A, T> {
204205
const VTABLE: bindings::file_operations = bindings::file_operations {
205-
open: Some(open_callback::<T>),
206+
open: Some(open_callback::<A, T>),
206207
release: Some(release_callback::<T>),
207208
read: if T::TO_USE.read {
208209
Some(read_callback::<T>)
@@ -262,7 +263,11 @@ impl<T: FileOperations> FileOperationsVtable<T> {
262263
};
263264

264265
/// Builds an instance of [`struct file_operations`].
265-
pub(crate) const fn build() -> &'static bindings::file_operations {
266+
///
267+
/// # Safety
268+
///
269+
/// The caller must ensure that the adapter is compatible with the way the device is registered.
270+
pub(crate) const unsafe fn build() -> &'static bindings::file_operations {
266271
&Self::VTABLE
267272
}
268273
}
@@ -400,6 +405,39 @@ impl IoctlCommand {
400405
}
401406
}
402407

408+
/// Trait for extracting file open arguments from kernel data structures.
409+
///
410+
/// This is meant to be implemented by registration managers.
411+
pub trait FileOpenAdapter {
412+
/// The type of argument this adapter extracts.
413+
type Arg;
414+
415+
/// Converts untyped data stored in [`struct inode`] and [`struct file`] (when [`struct
416+
/// file_operations::open`] is called) into the given type. For example, for `miscdev`
417+
/// devices, a pointer to the registered [`struct miscdev`] is stored in [`struct
418+
/// file::private_data`].
419+
///
420+
/// # Safety
421+
///
422+
/// This function must be called only when [`struct file_operations::open`] is being called for
423+
/// a file that was registered by the implementer.
424+
unsafe fn convert(_inode: *mut bindings::inode, _file: *mut bindings::file)
425+
-> *const Self::Arg;
426+
}
427+
428+
/// Trait for implementers of kernel files.
429+
///
430+
/// In addition to the methods in [`FileOperations`], implementers must also provide
431+
/// [`FileOpener::open`] with a customised argument. This allows a single implementation of
432+
/// [`FileOperations`] to be used for different types of registrations, for example, `miscdev` and
433+
/// `chrdev`.
434+
pub trait FileOpener<T: ?Sized>: FileOperations {
435+
/// Creates a new instance of this file.
436+
///
437+
/// Corresponds to the `open` function pointer in `struct file_operations`.
438+
fn open(context: &T) -> KernelResult<Self::Wrapper>;
439+
}
440+
403441
/// Corresponds to the kernel's `struct file_operations`.
404442
///
405443
/// You implement this trait whenever you would create a `struct file_operations`.
@@ -414,11 +452,6 @@ pub trait FileOperations: Send + Sync + Sized {
414452
/// The pointer type that will be used to hold ourselves.
415453
type Wrapper: PointerWrapper<Self>;
416454

417-
/// Creates a new instance of this file.
418-
///
419-
/// Corresponds to the `open` function pointer in `struct file_operations`.
420-
fn open() -> KernelResult<Self::Wrapper>;
421-
422455
/// Cleans up after the last reference to the file goes away.
423456
///
424457
/// Note that the object is moved, so it will be freed automatically unless the implementation

rust/kernel/miscdev.rs

+16-5
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
//! Reference: <https://www.kernel.org/doc/html/latest/driver-api/misc_devices.html>
88
99
use crate::error::{Error, KernelResult};
10-
use crate::file_operations::{FileOperations, FileOperationsVtable};
10+
use crate::file_operations::{FileOpenAdapter, FileOpener, FileOperationsVtable};
1111
use crate::{bindings, c_types, CStr};
1212
use alloc::boxed::Box;
1313
use core::marker::PhantomPinned;
@@ -19,7 +19,8 @@ pub struct Registration<T: Sync = ()> {
1919
mdev: bindings::miscdevice,
2020
_pin: PhantomPinned,
2121

22-
/// Context initialised on construction.
22+
/// Context initialised on construction and made available to all file instances on
23+
/// [`FileOpener::open`].
2324
pub context: T,
2425
}
2526

@@ -39,7 +40,7 @@ impl<T: Sync> Registration<T> {
3940
/// Registers a miscellaneous device.
4041
///
4142
/// Returns a pinned heap-allocated representation of the registration.
42-
pub fn new_pinned<F: FileOperations>(
43+
pub fn new_pinned<F: FileOpener<T>>(
4344
name: CStr<'static>,
4445
minor: Option<i32>,
4546
context: T,
@@ -53,7 +54,7 @@ impl<T: Sync> Registration<T> {
5354
///
5455
/// It must be pinned because the memory block that represents the registration is
5556
/// self-referential. If a minor is not given, the kernel allocates a new one if possible.
56-
pub fn register<F: FileOperations>(
57+
pub fn register<F: FileOpener<T>>(
5758
self: Pin<&mut Self>,
5859
name: CStr<'static>,
5960
minor: Option<i32>,
@@ -65,7 +66,8 @@ impl<T: Sync> Registration<T> {
6566
return Err(Error::EINVAL);
6667
}
6768

68-
this.mdev.fops = FileOperationsVtable::<F>::build();
69+
// SAFETY: The adapter is compatible with `misc_register`.
70+
this.mdev.fops = unsafe { FileOperationsVtable::<Self, F>::build() };
6971
this.mdev.name = name.as_ptr() as *const c_types::c_char;
7072
this.mdev.minor = minor.unwrap_or(bindings::MISC_DYNAMIC_MINOR as i32);
7173

@@ -78,6 +80,15 @@ impl<T: Sync> Registration<T> {
7880
}
7981
}
8082

83+
impl<T: Sync> FileOpenAdapter for Registration<T> {
84+
type Arg = T;
85+
86+
unsafe fn convert(_inode: *mut bindings::inode, file: *mut bindings::file) -> *const Self::Arg {
87+
let reg = crate::container_of!((*file).private_data, Self, mdev);
88+
&(*reg).context
89+
}
90+
}
91+
8192
// SAFETY: The only method is `register()`, which requires a (pinned) mutable `Registration`, so it
8293
// is safe to pass `&Registration` to multiple threads because it offers no interior mutability,
8394
// except maybe through `Registration::context`, but it is itself `Sync`.

0 commit comments

Comments
 (0)