[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-ID: <tencent_6FBA3773CF74B276166B63D292CB2E8D3D07@qq.com>
Date: Wed, 11 Dec 2024 20:43:36 +0800
From: Guangbo Cui <2407018371@...com>
To: Miguel Ojeda <ojeda@...nel.org>,
Greg Kroah-Hartman <gregkh@...uxfoundation.org>,
Alex Gaynor <alex.gaynor@...il.com>
Cc: Boqun Feng <boqun.feng@...il.com>,
Gary Guo <gary@...yguo.net>,
Björn Roy Baron <bjorn3_gh@...tonmail.com>,
Benno Lossin <benno.lossin@...ton.me>,
Andreas Hindborg <a.hindborg@...nel.org>,
Alice Ryhl <aliceryhl@...gle.com>,
Danilo Krummrich <dakr@...nel.org>,
Trevor Gross <tmgross@...ch.edu>,
rust-for-linux@...r.kernel.org,
linux-kernel@...r.kernel.org,
Guangbo Cui <2407018371@...com>
Subject: [RFC PATCH] Add UIO (Userspace I/O) device rust abstraction
This patch implements the necessary Rust abstractions to implement
UIO device in Rust.
I am preparing to refactor our company’s UIO driver in Rust, targeting
both user space and kernel space. As the first step, I plan to focus on
implementing the UIO device abstraction as foundational infrastructure and
eager to explore potential improvements and suggestions from the
community.
Signed-off-by: Guangbo Cui <2407018371@...com>
---
rust/bindings/bindings_helper.h | 1 +
rust/kernel/lib.rs | 2 +
rust/kernel/uio.rs | 420 ++++++++++++++++++++++++++++++++
3 files changed, 423 insertions(+)
create mode 100644 rust/kernel/uio.rs
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index e9fdceb568..0d9abe7853 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -31,6 +31,7 @@
#include <linux/security.h>
#include <linux/slab.h>
#include <linux/tracepoint.h>
+#include <linux/uio_driver.h>
#include <linux/wait.h>
#include <linux/workqueue.h>
#include <trace/events/rust_sample.h>
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 1b4b533b38..03f95edaab 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -79,6 +79,8 @@
pub mod transmute;
pub mod types;
pub mod uaccess;
+#[cfg(CONFIG_UIO)]
+pub mod uio;
pub mod workqueue;
#[doc(hidden)]
diff --git a/rust/kernel/uio.rs b/rust/kernel/uio.rs
new file mode 100644
index 0000000000..2c868d1c13
--- /dev/null
+++ b/rust/kernel/uio.rs
@@ -0,0 +1,420 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Abstractions for the uio driver.
+//!
+//! C header: [`include/linux/uio_driver.h`](srctree/include/linux/uio_driver.h)
+
+use core::{marker::PhantomData, mem::MaybeUninit, slice};
+
+use crate::{
+ device,
+ error::{to_result, Result, VTABLE_DEFAULT_ERROR},
+ ffi,
+ mm::virt::VmAreaNew,
+ prelude::*,
+ types::{ARef, ForeignOwnable, Opaque},
+};
+
+/// Maximum number of memory maps supported by UIO.
+pub const MAX_UIO_MAPS: usize = bindings::MAX_UIO_MAPS as _;
+
+/// Options for configuring a UIO (Userspace I/O) device.
+///
+/// This struct provides the necessary configuration to register a UIO device,
+/// including its name, version, interrupt settings, and memory maps.
+pub struct UioDeviceOptions {
+ /// device name
+ pub name: &'static CStr,
+ /// device version
+ pub version: &'static CStr,
+ /// interrupt
+ pub irq: u32,
+ /// uio memory maps
+ pub mem: [UioDeviceMemOptions; MAX_UIO_MAPS],
+}
+
+impl UioDeviceOptions {
+ /// create a default uio device options
+ pub const fn new(name: &'static CStr, version: &'static CStr) -> Self {
+ Self {
+ name,
+ version,
+ irq: irq::UIO_IRQ_NONE,
+ mem: [const { UioDeviceMemOptions::new() }; MAX_UIO_MAPS],
+ }
+ }
+
+ /// Converts the `UioDeviceOptions` into a kernel-compatible `struct uio_info`.
+ ///
+ /// This method transforms the Rust representation of UIO device options into the
+ /// kernel's `uio_info` structure. It also registers the relevant callbacks for
+ /// device operations such as `open`, `release`, `mmap`, `handler`, and `irqcontrol`.
+ pub fn into_raw_info<T: UioDevice>(self) -> bindings::uio_info {
+ const fn maybe_fn<T: Copy>(check: bool, func: T) -> Option<T> {
+ if check {
+ Some(func)
+ } else {
+ None
+ }
+ }
+
+ // SAFETY: zero initialize, valid
+ let mut result: bindings::uio_info = unsafe { MaybeUninit::zeroed().assume_init() };
+ result.name = self.name.as_char_ptr();
+ result.version = self.version.as_char_ptr();
+ result.irq = self.irq as i32 as _;
+
+ // SAFETY: kernel `struct uio_mem` and `UioDeviceMemmap` has same memory layout
+ result.mem.copy_from_slice(unsafe {
+ slice::from_raw_parts(self.mem.as_ptr().cast(), MAX_UIO_MAPS)
+ });
+ result.open = Some(uio_open::<T>);
+ result.release = Some(uio_release::<T>);
+ result.mmap = maybe_fn(T::HAS_MMAP, uio_mmap::<T>);
+ result.handler = maybe_fn(T::HAS_HANDLER, uio_handler::<T>);
+ result.irqcontrol = maybe_fn(T::HAS_IRQCONTROL, uio_irqcontrol::<T>);
+
+ result
+ }
+}
+
+/// Options for configuring a UIO (Userspace I/O) device memory.
+#[repr(transparent)]
+pub struct UioDeviceMemOptions(bindings::uio_mem);
+
+impl UioDeviceMemOptions {
+ /// Creates a new, zero-initialized `UioDeviceMemmap`.
+ pub const fn new() -> Self {
+ // SAFETY: `MaybeUninit::zeroed()` ensures the memory is initialized to zero,
+ // which is a valid initial state for `uio_mem`.
+ unsafe { MaybeUninit::zeroed().assume_init() }
+ }
+
+ /// Sets the name of the memory region.
+ ///
+ /// This method assigns a name to the memory region, which can be used to
+ /// identify it in user space.
+ pub fn set_name(&mut self, name: &CStr) {
+ self.0.name = name.as_char_ptr();
+ }
+
+ /// Sets the address of the memory region.
+ ///
+ /// This method specifies the physical or virtual address of the memory region.
+ pub fn set_addr(&mut self, addr: usize) {
+ self.0.addr = addr as _;
+ }
+
+ /// Sets the memory type for the region.
+ ///
+ /// The memory type determines how the memory region is mapped or accessed.
+ pub fn set_type(&mut self, ty: MemType) {
+ self.0.memtype = ty as _;
+ }
+
+ /// Sets the size of the memory region.
+ ///
+ /// This method specifies the size of the memory region in bytes.
+ pub fn set_size(&mut self, size: usize) {
+ self.0.size = size as _;
+ }
+}
+
+/// uio registration
+#[pin_data(PinnedDrop)]
+pub struct Registration<T> {
+ #[pin]
+ uio_info: Opaque<bindings::uio_info>,
+ _phantom: PhantomData<T>,
+}
+
+// SAFETY: It is allowed to call `__uio_register_device` on a different thread from where you called
+// `__uio_register_device`.
+unsafe impl<T> Send for Registration<T> {}
+// SAFETY: It is safe to call them in parallel.
+unsafe impl<T> Sync for Registration<T> {}
+
+impl<T: UioDevice> Registration<T> {
+ /// register an uio driver
+ pub fn register<'a>(
+ module: &'static ThisModule,
+ dev: &'a device::Device,
+ options: UioDeviceOptions,
+ ) -> impl PinInit<Self, Error> + use<'a, T> {
+ try_pin_init!(Self {
+ uio_info <- Opaque::try_ffi_init(move |slot: *mut bindings::uio_info| {
+ // SAFETY: The initializer can write to the provided `slot`.
+ unsafe { slot.write(options.into_raw_info::<T>()) };
+
+ // SAFETY: We just wrote the uio device options to the slot. The uio device will
+ // get unregistered before `slot` is deallocated because the memory is pinned and
+ // the destructor of this type deallocates the memory.
+ // INVARIANT: If this returns `Ok(())`, then the `slot` will contain a registered
+ // uio device.
+ to_result(unsafe {
+ bindings::__uio_register_device(module.as_ptr(), dev.as_raw(), slot)
+ })
+ }),
+ _phantom: PhantomData,
+ })
+ }
+
+ /// get the uio driver info
+ pub fn info(&self) -> &Info {
+ // SAFETY: self.uio_info is valid.
+ unsafe { &*self.uio_info.get().cast() }
+ }
+}
+
+#[pinned_drop]
+impl<T> PinnedDrop for Registration<T> {
+ fn drop(self: Pin<&mut Self>) {
+ // SAFETY: We know that the device is registered by the type invariants.
+ unsafe {
+ bindings::uio_unregister_device(self.uio_info.get());
+ };
+ }
+}
+
+/// A trait representing a UIO (Userspace I/O) device.
+///
+/// This trait provides an interface for implementing UIO device behavior in Rust.
+/// It defines methods for handling device lifecycle events (`open`, `release`) and
+/// optional functionalities such as interrupt handling and memory mapping. Implementors
+/// can customize these methods to suit the specific requirements of their device.
+#[vtable]
+pub trait UioDevice {
+ /// The type of pointer used to wrap `Self`.
+ type Ptr: ForeignOwnable + Send + Sync;
+
+ /// Called when the UIO device is opened.
+ fn open(_info: &Info) -> Result<Self::Ptr>;
+
+ /// Called when the UIO device is released.
+ fn release(device: Self::Ptr, _info: &Info) {
+ drop(device);
+ }
+
+ /// Called to control device interrupts.
+ fn irqcontrol(
+ _device: <Self::Ptr as ForeignOwnable>::Borrowed<'_>,
+ _info: &Info,
+ _irq_on: i32,
+ ) -> Result<()> {
+ kernel::build_error(VTABLE_DEFAULT_ERROR)
+ }
+
+ /// Called to handle an interrupt for the UIO device.
+ fn handler(
+ _device: <Self::Ptr as ForeignOwnable>::Borrowed<'_>,
+ _info: &Info,
+ _irq: ffi::c_int,
+ ) -> bindings::irqreturn_t {
+ kernel::build_error(VTABLE_DEFAULT_ERROR)
+ }
+
+ /// Called to handle memory mapping for the UIO device.
+ fn mmap(
+ _device: <Self::Ptr as ForeignOwnable>::Borrowed<'_>,
+ _info: &Info,
+ _vma: &VmAreaNew,
+ ) -> Result<()> {
+ kernel::build_error(VTABLE_DEFAULT_ERROR)
+ }
+}
+
+/// # Safety
+///
+/// `info` must be a valid `struct uio_info` that is associated with `T`.
+/// `inode` must be the inode for a file that is being released.
+unsafe extern "C" fn uio_open<T: UioDevice>(
+ info: *mut bindings::uio_info,
+ _inode: *mut bindings::inode,
+) -> ffi::c_int {
+ // SAFETY: The caller provides a info that is valid.
+ let ptr = match T::open(unsafe { Info::from_raw(info) }) {
+ Ok(ptr) => ptr,
+ Err(err) => return err.to_errno(),
+ };
+
+ // SAFETY: The `T::open` implementation guarantees that the returned pointer is valid.
+ // We safely store it in the `priv_` field of the `uio_info` structure.
+ unsafe {
+ (*info).priv_ = ptr.into_foreign().cast_mut();
+ }
+
+ 0
+}
+
+/// # Safety
+///
+/// `info` must be a valid `struct uio_info` that is associated with `T`.
+/// `inode` must be the inode for a file that is undergoing initialization.
+unsafe extern "C" fn uio_release<T: UioDevice>(
+ info: *mut bindings::uio_info,
+ _inode: *mut bindings::inode,
+) -> ffi::c_int {
+ // SAFETY: The caller guarantees that `info` is a valid pointer to a `struct uio_info`.
+ let private = unsafe { (*info).priv_ };
+ // SAFETY: The `priv_` field is expected to point to a valid instance of the type managed
+ // by `ForeignOwnable` for `T::Ptr`. The caller must ensure this invariant.
+ let ptr = unsafe { <T::Ptr as ForeignOwnable>::from_foreign(private) };
+ // SAFETY: The caller provides a info that is valid.
+ let info = unsafe { Info::from_raw(info) };
+
+ T::release(ptr, info);
+
+ 0
+}
+
+/// # Safety
+///
+/// `info` must be a valid `struct uio_info` that is associated with `T`.
+unsafe extern "C" fn uio_irqcontrol<T: UioDevice>(
+ info: *mut bindings::uio_info,
+ irq_on: ffi::c_int,
+) -> ffi::c_int {
+ // SAFETY: The caller guarantees that `info` is a valid pointer to a `struct uio_info`.
+ let private = unsafe { (*info).priv_ };
+ // SAFETY: The `priv_` field is expected to point to a valid instance of the type
+ // managed by `ForeignOwnable` for `T::Ptr`. The caller must ensure this invariant.
+ let device = unsafe { <T::Ptr as ForeignOwnable>::borrow(private) };
+ // SAFETY: The caller provides a info that is valid.
+ let info = unsafe { Info::from_raw(info) };
+
+ match T::irqcontrol(device, info, irq_on as _) {
+ Ok(()) => 0,
+ Err(err) => err.to_errno(),
+ }
+}
+
+/// # Safety
+///
+/// `info` must be a valid `struct uio_info` that is associated with `T`.
+unsafe extern "C" fn uio_handler<T: UioDevice>(
+ irq: ffi::c_int,
+ dev_info: *mut bindings::uio_info,
+) -> bindings::irqreturn_t {
+ // SAFETY: The caller guarantees that `info` is a valid pointer to a `struct uio_info`.
+ let private = unsafe { (*dev_info).priv_ };
+ // SAFETY: The `priv_` field is expected to point to a valid instance of the type
+ // managed by `ForeignOwnable` for `T::Ptr`. The caller must ensure this invariant.
+ let device = unsafe { <T::Ptr as ForeignOwnable>::borrow(private) };
+ // SAFETY: The caller provides a info that is valid.
+ let info = unsafe { Info::from_raw(dev_info) };
+
+ T::handler(device, info, irq)
+}
+
+/// # Safety
+///
+/// `info` must be a valid `struct uio_info` that is associated with `T`.
+/// `vma` must be a vma that is currently being mmap'ed with this file.
+unsafe extern "C" fn uio_mmap<T: UioDevice>(
+ info: *mut bindings::uio_info,
+ vma: *mut bindings::vm_area_struct,
+) -> ffi::c_int {
+ // SAFETY: The caller guarantees that `info` is a valid pointer to a `struct uio_info`.
+ let private = unsafe { (*info).priv_ };
+ // SAFETY: The `priv_` field is expected to point to a valid instance of the type
+ // managed by `ForeignOwnable` for `T::Ptr`. The caller must ensure this invariant.
+ let device = unsafe { <T::Ptr as ForeignOwnable>::borrow(private) };
+ // SAFETY: The caller provides a vma that is undergoing initial VMA setup.
+ let area = unsafe { VmAreaNew::from_raw(vma) };
+ // SAFETY: The caller provides a info that is valid.
+ let info = unsafe { Info::from_raw(info) };
+
+ match T::mmap(device, info, area) {
+ Ok(()) => 0,
+ Err(err) => err.to_errno(),
+ }
+}
+
+/// Wrapper for the kernel's `struct uio_info`.
+#[repr(transparent)]
+pub struct Info {
+ inner: Opaque<bindings::uio_info>,
+}
+
+impl Info {
+ /// Gets a raw pointer to the underlying `struct uio_info`.
+ #[inline]
+ pub fn as_raw(&self) -> *mut bindings::uio_info {
+ self.inner.get()
+ }
+
+ /// Creates a reference to `Info` from a raw pointer to `struct uio_info`.
+ ///
+ /// # Safety
+ /// - Callers must ensure that `ptr` is valid for the duration of 'a
+ /// - Callers must ensure that `ptr` point to a valid `struct uio_info`, which
+ /// initialize by `__uio_register_device`
+ #[inline]
+ pub unsafe fn from_raw<'a>(ptr: *mut bindings::uio_info) -> &'a Self {
+ // SAFETY: The caller ensures that the invariants are satisfied for the duration of 'a.
+ unsafe { &*ptr.cast() }
+ }
+
+ /// Notifies the kernel that an event has occurred on the UIO device.
+ pub fn notify(&self) {
+ // SAFETY: Only from `Info::from_raw`, which guarantee that `inner` is valid.
+ unsafe {
+ bindings::uio_event_notify(self.inner.get());
+ }
+ }
+
+ /// Return `Device` associated with this `Info`
+ pub fn get_device(&self) -> Device {
+ // SAFETY: Only from `Info::from_raw`, which guarantee that `inner` is valid.
+ let udev = unsafe { (*self.inner.get()).uio_dev };
+ // SAFETY: `(*udev).dev` is a valid device.
+ let dev = unsafe { device::Device::get_device(&mut (*udev).dev) };
+ // SAFETY: `dev` is from `uio_device`.
+ unsafe { Device::from_dev(dev) }
+ }
+}
+
+/// IRQ (Interrupt Request) types for UIO.
+pub mod irq {
+ /// A custom IRQ type defined by the driver.
+ /// Used when the interrupt mechanism does not conform to standard types.
+ pub const UIO_IRQ_CUSTOM: u32 = bindings::UIO_IRQ_CUSTOM as _;
+ /// No interrupt is used. The driver does not signal the user-space application via IRQs.
+ pub const UIO_IRQ_NONE: u32 = bindings::UIO_IRQ_NONE as _;
+}
+
+/// Types of memory address mapping for UIO devices.
+pub enum MemType {
+ /// No memory is mapped.
+ None = bindings::UIO_MEM_NONE as _,
+ /// Physical memory address mapping.
+ Physical = bindings::UIO_MEM_PHYS as _,
+ /// Logical memory address mapping.
+ Logical = bindings::UIO_MEM_LOGICAL as _,
+ /// Virtual memory address mapping.
+ Virtual = bindings::UIO_MEM_VIRTUAL as _,
+ /// IO virtual address (IOVA) mapping.
+ IoVirtual = bindings::UIO_MEM_IOVA as _,
+}
+
+/// kernel's `struct uio_device`
+#[derive(Clone)]
+pub struct Device(ARef<device::Device>);
+
+impl Device {
+ /// Convert a raw kernel device into a `Device`
+ ///
+ /// # Safety
+ ///
+ /// `dev` must be an `Aref<device::Device>` whose underlying `bindings::device` is a member of a
+ /// `bindings::uio_device`.
+ pub unsafe fn from_dev(dev: ARef<device::Device>) -> Self {
+ Self(dev)
+ }
+}
+
+impl AsRef<device::Device> for Device {
+ fn as_ref(&self) -> &device::Device {
+ &self.0
+ }
+}
--
2.34.1
Powered by blists - more mailing lists