Skip to content

Commit 7ea525c

Browse files
authored
Merge pull request #126 from wedsonaf/ioctl
Allow drivers to implement ioctls.
2 parents 8482d37 + 6cc9ab5 commit 7ea525c

File tree

1 file changed

+177
-2
lines changed

1 file changed

+177
-2
lines changed

rust/kernel/file_operations.rs

Lines changed: 177 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,36 @@ use crate::error::{Error, KernelResult};
1616
use crate::user_ptr::{UserSlicePtr, UserSlicePtrReader, UserSlicePtrWriter};
1717

1818
/// Wraps the kernel's `struct file`.
19+
///
20+
/// # Invariants
21+
///
22+
/// The pointer [`File::ptr`] is non-null and valid.
1923
pub struct File {
2024
ptr: *const bindings::file,
2125
}
2226

2327
impl File {
28+
/// Constructs a new [`struct file`] wrapper.
29+
///
30+
/// # Safety
31+
///
32+
/// The pointer `ptr` must be non-null and valid for the lifetime of the object.
2433
unsafe fn from_ptr(ptr: *const bindings::file) -> File {
34+
// INVARIANTS: the safety contract ensures the type invariant will hold.
2535
File { ptr }
2636
}
2737

2838
/// Returns the current seek/cursor/pointer position (`struct file::f_pos`).
2939
pub fn pos(&self) -> u64 {
40+
// SAFETY: `File::ptr` is guaranteed to be valid by the type invariants.
3041
unsafe { (*self.ptr).f_pos as u64 }
3142
}
43+
44+
/// Returns whether the file is in blocking mode.
45+
pub fn is_blocking(&self) -> bool {
46+
// SAFETY: `File::ptr` is guaranteed to be valid by the type invariants.
47+
unsafe { (*self.ptr).f_flags & bindings::O_NONBLOCK == 0 }
48+
}
3249
}
3350

3451
/// Equivalent to [`std::io::SeekFrom`].
@@ -138,6 +155,34 @@ unsafe extern "C" fn llseek_callback<T: FileOperations>(
138155
}
139156
}
140157

158+
unsafe extern "C" fn unlocked_ioctl_callback<T: FileOperations>(
159+
file: *mut bindings::file,
160+
cmd: c_types::c_uint,
161+
arg: c_types::c_ulong,
162+
) -> c_types::c_long {
163+
from_kernel_result! {
164+
let f = &*((*file).private_data as *const T);
165+
// SAFETY: This function is called by the kernel, so it must set `fs` appropriately.
166+
let mut cmd = IoctlCommand::new(cmd as _, arg as _);
167+
let ret = T::ioctl(f, &File::from_ptr(file), &mut cmd)?;
168+
Ok(ret as _)
169+
}
170+
}
171+
172+
unsafe extern "C" fn compat_ioctl_callback<T: FileOperations>(
173+
file: *mut bindings::file,
174+
cmd: c_types::c_uint,
175+
arg: c_types::c_ulong,
176+
) -> c_types::c_long {
177+
from_kernel_result! {
178+
let f = &*((*file).private_data as *const T);
179+
// SAFETY: This function is called by the kernel, so it must set `fs` appropriately.
180+
let mut cmd = IoctlCommand::new(cmd as _, arg as _);
181+
let ret = T::compat_ioctl(f, &File::from_ptr(file), &mut cmd)?;
182+
Ok(ret as _)
183+
}
184+
}
185+
141186
unsafe extern "C" fn fsync_callback<T: FileOperations>(
142187
file: *mut bindings::file,
143188
start: bindings::loff_t,
@@ -177,7 +222,11 @@ impl<T: FileOperations> FileOperationsVtable<T> {
177222
},
178223

179224
check_flags: None,
180-
compat_ioctl: None,
225+
compat_ioctl: if T::TO_USE.compat_ioctl {
226+
Some(compat_ioctl_callback::<T>)
227+
} else {
228+
None
229+
},
181230
copy_file_range: None,
182231
fallocate: None,
183232
fadvise: None,
@@ -205,7 +254,11 @@ impl<T: FileOperations> FileOperationsVtable<T> {
205254
show_fdinfo: None,
206255
splice_read: None,
207256
splice_write: None,
208-
unlocked_ioctl: None,
257+
unlocked_ioctl: if T::TO_USE.ioctl {
258+
Some(unlocked_ioctl_callback::<T>)
259+
} else {
260+
None
261+
},
209262
write_iter: None,
210263
};
211264
}
@@ -221,6 +274,12 @@ pub struct ToUse {
221274
/// The `llseek` field of [`struct file_operations`].
222275
pub seek: bool,
223276

277+
/// The `unlocked_ioctl` field of [`struct file_operations`].
278+
pub ioctl: bool,
279+
280+
/// The `compat_ioctl` field of [`struct file_operations`].
281+
pub compat_ioctl: bool,
282+
224283
/// The `fsync` field of [`struct file_operations`].
225284
pub fsync: bool,
226285
}
@@ -231,6 +290,8 @@ pub const USE_NONE: ToUse = ToUse {
231290
read: false,
232291
write: false,
233292
seek: false,
293+
ioctl: false,
294+
compat_ioctl: false,
234295
fsync: false,
235296
};
236297

@@ -249,6 +310,106 @@ macro_rules! declare_file_operations {
249310
};
250311
}
251312

