diff --git a/drivers/android/process.rs b/drivers/android/process.rs index c41531896cb07f..cede106fd57a7e 100644 --- a/drivers/android/process.rs +++ b/drivers/android/process.rs @@ -805,12 +805,11 @@ impl IoctlHandler for Process { } } +#[vtable] impl file::Operations for Process { type Data = Ref; type OpenData = Ref; - kernel::declare_file_operations!(ioctl, compat_ioctl, mmap, poll); - fn open(ctx: &Ref, file: &File) -> Result { Self::new(ctx.clone(), file.cred().into()) } diff --git a/drivers/char/hw_random/bcm2835_rng_rust.rs b/drivers/char/hw_random/bcm2835_rng_rust.rs index 6d3cdc36a96aa0..f2dbd8606b3e7f 100644 --- a/drivers/char/hw_random/bcm2835_rng_rust.rs +++ b/drivers/char/hw_random/bcm2835_rng_rust.rs @@ -17,9 +17,8 @@ module_platform_driver! { struct RngDevice; +#[vtable] impl file::Operations for RngDevice { - kernel::declare_file_operations!(read); - fn open(_open_data: &(), _file: &File) -> Result { Ok(()) } diff --git a/drivers/gpio/gpio_pl061_rust.rs b/drivers/gpio/gpio_pl061_rust.rs index ba385d02584ad7..d417fa3b0abcf6 100644 --- a/drivers/gpio/gpio_pl061_rust.rs +++ b/drivers/gpio/gpio_pl061_rust.rs @@ -55,17 +55,10 @@ type DeviceData = device::Data; struct PL061Device; +#[vtable] impl gpio::Chip for PL061Device { type Data = Ref; - kernel::declare_gpio_chip_operations!( - get_direction, - direction_input, - direction_output, - get, - set - ); - fn get_direction(data: RefBorrow<'_, DeviceData>, offset: u32) -> Result { let pl061 = data.resources().ok_or(ENXIO)?; Ok(if pl061.base.readb(GPIODIR) & bit(offset) != 0 { @@ -129,11 +122,10 @@ impl gpio::ChipWithIrqChip for PL061Device { } } +#[vtable] impl irq::Chip for PL061Device { type Data = Ref; - kernel::declare_irq_chip_operations!(set_type, set_wake); - fn set_type( data: RefBorrow<'_, DeviceData>, irq_data: &mut LockedIrqData, diff --git a/rust/kernel/file.rs b/rust/kernel/file.rs index 8d5106dcc9dd00..0385d139f70ed7 100644 --- a/rust/kernel/file.rs +++ b/rust/kernel/file.rs @@ -19,6 +19,7 @@ use crate::{ }; use core::convert::{TryFrom, TryInto}; use core::{cell::UnsafeCell, marker, mem, ptr}; +use macros::vtable; /// Wraps the kernel's `struct file`. /// @@ -468,24 +469,24 @@ impl, T: Operations> OperationsVtable { const VTABLE: bindings::file_operations = bindings::file_operations { open: Some(Self::open_callback), release: Some(Self::release_callback), - read: if T::TO_USE.read { + read: if T::HAS_READ { Some(Self::read_callback) } else { None }, - write: if T::TO_USE.write { + write: if T::HAS_WRITE { Some(Self::write_callback) } else { None }, - llseek: if T::TO_USE.seek { + llseek: if T::HAS_SEEK { Some(Self::llseek_callback) } else { None }, check_flags: None, - compat_ioctl: if T::TO_USE.compat_ioctl { + compat_ioctl: if T::HAS_COMPAT_IOCTL { Some(Self::compat_ioctl_callback) } else { None @@ -496,7 +497,7 @@ impl, T: Operations> OperationsVtable { fasync: None, flock: None, flush: None, - fsync: if T::TO_USE.fsync { + fsync: if T::HAS_FSYNC { Some(Self::fsync_callback) } else { None @@ -506,19 +507,19 @@ impl, T: Operations> OperationsVtable { iterate_shared: None, iopoll: None, lock: None, - mmap: if T::TO_USE.mmap { + mmap: if T::HAS_MMAP { Some(Self::mmap_callback) } else { None }, mmap_supported_flags: 0, owner: ptr::null_mut(), - poll: if T::TO_USE.poll { + poll: if T::HAS_POLL { Some(Self::poll_callback) } else { None }, - read_iter: if T::TO_USE.read_iter { + read_iter: if T::HAS_READ { Some(Self::read_iter_callback) } else { None @@ -529,13 +530,13 @@ impl, T: Operations> OperationsVtable { show_fdinfo: None, splice_read: None, splice_write: None, - unlocked_ioctl: if T::TO_USE.ioctl { + unlocked_ioctl: if T::HAS_IOCTL { Some(Self::unlocked_ioctl_callback) } else { None }, uring_cmd: None, - write_iter: if T::TO_USE.write_iter { + write_iter: if T::HAS_WRITE { Some(Self::write_iter_callback) } else { None @@ -552,69 +553,6 @@ impl, T: Operations> OperationsVtable { } } -/// Represents which fields of [`struct file_operations`] should be populated with pointers. -pub struct ToUse { - /// The `read` field of [`struct file_operations`]. - pub read: bool, - - /// The `read_iter` field of [`struct file_operations`]. - pub read_iter: bool, - - /// The `write` field of [`struct file_operations`]. - pub write: bool, - - /// The `write_iter` field of [`struct file_operations`]. - pub write_iter: bool, - - /// The `llseek` field of [`struct file_operations`]. - pub seek: bool, - - /// The `unlocked_ioctl` field of [`struct file_operations`]. - pub ioctl: bool, - - /// The `compat_ioctl` field of [`struct file_operations`]. - pub compat_ioctl: bool, - - /// The `fsync` field of [`struct file_operations`]. - pub fsync: bool, - - /// The `mmap` field of [`struct file_operations`]. - pub mmap: bool, - - /// The `poll` field of [`struct file_operations`]. - pub poll: bool, -} - -/// A constant version where all values are to set to `false`, that is, all supported fields will -/// be set to null pointers. -pub const USE_NONE: ToUse = ToUse { - read: false, - read_iter: false, - write: false, - write_iter: false, - seek: false, - ioctl: false, - compat_ioctl: false, - fsync: false, - mmap: false, - poll: false, -}; - -/// Defines the [`Operations::TO_USE`] field based on a list of fields to be populated. -#[macro_export] -macro_rules! declare_file_operations { - () => { - const TO_USE: $crate::file::ToUse = $crate::file::USE_NONE; - }; - ($($i:ident),+) => { - const TO_USE: kernel::file::ToUse = - $crate::file::ToUse { - $($i: true),+ , - ..$crate::file::USE_NONE - }; - }; -} - /// Allows the handling of ioctls defined with the `_IO`, `_IOR`, `_IOW`, and `_IOWR` macros. /// /// For each macro, there is a handler function that takes the appropriate types as arguments. @@ -742,10 +680,8 @@ pub trait OpenAdapter { /// File descriptors may be used from multiple threads/processes concurrently, so your type must be /// [`Sync`]. It must also be [`Send`] because [`Operations::release`] will be called from the /// thread that decrements that associated file's refcount to zero. +#[vtable] pub trait Operations { - /// The methods to use to populate [`struct file_operations`]. - const TO_USE: ToUse; - /// The type of the context data returned by [`Operations::open`] and made available to /// other methods. type Data: PointerWrapper + Send + Sync = (); diff --git a/rust/kernel/gpio.rs b/rust/kernel/gpio.rs index e80826d20a4fc8..fcd10e07265937 100644 --- a/rust/kernel/gpio.rs +++ b/rust/kernel/gpio.rs @@ -13,6 +13,7 @@ use core::{ marker::{PhantomData, PhantomPinned}, pin::Pin, }; +use macros::vtable; #[cfg(CONFIG_GPIOLIB_IRQCHIP)] pub use irqchip::{ChipWithIrqChip, RegistrationWithIrqChip}; @@ -27,16 +28,13 @@ pub enum LineDirection { } /// A gpio chip. +#[vtable] pub trait Chip { /// Context data associated with the gpio chip. /// /// It determines the type of the context data passed to each of the methods of the trait. type Data: PointerWrapper + Sync + Send; - /// The methods to use to populate [`struct gpio_chip`]. This is typically populated with - /// [`declare_gpio_chip_operations`]. - const TO_USE: ToUse; - /// Returns the direction of the given gpio line. fn get_direction( _data: ::Borrowed<'_>, @@ -73,52 +71,6 @@ pub trait Chip { fn set(_data: ::Borrowed<'_>, _offset: u32, _value: bool) {} } -/// Represents which fields of [`struct gpio_chip`] should be populated with pointers. -/// -/// This is typically populated with the [`declare_gpio_chip_operations`] macro. -pub struct ToUse { - /// The `get_direction` field of [`struct gpio_chip`]. - pub get_direction: bool, - - /// The `direction_input` field of [`struct gpio_chip`]. - pub direction_input: bool, - - /// The `direction_output` field of [`struct gpio_chip`]. - pub direction_output: bool, - - /// The `get` field of [`struct gpio_chip`]. - pub get: bool, - - /// The `set` field of [`struct gpio_chip`]. - pub set: bool, -} - -/// A constant version where all values are set to `false`, that is, all supported fields will be -/// set to null pointers. -pub const USE_NONE: ToUse = ToUse { - get_direction: false, - direction_input: false, - direction_output: false, - get: false, - set: false, -}; - -/// Defines the [`Chip::TO_USE`] field based on a list of fields to be populated. -#[macro_export] -macro_rules! declare_gpio_chip_operations { - () => { - const TO_USE: $crate::gpio::ToUse = $crate::gpio::USE_NONE; - }; - ($($i:ident),+) => { - #[allow(clippy::needless_update)] - const TO_USE: $crate::gpio::ToUse = - $crate::gpio::ToUse { - $($i: true),+ , - ..$crate::gpio::USE_NONE - }; - }; -} - /// A registration of a gpio chip. /// /// # Examples @@ -130,9 +82,9 @@ macro_rules! declare_gpio_chip_operations { /// use kernel::{device::RawDevice, gpio::{self, Registration}}; /// /// struct MyGpioChip; +/// #[vtable] /// impl gpio::Chip for MyGpioChip { /// type Data = (); -/// kernel::declare_gpio_chip_operations!(); /// } /// /// fn example(parent: &dyn RawDevice) -> Result>>> { @@ -186,19 +138,19 @@ impl Registration { // Set up the callbacks. gc.request = Some(bindings::gpiochip_generic_request); gc.free = Some(bindings::gpiochip_generic_free); - if T::TO_USE.get_direction { + if T::HAS_GET_DIRECTION { gc.get_direction = Some(get_direction_callback::); } - if T::TO_USE.direction_input { + if T::HAS_DIRECTION_INPUT { gc.direction_input = Some(direction_input_callback::); } - if T::TO_USE.direction_output { + if T::HAS_DIRECTION_OUTPUT { gc.direction_output = Some(direction_output_callback::); } - if T::TO_USE.get { + if T::HAS_GET { gc.get = Some(get_callback::); } - if T::TO_USE.set { + if T::HAS_SET { gc.set = Some(set_callback::); } @@ -475,9 +427,12 @@ mod irqchip { /// data is passed as context. struct IrqChipAdapter(PhantomData); + #[vtable] impl irq::Chip for IrqChipAdapter { type Data = *mut bindings::gpio_chip; - const TO_USE: irq::ToUse = T::TO_USE; + + const HAS_SET_TYPE: bool = T::HAS_SET_TYPE; + const HAS_SET_WAKE: bool = T::HAS_SET_WAKE; fn ack(gc: *mut bindings::gpio_chip, irq_data: &irq::IrqData) { // SAFETY: `IrqChipAdapter` is a private struct, only used when the data stored in the diff --git a/rust/kernel/hwrng.rs b/rust/kernel/hwrng.rs index 08d2295ca5a58e..3e9bd354ab6b56 100644 --- a/rust/kernel/hwrng.rs +++ b/rust/kernel/hwrng.rs @@ -10,14 +10,13 @@ use crate::{ bindings, error::code::*, error::from_kernel_result, str::CString, to_result, types::PointerWrapper, Result, ScopeGuard, }; +use macros::vtable; use core::{cell::UnsafeCell, fmt, marker::PhantomData, pin::Pin}; /// This trait is implemented in order to provide callbacks to `struct hwrng`. +#[vtable] pub trait Operations { - /// The methods to use to populate [`struct hwrng`]. - const TO_USE: ToUse; - /// The pointer type that will be used to hold user-defined data type. type Data: PointerWrapper + Send + Sync = (); @@ -122,12 +121,12 @@ impl Registration { ) { hwrng.name = name.as_char_ptr(); - hwrng.init = if T::TO_USE.init { + hwrng.init = if T::HAS_INIT { Some(Self::init_callback) } else { None }; - hwrng.cleanup = if T::TO_USE.cleanup { + hwrng.cleanup = if T::HAS_CLEANUP { Some(Self::cleanup_callback) } else { None @@ -190,38 +189,6 @@ impl Default for Registration { } } -/// Represents which callbacks of [`struct hwrng`] should be populated with pointers. -pub struct ToUse { - /// The `init` field of [`struct hwrng`]. - pub init: bool, - - /// The `cleanup` field of [`struct hwrng`]. - pub cleanup: bool, -} - -/// A constant version where all values are to set to `false`, that is, all supported fields will -/// be set to null pointers. -pub const USE_NONE: ToUse = ToUse { - init: false, - cleanup: false, -}; - -/// Defines the [`Operations::TO_USE`] field based on a list of fields to be populated. -#[macro_export] -macro_rules! declare_hwrng_operations { - () => { - const TO_USE: $crate::hwrng::ToUse = $crate::hwrng::USE_NONE; - }; - ($($i:ident),+) => { - #[allow(clippy::needless_update)] - const TO_USE: kernel::hwrng::ToUse = - $crate::hwrng::ToUse { - $($i: true),+ , - ..$crate::hwrng::USE_NONE - }; - }; -} - // SAFETY: `Registration` does not expose any of its state across threads. unsafe impl Sync for Registration {} diff --git a/rust/kernel/irq.rs b/rust/kernel/irq.rs index 04181a196b2d37..52706f78105876 100644 --- a/rust/kernel/irq.rs +++ b/rust/kernel/irq.rs @@ -17,6 +17,7 @@ use crate::{ Error, Result, ScopeGuard, }; use core::{fmt, marker::PhantomData, ops::Deref}; +use macros::vtable; /// The type of irq hardware numbers. pub type HwNumber = bindings::irq_hw_number_t; @@ -101,14 +102,11 @@ pub enum ExtraResult { /// It is a trait for the functions defined in [`struct irq_chip`]. /// /// [`struct irq_chip`]: ../../../include/linux/irq.h +#[vtable] pub trait Chip: Sized { /// The type of the context data stored in the irq chip and made available on each callback. type Data: PointerWrapper; - /// The methods to use to populate [`struct irq_chip`]. This is typically populated with - /// [`declare_irq_chip_operations`]. - const TO_USE: ToUse; - /// Called at the start of a new interrupt. fn ack(data: ::Borrowed<'_>, irq_data: &IrqData); @@ -150,49 +148,15 @@ pub(crate) unsafe fn init_chip(chip: &mut bindings::irq_chip) { chip.irq_mask = Some(irq_mask_callback::); chip.irq_unmask = Some(irq_unmask_callback::); - if T::TO_USE.set_type { + if T::HAS_SET_TYPE { chip.irq_set_type = Some(irq_set_type_callback::); } - if T::TO_USE.set_wake { + if T::HAS_SET_WAKE { chip.irq_set_wake = Some(irq_set_wake_callback::); } } -/// Represents which fields of [`struct irq_chip`] should be populated with pointers. -/// -/// This is typically populated with the [`declare_irq_chip_operations`] macro. -pub struct ToUse { - /// The `irq_set_type` field of [`struct irq_chip`]. - pub set_type: bool, - - /// The `irq_set_wake` field of [`struct irq_chip`]. - pub set_wake: bool, -} - -/// A constant version where all values are to set to `false`, that is, all supported fields will -/// be set to null pointers. -pub const USE_NONE: ToUse = ToUse { - set_type: false, - set_wake: false, -}; - -/// Defines the [`Chip::TO_USE`] field based on a list of fields to be populated. -#[macro_export] -macro_rules! declare_irq_chip_operations { - () => { - const TO_USE: $crate::irq::ToUse = $crate::irq::USE_NONE; - }; - ($($i:ident),+) => { - #[allow(clippy::needless_update)] - const TO_USE: $crate::irq::ToUse = - $crate::irq::ToUse { - $($i: true),+ , - ..$crate::irq::USE_NONE - }; - }; -} - /// Enables or disables power-management wake-on for the given irq number. pub fn set_wake(irq: u32, on: bool) -> Result { // SAFETY: Just an FFI call, there are no extra requirements for safety. diff --git a/rust/kernel/miscdev.rs b/rust/kernel/miscdev.rs index 8b1110b0143c9c..65b95d6dba906c 100644 --- a/rust/kernel/miscdev.rs +++ b/rust/kernel/miscdev.rs @@ -275,9 +275,8 @@ impl> crate::Module for Module { /// #[derive(Default)] /// struct MyFile; /// -/// impl kernel::file::Operations for MyFile { -/// kernel::declare_file_operations!(); -/// } +/// #[vtable] +/// impl kernel::file::Operations for MyFile {} /// ``` #[macro_export] macro_rules! module_misc_device { diff --git a/rust/kernel/prelude.rs b/rust/kernel/prelude.rs index a02b9a9d1937cc..c9c6ff5f4b478a 100644 --- a/rust/kernel/prelude.rs +++ b/rust/kernel/prelude.rs @@ -15,7 +15,7 @@ pub use core::pin::Pin; pub use alloc::{boxed::Box, string::String, vec::Vec}; -pub use macros::module; +pub use macros::{module, vtable}; pub use super::build_assert; diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs index d4ac221387a125..43e07b259a2d56 100644 --- a/rust/macros/lib.rs +++ b/rust/macros/lib.rs @@ -4,6 +4,7 @@ mod helpers; mod module; +mod vtable; use proc_macro::TokenStream; @@ -92,3 +93,54 @@ use proc_macro::TokenStream; pub fn module(ts: TokenStream) -> TokenStream { module::module(ts) } + +/// Declares or implements a vtable trait. +/// +/// Linux's use of pure vtables is very close to Rust traits, but they differ +/// in how unimplemented functions are represented. In Rust, traits can provide +/// default implementation for all non-required methods (and the default +/// implementation could just return `Error::EINVAL`); Linux typically use C +/// `NULL` pointers to represent these functions. +/// +/// This attribute is intended to close the gap. Traits can be declared and +/// implemented with the `#[vtable]` attribute, and a `HAS_*` associated constant +/// will be generated for each method in the trait, indicating if the implementor +/// has overriden a method. +/// +/// This attribute is not needed if all methods are required. +/// +/// # Examples +/// +/// ```ignore +/// use kernel::prelude::*; +/// +/// // Declares a `#[vtable]` trait +/// #[vtable] +/// pub trait Operations: Send + Sync + Sized { +/// fn foo(&self) -> Result<()> { +/// Err(EINVAL) +/// } +/// +/// fn bar(&self) -> Result<()> { +/// Err(EINVAL) +/// } +/// } +/// +/// struct Foo; +/// +/// // Implements the `#[vtable]` trait +/// #[vtable] +/// impl Operations for Foo { +/// fn foo(&self) -> Result<()> { +/// # Err(EINVAL) +/// /* ... */ +/// } +/// } +/// +/// assert_eq!(::HAS_FOO, true); +/// assert_eq!(::HAS_BAR, false); +/// ``` +#[proc_macro_attribute] +pub fn vtable(attr: TokenStream, ts: TokenStream) -> TokenStream { + vtable::vtable(attr, ts) +} diff --git a/rust/macros/vtable.rs b/rust/macros/vtable.rs new file mode 100644 index 00000000000000..1ab64bade873e3 --- /dev/null +++ b/rust/macros/vtable.rs @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0 + +use proc_macro::{Delimiter, Group, TokenStream, TokenTree}; +use std::collections::HashSet; +use std::fmt::Write; + +pub(crate) fn vtable(_attr: TokenStream, ts: TokenStream) -> TokenStream { + let mut tokens: Vec<_> = ts.into_iter().collect(); + + // Scan for the `trait` or `impl` keyword + let is_trait = tokens + .iter() + .find_map(|token| match token { + TokenTree::Ident(ident) => match ident.to_string().as_str() { + "trait" => Some(true), + "impl" => Some(false), + _ => None, + }, + _ => None, + }) + .expect("#[vtable] attribute should only be applied to trait or impl block"); + + // Retrieve the main body. The main body should be the last token tree. + let body = match tokens.pop() { + Some(TokenTree::Group(group)) if group.delimiter() == Delimiter::Brace => group, + _ => panic!("cannot locate main body of trait or impl block"), + }; + + let mut body_it = body.stream().into_iter(); + let mut functions = Vec::new(); + let mut consts = HashSet::new(); + while let Some(token) = body_it.next() { + match token { + TokenTree::Ident(ident) if ident.to_string() == "fn" => { + let fn_name = match body_it.next() { + Some(TokenTree::Ident(ident)) => ident.to_string(), + // Possibly we've encountered a fn pointer type instead. + _ => continue, + }; + functions.push(fn_name); + } + TokenTree::Ident(ident) if ident.to_string() == "const" => { + let const_name = match body_it.next() { + Some(TokenTree::Ident(ident)) => ident.to_string(), + // Possibly we've encountered an inline const block instead. + _ => continue, + }; + consts.insert(const_name); + } + _ => (), + } + } + + let mut const_items; + if is_trait { + const_items = "/// A marker to prevent implementors from forgetting to use [`#[vtable]`](vtable) attribute when implementing this trait. + const USE_VTABLE_ATTR: ();".to_owned(); + + for f in functions { + let gen_const_name = format!("HAS_{}", f.to_uppercase()); + // Skip if it's declared already -- this allows user override. + if consts.contains(&gen_const_name) { + continue; + } + // We don't know on the implementation-site whether a method is required or provided + // so we have to generate a const for all methods. + write!( + const_items, + "/// Indicates if the `{f}` method is overriden by the implementor. + const {gen_const_name}: bool = false;", + ) + .unwrap(); + } + } else { + const_items = "const USE_VTABLE_ATTR: () = ();".to_owned(); + + for f in functions { + let gen_const_name = format!("HAS_{}", f.to_uppercase()); + if consts.contains(&gen_const_name) { + continue; + } + write!(const_items, "const {gen_const_name}: bool = true;").unwrap(); + } + } + + let new_body = vec![const_items.parse().unwrap(), body.stream()] + .into_iter() + .collect(); + tokens.push(TokenTree::Group(Group::new(Delimiter::Brace, new_body))); + tokens.into_iter().collect() +} diff --git a/samples/rust/rust_chrdev.rs b/samples/rust/rust_chrdev.rs index 9f5d564671eaca..52f6e652d1a6c2 100644 --- a/samples/rust/rust_chrdev.rs +++ b/samples/rust/rust_chrdev.rs @@ -15,9 +15,8 @@ module! { struct RustFile; +#[vtable] impl file::Operations for RustFile { - kernel::declare_file_operations!(); - fn open(_shared: &(), _file: &file::File) -> Result { Ok(()) } diff --git a/samples/rust/rust_miscdev.rs b/samples/rust/rust_miscdev.rs index d1bf3c61f5cee8..647b77864f10ef 100644 --- a/samples/rust/rust_miscdev.rs +++ b/samples/rust/rust_miscdev.rs @@ -51,12 +51,11 @@ impl SharedState { } struct Token; +#[vtable] impl file::Operations for Token { type Data = Ref; type OpenData = Ref; - kernel::declare_file_operations!(read, write); - fn open(shared: &Ref, _file: &File) -> Result { Ok(shared.clone()) } diff --git a/samples/rust/rust_random.rs b/samples/rust/rust_random.rs index 8ec87119aa9bb8..341bf668dc6480 100644 --- a/samples/rust/rust_random.rs +++ b/samples/rust/rust_random.rs @@ -21,9 +21,8 @@ module_misc_device! { struct RandomFile; +#[vtable] impl file::Operations for RandomFile { - kernel::declare_file_operations!(read, write, read_iter, write_iter); - fn open(_data: &(), _file: &File) -> Result { Ok(()) } diff --git a/samples/rust/rust_semaphore.rs b/samples/rust/rust_semaphore.rs index 702ac1fcb48a8c..e91f82a6abfb42 100644 --- a/samples/rust/rust_semaphore.rs +++ b/samples/rust/rust_semaphore.rs @@ -15,7 +15,7 @@ use core::sync::atomic::{AtomicU64, Ordering}; use kernel::{ - condvar_init, declare_file_operations, + condvar_init, file::{self, File, IoctlCommand, IoctlHandler}, io_buffer::{IoBufferReader, IoBufferWriter}, miscdev::Registration, @@ -61,12 +61,11 @@ impl FileState { } } +#[vtable] impl file::Operations for FileState { type Data = Box; type OpenData = Ref; - declare_file_operations!(read, write, ioctl); - fn open(shared: &Ref, _file: &File) -> Result> { Ok(Box::try_new(Self { read_count: AtomicU64::new(0),