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(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 page_cache::{CachePage, PageCache, PageCacheBackend};
|
||||
pub use random_test::{generate_random_operation, new_fs_in_memory};
|
||||
|
|
@ -40,7 +39,6 @@ mod fs;
|
|||
mod id_bitmap;
|
||||
mod inode;
|
||||
mod inode_mode;
|
||||
mod ioctl;
|
||||
mod open_args;
|
||||
mod page_cache;
|
||||
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
|
||||
|
||||
mod copy_compact;
|
||||
pub mod ioctl;
|
||||
mod iovec;
|
||||
pub mod net;
|
||||
mod padded;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
imports_granularity="Crate"
|
||||
group_imports="StdExternalCrate"
|
||||
reorder_imports=true
|
||||
skip_macro_invocations = ["chmod", "mkmod"]
|
||||
skip_macro_invocations = ["chmod", "mkmod", "ioc"]
|
||||
|
|
|
|||
Loading…
Reference in New Issue