313+
/// Allows the handling of ioctls defined with the `_IO`, `_IOR`, `_IOW`, and `_IOWR` macros.
314+
///
315+
/// For each macro, there is a handler function that takes the appropriate types as arguments.
316+
pub trait IoctlHandler: Sync {
317+
/// Handles ioctls defined with the `_IO` macro, that is, with no buffer as argument.
318+
fn pure(&self, _file: &File, _cmd: u32, _arg: usize) -> KernelResult<i32> {
319+
Err(Error::EINVAL)
320+
}
321+
322+
/// Handles ioctls defined with the `_IOR` macro, that is, with an output buffer provided as
323+
/// argument.
324+
fn read(&self, _file: &File, _cmd: u32, _writer: &mut UserSlicePtrWriter) -> KernelResult<i32> {
325+
Err(Error::EINVAL)
326+
}
327+
328+
/// Handles ioctls defined with the `_IOW` macro, that is, with an input buffer provided as
329+
/// argument.
330+
fn write(
331+
&self,
332+
_file: &File,
333+
_cmd: u32,
334+
_reader: &mut UserSlicePtrReader,
335+
) -> KernelResult<i32> {
336+
Err(Error::EINVAL)
337+
}
338+
339+
/// Handles ioctls defined with the `_IOWR` macro, that is, with a buffer for both input and
340+
/// output provided as argument.
341+
fn read_write(&self, _file: &File, _cmd: u32, _data: UserSlicePtr) -> KernelResult<i32> {
342+
Err(Error::EINVAL)
343+
}
344+
}
345+
346+
/// Represents an ioctl command.
347+
///
348+
/// It can use the components of an ioctl command to dispatch ioctls using
349+
/// [`IoctlCommand::dispatch`].
350+
pub struct IoctlCommand {
351+
cmd: u32,
352+
arg: usize,
353+
user_slice: Option<UserSlicePtr>,
354+
}
355+
356+
impl IoctlCommand {
357+
/// Constructs a new [`IoctlCommand`].
358+
///
359+
/// # Safety
360+
///
361+
/// The caller must ensure that `fs` is compatible with `arg` and the original caller's
362+
/// context. For example, if the original caller is from userland (e.g., through the ioctl
363+
/// syscall), then `arg` is untrusted and `fs` should therefore be `USER_DS`.
364+
unsafe fn new(cmd: u32, arg: usize) -> Self {
365+
let user_slice = {
366+
let dir = (cmd >> bindings::_IOC_DIRSHIFT) & bindings::_IOC_DIRMASK;
367+
if dir == bindings::_IOC_NONE {
368+
None
369+
} else {
370+
let size = (cmd >> bindings::_IOC_SIZESHIFT) & bindings::_IOC_SIZEMASK;
371+
372+
// SAFETY: We only create one instance of the user slice, so TOCTOU issues are not
373+
// possible. The `set_fs` requirements are imposed on the caller.
374+
UserSlicePtr::new(arg as _, size as _).ok()
375+
}
376+
};
377+
378+
Self {
379+
cmd,
380+
arg,
381+
user_slice,
382+
}
383+
}
384+
385+
/// Dispatches the given ioctl to the appropriate handler based on the value of the command. It
386+
/// also creates a [`UserSlicePtr`], [`UserSlicePtrReader`], or [`UserSlicePtrWriter`]
387+
/// depending on the direction of the buffer of the command.
388+
///
389+
/// It is meant to be used in implementations of [`FileOperations::ioctl`] and
390+
/// [`FileOperations::compat_ioctl`].
391+
pub fn dispatch<T: IoctlHandler>(&mut self, handler: &T, file: &File) -> KernelResult<i32> {
392+
let dir = (self.cmd >> bindings::_IOC_DIRSHIFT) & bindings::_IOC_DIRMASK;
393+
if dir == bindings::_IOC_NONE {
394+
return T::pure(handler, file, self.cmd, self.arg);
395+
}
396+
397+
let data = self.user_slice.take().ok_or(Error::EFAULT)?;
398+
const READ_WRITE: u32 = bindings::_IOC_READ | bindings::_IOC_WRITE;
399+
match dir {
400+
bindings::_IOC_WRITE => T::write(handler, file, self.cmd, &mut data.reader()),
401+
bindings::_IOC_READ => T::read(handler, file, self.cmd, &mut data.writer()),
402+
READ_WRITE => T::read_write(handler, file, self.cmd, data),
403+
_ => Err(Error::EINVAL),
404+
}
405+
}
406+
407+
/// Returns the raw 32-bit value of the command and the ptr-sized argument.
408+
pub fn raw(&self) -> (u32, usize) {
409+
(self.cmd, self.arg)
410+
}
411+
}
412+
252413
/// Corresponds to the kernel's `struct file_operations`.
253414
///
254415
/// You implement this trait whenever you would create a `struct file_operations`.
@@ -296,6 +457,20 @@ pub trait FileOperations: Sync + Sized {
296457
Err(Error::EINVAL)
297458
}
298459

460+
/// Performs IO control operations that are specific to the file.
461+
///
462+
/// Corresponds to the `unlocked_ioctl` function pointer in `struct file_operations`.
463+
fn ioctl(&self, _file: &File, _cmd: &mut IoctlCommand) -> KernelResult<i32> {
464+
Err(Error::EINVAL)
465+
}
466+
467+
/// Performs 32-bit IO control operations on that are specific to the file on 64-bit kernels.
468+
///
469+
/// Corresponds to the `compat_ioctl` function pointer in `struct file_operations`.
470+
fn compat_ioctl(&self, _file: &File, _cmd: &mut IoctlCommand) -> KernelResult<i32> {
471+
Err(Error::EINVAL)
472+
}
473+
299474
/// Syncs pending changes to this file.
300475
///
301476
/// Corresponds to the `fsync` function pointer in `struct file_operations`.

0 commit comments

Comments
 (0)