Implement new ioctl infrastructure
This commit is contained in:
parent
04ef99cb82
commit
99fefb7adf
|
|
@ -1,75 +0,0 @@
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
#[expect(clippy::upper_case_acronyms)]
|
|
||||||
#[repr(u32)]
|
|
||||||
#[derive(Debug, Clone, Copy, TryFromInt)]
|
|
||||||
pub enum IoctlCmd {
|
|
||||||
/// Get terminal attributes
|
|
||||||
TCGETS = 0x5401,
|
|
||||||
/// Set terminal attributes
|
|
||||||
TCSETS = 0x5402,
|
|
||||||
/// Drain the output buffer and set attributes
|
|
||||||
TCSETSW = 0x5403,
|
|
||||||
/// Drain the output buffer, and discard pending input, and set attributes
|
|
||||||
TCSETSF = 0x5404,
|
|
||||||
/// Make the given terminal the controlling terminal of the calling process.
|
|
||||||
TIOCSCTTY = 0x540e,
|
|
||||||
/// Get the process group ID of the foreground process group on this terminal
|
|
||||||
TIOCGPGRP = 0x540f,
|
|
||||||
/// Set the foreground process group ID of this terminal.
|
|
||||||
TIOCSPGRP = 0x5410,
|
|
||||||
/// Get the number of bytes in the input buffer.
|
|
||||||
FIONREAD = 0x541B,
|
|
||||||
/// Get window size
|
|
||||||
TIOCGWINSZ = 0x5413,
|
|
||||||
/// Set window size
|
|
||||||
TIOCSWINSZ = 0x5414,
|
|
||||||
/// Enable or disable non-blocking I/O mode.
|
|
||||||
FIONBIO = 0x5421,
|
|
||||||
/// The calling process gives up this controlling terminal
|
|
||||||
TIOCNOTTY = 0x5422,
|
|
||||||
/// Return the session ID of FD
|
|
||||||
TIOCGSID = 0x5429,
|
|
||||||
/// Clear the close on exec flag on a file descriptor
|
|
||||||
FIONCLEX = 0x5450,
|
|
||||||
/// Set the close on exec flag on a file descriptor
|
|
||||||
FIOCLEX = 0x5451,
|
|
||||||
/// Enable or disable asynchronous I/O mode
|
|
||||||
FIOASYNC = 0x5452,
|
|
||||||
/// Get pty index
|
|
||||||
TIOCGPTN = 0x80045430,
|
|
||||||
/// Lock/unlock pty
|
|
||||||
TIOCSPTLCK = 0x40045431,
|
|
||||||
/// Get pty lock state
|
|
||||||
TIOCGPTLCK = 0x80045439,
|
|
||||||
/// Safely open the slave
|
|
||||||
TIOCGPTPEER = 0x5441,
|
|
||||||
/// font operations
|
|
||||||
KDFONTOP = 0x4B72,
|
|
||||||
/// Get console mode
|
|
||||||
KDGETMODE = 0x4B3B,
|
|
||||||
/// Set console mode
|
|
||||||
KDSETMODE = 0x4B3A,
|
|
||||||
/// Get keyboard mode
|
|
||||||
KDGKBMODE = 0x4B44,
|
|
||||||
/// Set keyboard mode
|
|
||||||
KDSKBMODE = 0x4B45,
|
|
||||||
/// Get tdx report using TDCALL
|
|
||||||
TDXGETREPORT = 0xc4405401,
|
|
||||||
/// Get variable screen information (resolution, color depth, etc.)
|
|
||||||
GETVSCREENINFO = 0x4600,
|
|
||||||
/// Set variable screen information
|
|
||||||
PUTVSCREENINFO = 0x4601,
|
|
||||||
/// Get fixed screen information (memory layout, line length, etc.)
|
|
||||||
GETFSCREENINFO = 0x4602,
|
|
||||||
/// Get framebuffer color map
|
|
||||||
GETCMAP = 0x4604,
|
|
||||||
/// Set framebuffer color map
|
|
||||||
PUTCMAP = 0x4605,
|
|
||||||
/// Pan display to show different part of virtual screen
|
|
||||||
PANDISPLAY = 0x4606,
|
|
||||||
/// Blank or unblank the framebuffer display
|
|
||||||
FBIOBLANK = 0x4611,
|
|
||||||
}
|
|
||||||
|
|
@ -17,7 +17,6 @@ pub use inode::{
|
||||||
};
|
};
|
||||||
pub use inode_mode::InodeMode;
|
pub use inode_mode::InodeMode;
|
||||||
pub(crate) use inode_mode::{chmod, mkmod, perms_to_mask, who_and_perms_to_mask, who_to_mask};
|
pub(crate) use inode_mode::{chmod, mkmod, perms_to_mask, who_and_perms_to_mask, who_to_mask};
|
||||||
pub use ioctl::IoctlCmd;
|
|
||||||
pub use open_args::OpenArgs;
|
pub use open_args::OpenArgs;
|
||||||
pub use page_cache::{CachePage, PageCache, PageCacheBackend};
|
pub use page_cache::{CachePage, PageCache, PageCacheBackend};
|
||||||
pub use random_test::{generate_random_operation, new_fs_in_memory};
|
pub use random_test::{generate_random_operation, new_fs_in_memory};
|
||||||
|
|
@ -40,7 +39,6 @@ mod fs;
|
||||||
mod id_bitmap;
|
mod id_bitmap;
|
||||||
mod inode;
|
mod inode;
|
||||||
mod inode_mode;
|
mod inode_mode;
|
||||||
mod ioctl;
|
|
||||||
mod open_args;
|
mod open_args;
|
||||||
mod page_cache;
|
mod page_cache;
|
||||||
mod random_test;
|
mod random_test;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,473 @@
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
//! Decoding and dispatching ioctl commands.
|
||||||
|
//!
|
||||||
|
//! When the system call layer handles an ioctl system call, it creates a [`RawIoctl`], which is
|
||||||
|
//! basically the numeric ioctl command and argument.
|
||||||
|
//!
|
||||||
|
//! The component that handles the specific ioctl logic should first convert the [`RawIoctl`] to a
|
||||||
|
//! strongly typed [`Ioctl`] instance, which decodes the ioctl command and includes type
|
||||||
|
//! information about the ioctl argument.
|
||||||
|
//!
|
||||||
|
//! To achieve this, define type aliases for [`Ioctl`] using the [`ioc`] macro, preferably in a
|
||||||
|
//! separate module for clarity. At the function that dispatches the ioctl commands, we can
|
||||||
|
//! glob-import the defined [`Ioctl`] type aliases and use the [`dispatch_ioctl`] macro for the
|
||||||
|
//! ioctl dispatching.
|
||||||
|
//!
|
||||||
|
//! Here is an complete example that demonstrates the basic usage:
|
||||||
|
//! ```
|
||||||
|
//! mod ioctl_defs {
|
||||||
|
//! use crate::util::ioctl::{ioc, InData, OutData, PassByVal};
|
||||||
|
//!
|
||||||
|
//! // Here we give the code in Linux to provide an intuitive guide on how to use the `ioc`
|
||||||
|
//! // macro and how it corresponds to the Linux definitions. This is for demonstration
|
||||||
|
//! // purposes only. For regular code, keeping the reference link below is sufficient.
|
||||||
|
//! //
|
||||||
|
//! // Also, note that the line containing the `ioc` macro will not be automatically formatted.
|
||||||
|
//! // This is a deliberate design so that whitespaces can be manually inserted to vertically
|
||||||
|
//! // align the `ioc` macro's parameters and improve code readability.
|
||||||
|
//!
|
||||||
|
//! // Reference: <https://elixir.bootlin.com/linux/v6.18/source/include/uapi/asm-generic/ioctls.h>>
|
||||||
|
//!
|
||||||
|
//! // ```c
|
||||||
|
//! // #define TIOCSCTTY 0x540E
|
||||||
|
//! // ```
|
||||||
|
//! pub(super) type SetControlTty = ioc!(TIOCSCTTY, 0x540E, InData<i32, PassByVal>);
|
||||||
|
//!
|
||||||
|
//! // ```c
|
||||||
|
//! // #define TIOCSPTLCK _IOW('T', 0x31, int) /* Lock/unlock Pty */
|
||||||
|
//! // ```
|
||||||
|
//! pub(super) type SetPtyLock = ioc!(TIOCSPTLCK, b'T', 0x31, InData<i32>);
|
||||||
|
//!
|
||||||
|
//! // ```c
|
||||||
|
//! // #define TIOCGPTLCK _IOR('T', 0x39, int) /* Get Pty lock state */
|
||||||
|
//! // ```
|
||||||
|
//! pub(super) type GetPtyLock = ioc!(TIOCGPTLCK, b'T', 0x39, OutData<i32>);
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! #[derive(Debug, Default)]
|
||||||
|
//! struct TtyState {
|
||||||
|
//! is_controlling: bool,
|
||||||
|
//! is_locked: bool,
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! impl TtyState {
|
||||||
|
//! fn ioctl(&mut self, raw_ioctl: RawIoctl) -> Result<()> {
|
||||||
|
//! use ioctl_defs::*;
|
||||||
|
//!
|
||||||
|
//! dispatch_ioctl!(match raw_ioctl {
|
||||||
|
//! cmd @ SetControlTty => {
|
||||||
|
//! let _should_steal_tty = cmd.get() == 1;
|
||||||
|
//! self.is_controlling = true;
|
||||||
|
//! }
|
||||||
|
//! cmd @ SetPtyLock => {
|
||||||
|
//! self.is_locked = cmd.read()? != 0;
|
||||||
|
//! }
|
||||||
|
//! cmd @ GetPtyLock => {
|
||||||
|
//! cmd.write(if self.is_locked { &1 } else { &0 })?;
|
||||||
|
//! }
|
||||||
|
//! _ => return_errno_with_message!(Errno::ENOTTY, "the ioctl command is unknown"),
|
||||||
|
//! });
|
||||||
|
//! Ok(())
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn fake_tty_ioctl() {
|
||||||
|
//! let mut state = TtyState::default();
|
||||||
|
//! assert!(!state.is_controlling);
|
||||||
|
//!
|
||||||
|
//! state.ioctl(RawIoctl::new(0x540E, 0));
|
||||||
|
//! assert!(state.is_controlling);
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Additionally, we support the following advanced usage:
|
||||||
|
//!
|
||||||
|
//! - For [`InOutData`], it is possible to obtain a [`SafePtr`] instance to the underlying data
|
||||||
|
//! using [`Ioctl::with_data_ptr`]. This allows some structure fields to be treated as input and
|
||||||
|
//! others as output. `TDX_CMD_GET_REPORT0` is such an example.
|
||||||
|
//!
|
||||||
|
//! - For `OutData<[u8]>` (or `InData<[u8]>`), the argument size is encoded in the ioctl command.
|
||||||
|
//! A [`VmWriter`] (or [`VmReader`]) can be obtained from [`Ioctl::with_writer`] (or
|
||||||
|
//! [`Ioctl::with_reader`]), which allows for the writing (or reading) of variable-length data.
|
||||||
|
//! `EVIOCGNAME` is such an example.
|
||||||
|
//!
|
||||||
|
|
||||||
|
use core::marker::PhantomData;
|
||||||
|
|
||||||
|
use aster_util::safe_ptr::SafePtr;
|
||||||
|
use sealed::{DataSpec, IoctlCmd, IoctlDir, PtrDataSpec};
|
||||||
|
|
||||||
|
use crate::{current_userspace, prelude::*};
|
||||||
|
|
||||||
|
mod sealed;
|
||||||
|
|
||||||
|
/// An ioctl command and its argument in raw form.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct RawIoctl {
|
||||||
|
cmd: u32,
|
||||||
|
arg: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawIoctl {
|
||||||
|
/// Creates an instance with the given ioctl command and argument.
|
||||||
|
pub const fn new(cmd: u32, arg: usize) -> Self {
|
||||||
|
Self { cmd, arg }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the ioctl command.
|
||||||
|
pub const fn cmd(self) -> u32 {
|
||||||
|
self.cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the ioctl argument.
|
||||||
|
pub const fn arg(self) -> usize {
|
||||||
|
self.arg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An ioctl command and its argument in strongly typed form.
|
||||||
|
///
|
||||||
|
/// `MAGIC` and `NR` are the Linux "magic" and "number" fields.
|
||||||
|
/// For legacy commands defined as raw constants, we may set them to the values
|
||||||
|
/// decoded from the raw number.
|
||||||
|
///
|
||||||
|
/// `IS_MODERN` indicates whether the ioctl is a modern or legacy one.
|
||||||
|
/// An legacy ioctl uses an arbitrary `u16` value as its number,
|
||||||
|
/// whereas a modern ioctl adopts a `u32` encoding.
|
||||||
|
///
|
||||||
|
/// `D` is one of [`NoData`], [`InData`], [`OutData`], or [`InOutData`].
|
||||||
|
/// It specifies key aspects about the input/output data in the ioctl argument.
|
||||||
|
pub struct Ioctl<const MAGIC: u8, const NR: u8, const IS_MODERN: bool, D> {
|
||||||
|
cmd: IoctlCmd,
|
||||||
|
arg: usize,
|
||||||
|
_phantom: PhantomData<D>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Defines an ioctl type.
|
||||||
|
///
|
||||||
|
/// # Legacy encoding
|
||||||
|
///
|
||||||
|
/// For legacy encoding, a 16-bit raw number is specified as the ioctl command.
|
||||||
|
///
|
||||||
|
/// For example,
|
||||||
|
/// ```
|
||||||
|
/// type NoTty = ioc!(TIOCNOTTY, 0x5422, NoData);
|
||||||
|
/// ```
|
||||||
|
/// It is equivalent to:
|
||||||
|
/// ```
|
||||||
|
/// type NoTty = Ioctl<0x54, 0x0E, /* IS_MODERN = */ false, NoData>;
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Modern encoding
|
||||||
|
///
|
||||||
|
/// For modern encoding, a magic (type number) and a command ID (sequence number) are specified.
|
||||||
|
/// These, along with the data direction and size, compose the 32-bit ioctl command.
|
||||||
|
///
|
||||||
|
/// For example,
|
||||||
|
/// ```
|
||||||
|
/// type SetPtyLock = ioc!(TIOCSPTLCK, b'T', 0x31, InData<i32>);
|
||||||
|
/// ```
|
||||||
|
/// It is equivalent to:
|
||||||
|
/// ```
|
||||||
|
/// type SetPtyLock = Ioctl<b'T', 0x31, /* IS_MODERN = */ true, InData<i32>>;
|
||||||
|
/// ```
|
||||||
|
macro_rules! ioc {
|
||||||
|
// Legacy encoding.
|
||||||
|
($linux_name:ident, $raw:literal, $data:ty) => {
|
||||||
|
$crate::util::ioctl::Ioctl::<
|
||||||
|
{
|
||||||
|
// MAGIC
|
||||||
|
$crate::util::ioctl::magic_and_nr_from_cmd($raw).0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// NR
|
||||||
|
$crate::util::ioctl::magic_and_nr_from_cmd($raw).1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// IS_MODERN
|
||||||
|
false
|
||||||
|
},
|
||||||
|
$data,
|
||||||
|
>
|
||||||
|
};
|
||||||
|
// Modern encoding.
|
||||||
|
($linux_name:ident, $magic:literal, $nr:literal, $data:ty) => {
|
||||||
|
$crate::util::ioctl::Ioctl::<{ $magic }, { $nr }, { true }, $data>
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub(crate) use ioc;
|
||||||
|
|
||||||
|
/// Extracts the "magic" and "number" fields from an ioctl command.
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub const fn magic_and_nr_from_cmd(raw_cmd: u16) -> (u8, u8) {
|
||||||
|
let cmd = IoctlCmd::new(raw_cmd as u32);
|
||||||
|
(cmd.magic(), cmd.nr())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const MAGIC: u8, const NR: u8, const IS_MODERN: bool, D: DataSpec>
|
||||||
|
Ioctl<MAGIC, NR, IS_MODERN, D>
|
||||||
|
{
|
||||||
|
/// Tries to interpret a [`RawIoctl`] as this particular ioctl command.
|
||||||
|
///
|
||||||
|
/// This method succeeds only if the ioctl command matches. Otherwise, this method returns
|
||||||
|
/// `None`.
|
||||||
|
pub fn try_from_raw(raw_ioctl: RawIoctl) -> Option<Self> {
|
||||||
|
let cmd = IoctlCmd::new(raw_ioctl.cmd);
|
||||||
|
|
||||||
|
if cmd.magic() != MAGIC {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if cmd.nr() != NR {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if IS_MODERN {
|
||||||
|
// For modern encoding, the upper 16 bits should contain the size and direction.
|
||||||
|
if let Some(size) = D::SIZE
|
||||||
|
&& cmd.size() != size
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if cmd.dir() != D::DIR {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For legacy encoding, the upper 16 bits should be zero.
|
||||||
|
if cmd.size() != 0 || cmd.dir() != IoctlDir::None {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Self {
|
||||||
|
cmd,
|
||||||
|
arg: raw_ioctl.arg,
|
||||||
|
_phantom: PhantomData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const MAGIC: u8, const NR: u8, const IS_MODERN: bool, D: PtrDataSpec>
|
||||||
|
Ioctl<MAGIC, NR, IS_MODERN, D>
|
||||||
|
{
|
||||||
|
fn with_data_ptr_unchecked_access<F, R>(&self, f: F) -> R
|
||||||
|
where
|
||||||
|
F: for<'a> FnOnce(SafePtr<D::Pointee, CurrentUserSpace<'a>>) -> R,
|
||||||
|
{
|
||||||
|
f(SafePtr::new(current_userspace!(), self.arg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// No input/output data.
|
||||||
|
pub struct NoData;
|
||||||
|
|
||||||
|
impl DataSpec for NoData {
|
||||||
|
const SIZE: Option<u16> = Some(0);
|
||||||
|
const DIR: IoctlDir = IoctlDir::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Input-only data.
|
||||||
|
///
|
||||||
|
/// `T` describes the data type.
|
||||||
|
/// `P` describes how the data is passed (by value or by pointer).
|
||||||
|
pub struct InData<T: ?Sized, P = PassByPtr>(PhantomData<T>, PhantomData<P>);
|
||||||
|
|
||||||
|
impl<T, P> DataSpec for InData<T, P> {
|
||||||
|
const SIZE: Option<u16> = Some(u16_size_of::<T>());
|
||||||
|
const DIR: IoctlDir = IoctlDir::Write;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> PtrDataSpec for InData<T, PassByPtr> {
|
||||||
|
type Pointee = T;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const MAGIC: u8, const NR: u8, const IS_MODERN: bool, T: Pod>
|
||||||
|
Ioctl<MAGIC, NR, IS_MODERN, InData<T, PassByPtr>>
|
||||||
|
{
|
||||||
|
/// Reads the ioctl argument from userspace.
|
||||||
|
pub fn read(&self) -> Result<T> {
|
||||||
|
Ok(self.with_data_ptr_unchecked_access(|ptr| ptr.read())?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_get_by_val_for {
|
||||||
|
{ $( $ty:ident )* } => {
|
||||||
|
$(
|
||||||
|
impl<const MAGIC: u8, const NR: u8, const IS_MODERN: bool>
|
||||||
|
Ioctl<MAGIC, NR, IS_MODERN, InData<$ty, PassByVal>>
|
||||||
|
{
|
||||||
|
/// Gets the ioctl argument.
|
||||||
|
pub fn get(&self) -> $ty {
|
||||||
|
self.arg as $ty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can add more types as needed, e.g., `u32`, `i8`.
|
||||||
|
impl_get_by_val_for! { i32 }
|
||||||
|
|
||||||
|
impl DataSpec for InData<[u8]> {
|
||||||
|
const SIZE: Option<u16> = None;
|
||||||
|
const DIR: IoctlDir = IoctlDir::Write;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const MAGIC: u8, const NR: u8, const IS_MODERN: bool>
|
||||||
|
Ioctl<MAGIC, NR, IS_MODERN, InData<[u8]>>
|
||||||
|
{
|
||||||
|
/// Obtains a [`VmReader`] that can read the dynamically-sized ioctl argument from userspace.
|
||||||
|
///
|
||||||
|
/// The size of the ioctl argument is specified in [`VmReader::remain`].
|
||||||
|
#[expect(dead_code)]
|
||||||
|
pub fn with_reader<F, R>(&self, f: F) -> Result<R>
|
||||||
|
where
|
||||||
|
F: for<'a> FnOnce(VmReader<'a>) -> Result<R>,
|
||||||
|
{
|
||||||
|
f(current_userspace!().reader(self.arg, self.cmd.size() as usize)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Output-only data, always passed by pointer.
|
||||||
|
pub struct OutData<T: ?Sized>(PhantomData<T>);
|
||||||
|
|
||||||
|
impl<T> DataSpec for OutData<T> {
|
||||||
|
const SIZE: Option<u16> = Some(u16_size_of::<T>());
|
||||||
|
const DIR: IoctlDir = IoctlDir::Read;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> PtrDataSpec for OutData<T> {
|
||||||
|
type Pointee = T;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const MAGIC: u8, const NR: u8, const IS_MODERN: bool, T: Pod>
|
||||||
|
Ioctl<MAGIC, NR, IS_MODERN, OutData<T>>
|
||||||
|
{
|
||||||
|
/// Writes the ioctl argument to userspace.
|
||||||
|
pub fn write(&self, val: &T) -> Result<()> {
|
||||||
|
self.with_data_ptr_unchecked_access(|ptr| ptr.write(val))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DataSpec for OutData<[u8]> {
|
||||||
|
const SIZE: Option<u16> = None;
|
||||||
|
const DIR: IoctlDir = IoctlDir::Read;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const MAGIC: u8, const NR: u8, const IS_MODERN: bool>
|
||||||
|
Ioctl<MAGIC, NR, IS_MODERN, OutData<[u8]>>
|
||||||
|
{
|
||||||
|
/// Obtains a [`VmWriter`] that can write the dynamically-sized ioctl argument to userspace.
|
||||||
|
///
|
||||||
|
/// The size of the ioctl argument is specified in [`VmWriter::avail`].
|
||||||
|
#[expect(dead_code)]
|
||||||
|
pub fn with_writer<F, R>(&self, f: F) -> Result<R>
|
||||||
|
where
|
||||||
|
F: for<'a> FnOnce(VmWriter<'a>) -> Result<R>,
|
||||||
|
{
|
||||||
|
f(current_userspace!().writer(self.arg, self.cmd.size() as usize)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Input and output data, passed by pointer to a single object.
|
||||||
|
pub struct InOutData<T>(PhantomData<T>);
|
||||||
|
|
||||||
|
impl<T> DataSpec for InOutData<T> {
|
||||||
|
const SIZE: Option<u16> = Some(u16_size_of::<T>());
|
||||||
|
const DIR: IoctlDir = IoctlDir::ReadWrite;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> PtrDataSpec for InOutData<T> {
|
||||||
|
type Pointee = T;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const MAGIC: u8, const NR: u8, const IS_MODERN: bool, T: Pod>
|
||||||
|
Ioctl<MAGIC, NR, IS_MODERN, InOutData<T>>
|
||||||
|
{
|
||||||
|
/// Reads the ioctl argument from userspace.
|
||||||
|
#[expect(dead_code)]
|
||||||
|
pub fn read(&self) -> Result<T> {
|
||||||
|
self.with_data_ptr(|ptr| Ok(ptr.read()?))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes the ioctl argument to userspace.
|
||||||
|
pub fn write(&self, val: &T) -> Result<()> {
|
||||||
|
self.with_data_ptr(|ptr| Ok(ptr.write(val)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Obtains a [`SafePtr`] that can access the ioctl argument in userspace.
|
||||||
|
pub fn with_data_ptr<F, R>(&self, f: F) -> Result<R>
|
||||||
|
where
|
||||||
|
F: for<'a> FnOnce(SafePtr<T, CurrentUserSpace<'a>>) -> Result<R>,
|
||||||
|
{
|
||||||
|
self.with_data_ptr_unchecked_access(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A marker that denotes the input is passed by value (i.e., encoded in the ioctl argument).
|
||||||
|
pub enum PassByVal {}
|
||||||
|
/// A marker that denotes the input is passed by pointer (i.e., pointed to by the ioctl argument).
|
||||||
|
pub enum PassByPtr {}
|
||||||
|
|
||||||
|
const fn u16_size_of<T>() -> u16 {
|
||||||
|
let size = size_of::<T>();
|
||||||
|
assert!(
|
||||||
|
size <= u16::MAX as usize,
|
||||||
|
"the type is too large to fit in an ioctl command"
|
||||||
|
);
|
||||||
|
size as u16
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dispatches ioctl commands.
|
||||||
|
///
|
||||||
|
/// See [the module-level documentation](self) for how to use this macro and the suggested style to
|
||||||
|
/// use this macro.
|
||||||
|
macro_rules! dispatch_ioctl {
|
||||||
|
// An empty match.
|
||||||
|
(
|
||||||
|
match $raw:ident {}
|
||||||
|
) => {
|
||||||
|
()
|
||||||
|
};
|
||||||
|
|
||||||
|
// The default branch.
|
||||||
|
(
|
||||||
|
match $raw:ident {
|
||||||
|
_ => $arm:expr $(,)?
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
$arm
|
||||||
|
};
|
||||||
|
|
||||||
|
// A branch that matches multiple ioctl commands.
|
||||||
|
(
|
||||||
|
match $raw:ident {
|
||||||
|
$ty0:ty $(| $ty1:ty)* => $arm:block $(,)?
|
||||||
|
$($rest:tt)*
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
if <$ty0>::try_from_raw($raw).is_some()
|
||||||
|
$(|| <$ty1>::try_from_raw($raw).is_some())*
|
||||||
|
{
|
||||||
|
$arm
|
||||||
|
} else {
|
||||||
|
crate::util::ioctl::dispatch_ioctl!(match $raw { $($rest)* })
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// A branch that matches a single ioctl command.
|
||||||
|
(
|
||||||
|
match $raw:ident {
|
||||||
|
$bind:ident @ $ty:ty => $arm:block $(,)?
|
||||||
|
$($rest:tt)*
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
if let Some($bind) = <$ty>::try_from_raw($raw) {
|
||||||
|
$arm
|
||||||
|
} else {
|
||||||
|
crate::util::ioctl::dispatch_ioctl!(match $raw { $($rest)* })
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub(crate) use dispatch_ioctl;
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use int_to_c_enum::TryFromInt;
|
||||||
|
|
||||||
|
/// An ioctl command that follows Linux-style encoding.
|
||||||
|
///
|
||||||
|
/// Layout (from LSB to MSB):
|
||||||
|
/// - bits 0..=7 : command number (nr)
|
||||||
|
/// - bits 8..=15 : type / magic
|
||||||
|
/// - bits 16..=29 : size (in bytes)
|
||||||
|
/// - bits 30..=31 : direction
|
||||||
|
///
|
||||||
|
/// Reference: <https://elixir.bootlin.com/linux/v6.18/source/include/uapi/asm-generic/ioctl.h#L69-L73>
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub(super) struct IoctlCmd(u32);
|
||||||
|
|
||||||
|
/// The direction of data transfer for an ioctl command.
|
||||||
|
///
|
||||||
|
/// The meaning is from the *user-space* perspective,
|
||||||
|
/// following Linux's `_IOC_*` macros:
|
||||||
|
/// - `None` -> `_IOC_NONE`
|
||||||
|
/// - `Write` -> `_IOC_WRITE` (user to kernel)
|
||||||
|
/// - `Read` -> `_IOC_READ` (kernel to user)
|
||||||
|
/// - `ReadWrite` -> `_IOC_READ | _IOC_WRITE`
|
||||||
|
///
|
||||||
|
/// Reference: <https://elixir.bootlin.com/linux/v6.18/source/include/uapi/asm-generic/ioctl.h#L49-L67>
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, TryFromInt)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum IoctlDir {
|
||||||
|
None = 0,
|
||||||
|
Write = 1,
|
||||||
|
Read = 2,
|
||||||
|
ReadWrite = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IoctlCmd {
|
||||||
|
pub(super) const fn new(cmd: u32) -> Self {
|
||||||
|
Self(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) const fn nr(self) -> u8 {
|
||||||
|
// Bits 0..=7
|
||||||
|
self.0 as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) const fn magic(self) -> u8 {
|
||||||
|
// Bits 8..=15
|
||||||
|
(self.0 >> 8) as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) const fn size(self) -> u16 {
|
||||||
|
// Bits 16..=29
|
||||||
|
((self.0 >> 16) as u16) & 0x3FFF
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn dir(self) -> IoctlDir {
|
||||||
|
// Bits 30..=31
|
||||||
|
IoctlDir::try_from((self.0 >> 30) as u8).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait DataSpec {
|
||||||
|
const SIZE: Option<u16>;
|
||||||
|
const DIR: IoctlDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait PtrDataSpec: DataSpec {
|
||||||
|
type Pointee;
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
mod copy_compact;
|
mod copy_compact;
|
||||||
|
pub mod ioctl;
|
||||||
mod iovec;
|
mod iovec;
|
||||||
pub mod net;
|
pub mod net;
|
||||||
mod padded;
|
mod padded;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
imports_granularity="Crate"
|
imports_granularity="Crate"
|
||||||
group_imports="StdExternalCrate"
|
group_imports="StdExternalCrate"
|
||||||
reorder_imports=true
|
reorder_imports=true
|
||||||
skip_macro_invocations = ["chmod", "mkmod"]
|
skip_macro_invocations = ["chmod", "mkmod", "ioc"]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue